|
|
@@ -15,6 +15,7 @@
|
|
15
|
15
|
客户档案
|
|
16
|
16
|
</div>
|
|
17
|
17
|
</template>
|
|
|
18
|
+
|
|
18
|
19
|
<div class="space-y-4">
|
|
19
|
20
|
<div class="bg-white rounded-lg p-4 shadow-sm">
|
|
20
|
21
|
<div class="flex justify-between items-center mb-4">
|
|
|
@@ -111,7 +112,78 @@
|
|
111
|
112
|
</div>
|
|
112
|
113
|
</div>
|
|
113
|
114
|
</el-tab-pane>
|
|
114
|
|
-
|
|
|
115
|
+
|
|
|
116
|
+ <!-- 坐席助手 -->
|
|
|
117
|
+ <el-tab-pane>
|
|
|
118
|
+ <template #label>
|
|
|
119
|
+ <div class="flex items-center">
|
|
|
120
|
+ <el-icon class="mr-2">
|
|
|
121
|
+ <Service />
|
|
|
122
|
+ </el-icon>
|
|
|
123
|
+ 坐席助手
|
|
|
124
|
+ </div>
|
|
|
125
|
+ </template>
|
|
|
126
|
+ <div class="space-y-4">
|
|
|
127
|
+ <!-- 实时语音识别区域 -->
|
|
|
128
|
+ <div class="bg-white rounded-lg p-4 shadow-sm">
|
|
|
129
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
130
|
+ <h3 class="text-lg font-medium">语音识别</h3>
|
|
|
131
|
+ <div class="flex items-center space-x-2">
|
|
|
132
|
+ <el-tag :type="voiceStatus === 'active' ? 'success' : 'info'" size="small">
|
|
|
133
|
+ {{ voiceStatus === 'active' ? '识别中' : '未开始' }}
|
|
|
134
|
+ </el-tag>
|
|
|
135
|
+ </div>
|
|
|
136
|
+ </div>
|
|
|
137
|
+ <div class="relative mb-4">
|
|
|
138
|
+ <div class="absolute left-0 top-0 w-full h-16 bg-gradient-to-b from
|
|
|
139
|
+from-white to-transparent pointer-events-none z-10"></div>
|
|
|
140
|
+ <div ref="transcriptContainer" class="h-[500px] overflow-y-auto pr-4 space-y-3">
|
|
|
141
|
+ <div v-for="(item, index) in transcripts" :key="index" class="p-3 rounded-lg"
|
|
|
142
|
+ :class="[item.direction === 2 ? 'bg-blue-50' : 'bg-gray-50']">
|
|
|
143
|
+ <div class="flex items-center mb-1">
|
|
|
144
|
+ <span class="text-xs text-gray-500">{{ item.timestamp }}</span>
|
|
|
145
|
+ <span class="text-xs font-medium ml-2"
|
|
|
146
|
+ :class="[item.direction === 2 ? 'text-blue-600' : 'text-gray-600']">
|
|
|
147
|
+ {{ item.direction === 2 ? '客户' : '坐席' }}
|
|
|
148
|
+ </span>
|
|
|
149
|
+ <div class="flex-1"></div>
|
|
|
150
|
+ <!-- <el-tag size="small" effect="plain" class="ml-2"
|
|
|
151
|
+ :type="item.emotion === 'positive' ? 'success' : item.emotion === 'negative' ? 'danger' : 'info'">
|
|
|
152
|
+ {{ item.emotion === 'positive' ? '积极' : item.emotion === 'negative'
|
|
|
153
|
+ ? '消极' : '中性' }}
|
|
|
154
|
+ </el-tag> -->
|
|
|
155
|
+ </div>
|
|
|
156
|
+ <p class="text-sm text-gray-700">{{ item.page_content }}</p>
|
|
|
157
|
+ </div>
|
|
|
158
|
+ </div>
|
|
|
159
|
+ <div
|
|
|
160
|
+ class="absolute left-0 bottom-0 w-full h-16 bg-gradient-to-t from-white to-transparent pointer-events-none">
|
|
|
161
|
+ </div>
|
|
|
162
|
+ </div>
|
|
|
163
|
+ <!-- 关键词提示 -->
|
|
|
164
|
+ <!-- <div class="space-y-2">
|
|
|
165
|
+ <h4 class="font-medium text-sm text-gray-600">关键词提示</h4>
|
|
|
166
|
+ <div class="flex flex-wrap gap-2">
|
|
|
167
|
+ <el-tag v-for="(keyword, idx) in keywords" :key="idx" :type="keyword.type"
|
|
|
168
|
+ effect="plain" size="small">
|
|
|
169
|
+ {{ keyword.text }}
|
|
|
170
|
+ </el-tag>
|
|
|
171
|
+ </div>
|
|
|
172
|
+ </div> -->
|
|
|
173
|
+ <!-- 建议话术 -->
|
|
|
174
|
+ <!-- <div class="mt-4 space-y-2">
|
|
|
175
|
+ <h4 class="font-medium text-sm text-gray-600">建议话术</h4>
|
|
|
176
|
+ <div class="space-y-2">
|
|
|
177
|
+ <div v-for="(suggestion, idx) in suggestions" :key="idx"
|
|
|
178
|
+ class="p-2 bg-gray-50 rounded text-sm text-gray-600 cursor-pointer hover:bg-gray-100">
|
|
|
179
|
+ {{ suggestion }}
|
|
|
180
|
+ </div>
|
|
|
181
|
+ </div>
|
|
|
182
|
+ </div> -->
|
|
|
183
|
+ </div>
|
|
|
184
|
+ </div>
|
|
|
185
|
+ </el-tab-pane>
|
|
|
186
|
+
|
|
115
|
187
|
<!-- 通话记录 -->
|
|
116
|
188
|
<el-tab-pane>
|
|
117
|
189
|
<template #label>
|
|
|
@@ -124,34 +196,35 @@
|
|
124
|
196
|
</template>
|
|
125
|
197
|
<div class="space-y-4">
|
|
126
|
198
|
<!-- <el-input v-model="workOrderSearchQuery" placeholder="搜索工单..." :prefix-icon="Search" /> -->
|
|
127
|
|
- <el-date-picker v-model="callSearchQuery"
|
|
128
|
|
- type="datetimerange"
|
|
129
|
|
- @change="callChange"
|
|
130
|
|
- range-separator="至"
|
|
131
|
|
- start-placeholder="开始日期"
|
|
132
|
|
- end-placeholder="结束日期"/>
|
|
133
|
|
- <div class="space-y-2 overflow-y-auto" style="height: 100%;" ref="callContainer" @scroll="handleCallScroll">
|
|
|
199
|
+ <el-date-picker v-model="callSearchQuery" type="datetimerange" @change="callChange"
|
|
|
200
|
+ range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
|
|
|
201
|
+ <div class="space-y-2 overflow-y-auto" style="height: 100%;" ref="callContainer"
|
|
|
202
|
+ @scroll="handleCallScroll">
|
|
134
|
203
|
<div v-for="(item, index) in callData" :key="index"
|
|
135
|
204
|
class="bg-white p-3 rounded-lg shadow-sm hover:shadow-md transition-shadow mb-4 border border-gray-100">
|
|
136
|
205
|
<div class="flex justify-between items-start">
|
|
137
|
|
- <div>
|
|
|
206
|
+ <div>
|
|
138
|
207
|
<!-- <h4 class="font-medium">{{ getOfffixNuber(item.callType==1 ? item.caller : item.callee) }}</h4> -->
|
|
139
|
208
|
<div class="flex items-center gap-2 mt-1">
|
|
140
|
|
- <span v-if="item.callerAgent || item.calleeAgent" style="margin-right: 15px;">
|
|
141
|
|
- 坐席工号:{{ item.callType==2 ? item.callerAgent : item.calleeAgent }}
|
|
|
209
|
+ <span v-if="item.callerAgent || item.calleeAgent"
|
|
|
210
|
+ style="margin-right: 15px;">
|
|
|
211
|
+ 坐席工号:{{ item.callType == 2 ? item.callerAgent : item.calleeAgent }}
|
|
142
|
212
|
</span>
|
|
143
|
213
|
<span v-if="item.caller || item.callee">
|
|
144
|
|
- 分机号:{{ item.callType==2 ? item.caller : item.callee }}
|
|
|
214
|
+ 分机号:{{ item.callType == 2 ? item.caller : item.callee }}
|
|
145
|
215
|
</span>
|
|
146
|
|
-
|
|
|
216
|
+
|
|
147
|
217
|
</div>
|
|
148
|
218
|
</div>
|
|
149
|
|
- <span class="text-xs text-gray-500">{{ getCallSate(item.callType, item.isAnswer) }}</span>
|
|
|
219
|
+ <span class="text-xs text-gray-500">{{ getCallSate(item.callType, item.isAnswer)
|
|
|
220
|
+ }}</span>
|
|
150
|
221
|
</div>
|
|
151
|
222
|
<div class="text-sm text-gray-600 my-2 cursor-pointer">
|
|
152
|
|
- <div v-if="item.answerTime">通话开始时间:{{item.answerTime}}</div>
|
|
153
|
|
- <div v-if="item.hangupTime">通话结束时间:{{item.hangupTime}}</div>
|
|
154
|
|
- <div v-if="item.answerTime && item.hangupTime">通话时长:{{getTimeLimit(item.answerTime, item.hangupTime)}}</div>
|
|
|
223
|
+ <div v-if="item.answerTime">通话开始时间:{{ item.answerTime }}</div>
|
|
|
224
|
+ <div v-if="item.hangupTime">通话结束时间:{{ item.hangupTime }}</div>
|
|
|
225
|
+ <div v-if="item.answerTime && item.hangupTime">
|
|
|
226
|
+ 通话时长:{{ getTimeLimit(item.answerTime,
|
|
|
227
|
+ item.hangupTime)}}</div>
|
|
155
|
228
|
</div>
|
|
156
|
229
|
</div>
|
|
157
|
230
|
<div v-if="callLoading" class="flex justify-center items-center py-4">
|
|
|
@@ -176,19 +249,18 @@
|
|
176
|
249
|
</div>
|
|
177
|
250
|
</template>
|
|
178
|
251
|
<div class="space-y-4">
|
|
179
|
|
- <el-date-picker v-model="orderSearchQuery"
|
|
180
|
|
- type="datetimerange"
|
|
181
|
|
- @change="orderChange"
|
|
182
|
|
- range-separator="至"
|
|
183
|
|
- start-placeholder="开始日期"
|
|
184
|
|
- end-placeholder="结束日期"/>
|
|
|
252
|
+ <el-date-picker v-model="orderSearchQuery" type="datetimerange" @change="orderChange"
|
|
|
253
|
+ range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
|
|
185
|
254
|
<!-- <el-input v-model="workOrderSearchQuery" placeholder="搜索工单..." :prefix-icon="Search" /> -->
|
|
186
|
|
- <div class="space-y-2 overflow-y-auto" style="height: 100%;" ref="workOrderContainer" @scroll="handleScroll">
|
|
|
255
|
+ <div class="space-y-2 overflow-y-auto" style="height: 100%;" ref="workOrderContainer"
|
|
|
256
|
+ @scroll="handleScroll">
|
|
187
|
257
|
<div v-for="(item, index) in displayedActivities" :key="index"
|
|
188
|
258
|
class="bg-white p-3 rounded-lg shadow-sm hover:shadow-md transition-shadow mb-4 border border-gray-100">
|
|
189
|
259
|
<div class="flex justify-between items-start">
|
|
190
|
260
|
<div>
|
|
191
|
|
- <h4 class="font-medium">{{ item.workordercatename ? item.workordercatename.substring(item.workordercatename.lastIndexOf('/') + 1) : '' }}</h4>
|
|
|
261
|
+ <h4 class="font-medium">{{ item.workordercatename ?
|
|
|
262
|
+ item.workordercatename.substring(item.workordercatename.lastIndexOf('/')
|
|
|
263
|
+ + 1) : '' }}</h4>
|
|
192
|
264
|
<!-- <div class="flex items-center gap-2 mt-1">
|
|
193
|
265
|
<el-tag size="small" type="info" class="!rounded-full">{{ item.workordercatename }}</el-tag>
|
|
194
|
266
|
</div> -->
|
|
|
@@ -209,7 +281,8 @@
|
|
209
|
281
|
<Document />
|
|
210
|
282
|
</el-icon> -->
|
|
211
|
283
|
<span>办理结果</span>
|
|
212
|
|
- <span class="text-gray-400 ml-2">{{ item.endtime ? `[${item.endtime}]` : '' }}</span>
|
|
|
284
|
+ <span class="text-gray-400 ml-2">{{ item.endtime ?
|
|
|
285
|
+ `[${item.endtime}]` : '' }}</span>
|
|
213
|
286
|
</div>
|
|
214
|
287
|
<!-- <div class="text-gray-400">处理部门:{{ item.department || '客服部' }}</div> -->
|
|
215
|
288
|
</div>
|
|
|
@@ -268,7 +341,12 @@
|
|
268
|
341
|
<!-- 右侧工单区 -->
|
|
269
|
342
|
<div class="w-[65%] space-y-6">
|
|
270
|
343
|
<div class="bg-white rounded-lg p-6 shadow-sm">
|
|
271
|
|
- <h2 class="text-xl font-medium mb-6">工单信息</h2>
|
|
|
344
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
345
|
+ <h2 class="text-xl font-medium">工单信息</h2>
|
|
|
346
|
+
|
|
|
347
|
+ <el-button :loading-icon="Eleme" class="flex items-center gap-1" type="primary" link @click="aiSubmit"
|
|
|
348
|
+ :loading="aiLoading">智能填单</el-button>
|
|
|
349
|
+ </div>
|
|
272
|
350
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
|
273
|
351
|
<el-row :gutter="20">
|
|
274
|
352
|
<el-col :span="12">
|
|
|
@@ -283,14 +361,18 @@
|
|
283
|
361
|
</el-col>
|
|
284
|
362
|
</el-row>
|
|
285
|
363
|
<el-form-item label="工单类型" required prop="type">
|
|
286
|
|
- <el-cascader ref="typeCascaderRef" class="w-full" v-model="form.type" :props="{value: 'id',}" placeholder="请选择工单类型" :options="ticketTypes" filterable clearable @change="handleTypeChange" />
|
|
|
364
|
+ <el-cascader ref="typeCascaderRef" class="w-full" v-model="form.type"
|
|
|
365
|
+ :props="{ value: 'id', }" placeholder="请选择工单类型" :options="ticketTypes" filterable
|
|
|
366
|
+ clearable @change="handleTypeChange" />
|
|
287
|
367
|
</el-form-item>
|
|
288
|
368
|
<el-form-item v-if="form.typeCode === 'complain'" label="投诉部门" required prop="department">
|
|
289
|
369
|
<!-- <el-select v-model="form.department" placeholder="请选择投诉部门" class="w-full">
|
|
290
|
370
|
<el-option v-for="item in departments" :key="item.value" :label="item.label"
|
|
291
|
371
|
:value="item.value" />
|
|
292
|
372
|
</el-select> -->
|
|
293
|
|
- <el-cascader ref="deptCascaderRef" class="w-full" v-model="form.department" :props="{checkStrictly: true}" placeholder="请选择投诉科室" :options="departments" filterable @change="deptChange"/>
|
|
|
373
|
+ <el-cascader ref="deptCascaderRef" class="w-full" v-model="form.department"
|
|
|
374
|
+ :props="{ checkStrictly: true }" placeholder="请选择投诉科室" :options="departments" filterable
|
|
|
375
|
+ @change="deptChange" />
|
|
294
|
376
|
</el-form-item>
|
|
295
|
377
|
<el-form-item label="问题描述" required prop="description">
|
|
296
|
378
|
<el-input v-model="form.description" type="textarea" :rows="4" placeholder="请详细描述问题" />
|
|
|
@@ -313,7 +395,8 @@
|
|
313
|
395
|
</el-form-item> -->
|
|
314
|
396
|
<!-- 悬浮提交按钮 -->
|
|
315
|
397
|
<div class="bottom-0 left-0 right-0 p-4 bg-white border-t flex justify-end">
|
|
316
|
|
- <el-button type="primary" class="w-32 !rounded-button whitespace-nowrap" @click="submit">提交工单</el-button>
|
|
|
398
|
+ <el-button type="primary" class="w-32 !rounded-button whitespace-nowrap"
|
|
|
399
|
+ @click="submit">提交工单</el-button>
|
|
317
|
400
|
</div>
|
|
318
|
401
|
</el-form>
|
|
319
|
402
|
|
|
|
@@ -325,7 +408,7 @@
|
|
325
|
408
|
</div>
|
|
326
|
409
|
</template>
|
|
327
|
410
|
<script lang="ts" setup name="CallScreen">
|
|
328
|
|
-import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
|
411
|
+import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
|
329
|
412
|
import moment from 'moment';
|
|
330
|
413
|
import {
|
|
331
|
414
|
UserFilled,
|
|
|
@@ -345,7 +428,9 @@ import {
|
|
345
|
428
|
Connection,
|
|
346
|
429
|
List,
|
|
347
|
430
|
Notebook,
|
|
348
|
|
- Edit
|
|
|
431
|
+ Edit,
|
|
|
432
|
+ Eleme,
|
|
|
433
|
+ Loading
|
|
349
|
434
|
} from '@element-plus/icons-vue';
|
|
350
|
435
|
import { hidePhone } from '@/utils/tools';
|
|
351
|
436
|
import { ElMessage } from 'element-plus';
|
|
|
@@ -354,13 +439,15 @@ import { getPageListData, createPageData } from '@/api/main/system/system';
|
|
354
|
439
|
import { userDecryptToAsterisk } from '@/utils/aes';
|
|
355
|
440
|
import knowledgeList from "@/views/main/knowledgeBase/knowledgeList/cpns/konwlegelist/konwlegelist";
|
|
356
|
441
|
|
|
357
|
|
-import { max } from 'lodash';
|
|
358
|
|
-import { getOfffixNuber,getCallSate } from '@/utils/index';
|
|
|
442
|
+import { max,flatten } from 'lodash';
|
|
|
443
|
+import { getOfffixNuber, getCallSate } from '@/utils/index';
|
|
359
|
444
|
|
|
360
|
445
|
const router = useRouter()
|
|
361
|
446
|
let { proxy } = getCurrentInstance()
|
|
362
|
447
|
const showCallPanel = ref(false);
|
|
363
|
448
|
const searchQuery = ref('');
|
|
|
449
|
+const aiLoading = ref(false);
|
|
|
450
|
+const isCanAutoScroll = ref(1);
|
|
364
|
451
|
console.log('proxy', proxy.$route);
|
|
365
|
452
|
// console.log(proxy.$route.query.callNumber)
|
|
366
|
453
|
const telNumber = ref(proxy.$route.query.phone || proxy.$route.query.callNumber || proxy.$route.params.callNumber);
|
|
|
@@ -395,7 +482,7 @@ const loadMoreData = async () => {
|
|
395
|
482
|
loading.value = true;
|
|
396
|
483
|
await new Promise(resolve => setTimeout(resolve, 500)); // 模拟加载延迟
|
|
397
|
484
|
const res = await getWorkOrders();
|
|
398
|
|
- if (res.length === 0) {
|
|
|
485
|
+ if (res?.length === 0) {
|
|
399
|
486
|
noMore.value = true;
|
|
400
|
487
|
} else {
|
|
401
|
488
|
pageIndex.value++;
|
|
|
@@ -411,15 +498,16 @@ const handleScroll = async (event: Event) => {
|
|
411
|
498
|
}
|
|
412
|
499
|
};
|
|
413
|
500
|
// 通话记录开始
|
|
414
|
|
-const callData =ref([])
|
|
|
501
|
+const callData = ref([])
|
|
415
|
502
|
const callLoading = ref(false)
|
|
416
|
503
|
const callNoMore = ref(false);
|
|
417
|
504
|
const loadMoreCallData = async () => {
|
|
418
|
505
|
if (callLoading.value || callNoMore.value) return;
|
|
419
|
506
|
callLoading.value = true;
|
|
420
|
|
- await new Promise(resolve => setTimeout(resolve, 500)); // 模拟加载延迟
|
|
|
507
|
+ // await new Promise(resolve => setTimeout(resolve, 500)); // 模拟加载延迟
|
|
421
|
508
|
const res = await getCalllog();
|
|
422
|
|
- if (res.length === 0) {
|
|
|
509
|
+ console.log(res, 'res')
|
|
|
510
|
+ if (res?.length === 0) {
|
|
423
|
511
|
callNoMore.value = true;
|
|
424
|
512
|
} else {
|
|
425
|
513
|
// displayedActivities.value.push(...newItems);
|
|
|
@@ -442,9 +530,9 @@ const callAnswerTime = ref()
|
|
442
|
530
|
const callHangupTime = ref()
|
|
443
|
531
|
function callChange(e) {
|
|
444
|
532
|
console.log(e)
|
|
445
|
|
- if (e.length>0) {
|
|
|
533
|
+ if (e.length > 0) {
|
|
446
|
534
|
callAnswerTime.value = moment(e[0]).format('YYYY-MM-DD HH:mm:ss')
|
|
447
|
|
- callHangupTime.value = moment(e[1]).format('YYYY-MM-DD HH:mm:ss')
|
|
|
535
|
+ callHangupTime.value = moment(e[1]).format('YYYY-MM-DD HH:mm:ss')
|
|
448
|
536
|
}
|
|
449
|
537
|
callPageIndex.value = 1
|
|
450
|
538
|
callData.value = []
|
|
|
@@ -460,17 +548,17 @@ const getCalllog = async () => {
|
|
460
|
548
|
}
|
|
461
|
549
|
if (callAnswerTime.value) {
|
|
462
|
550
|
params.answerTime = callAnswerTime.value
|
|
463
|
|
- }
|
|
|
551
|
+ }
|
|
464
|
552
|
if (callHangupTime) {
|
|
465
|
553
|
params.hangupTime = callHangupTime.value
|
|
466
|
554
|
}
|
|
467
|
555
|
const result = await getPageListData('/call/calllog', params);
|
|
468
|
556
|
console.log(result, 'callData');
|
|
469
|
557
|
if (result.state === 'success') {
|
|
470
|
|
- callData.value = callData.value.concat(result.data)
|
|
|
558
|
+ callData.value = callData.value.concat(result.data)
|
|
471
|
559
|
if (callData.value.length === 0) {
|
|
472
|
560
|
callNoMore.value = true;
|
|
473
|
|
- }else {
|
|
|
561
|
+ } else {
|
|
474
|
562
|
callNoMore.value = false;
|
|
475
|
563
|
}
|
|
476
|
564
|
return result.data;
|
|
|
@@ -478,7 +566,7 @@ const getCalllog = async () => {
|
|
478
|
566
|
return [];
|
|
479
|
567
|
}
|
|
480
|
568
|
function getTimeLimit(startTime, endTime) {
|
|
481
|
|
- return proxy.getTimeDifference(startTime, endTime);
|
|
|
569
|
+ return proxy.getTimeDifference(startTime, endTime);
|
|
482
|
570
|
}
|
|
483
|
571
|
// 通话记录结束
|
|
484
|
572
|
onMounted(() => {
|
|
|
@@ -520,7 +608,7 @@ const profile = ref({
|
|
520
|
608
|
});
|
|
521
|
609
|
const formRef: any = ref(null);
|
|
522
|
610
|
const form = ref({
|
|
523
|
|
- name: '',
|
|
|
611
|
+ name: '不详',
|
|
524
|
612
|
phone: '',
|
|
525
|
613
|
type: '',
|
|
526
|
614
|
typeCode: '',
|
|
|
@@ -640,29 +728,29 @@ const submit = async () => {
|
|
640
|
728
|
} else {
|
|
641
|
729
|
ElMessage.error(data.message || '提交失败');
|
|
642
|
730
|
}
|
|
643
|
|
-
|
|
|
731
|
+
|
|
644
|
732
|
})
|
|
645
|
|
-
|
|
|
733
|
+
|
|
646
|
734
|
} else {
|
|
647
|
735
|
ElMessage.error('请填写完整信息');
|
|
648
|
736
|
}
|
|
649
|
|
-
|
|
|
737
|
+
|
|
650
|
738
|
});
|
|
651
|
739
|
}
|
|
652
|
740
|
/** 关闭按钮 */
|
|
653
|
741
|
function close() {
|
|
654
|
|
- const obj = { path: '/' }
|
|
655
|
|
- proxy.$tab.closeOpenPage(obj)
|
|
|
742
|
+ const obj = { path: '/' }
|
|
|
743
|
+ proxy.$tab.closeOpenPage(obj)
|
|
656
|
744
|
}
|
|
657
|
745
|
const orderSearchQuery = ref()
|
|
658
|
746
|
const createtime = ref()
|
|
659
|
747
|
const endtime = ref()
|
|
660
|
748
|
|
|
661
|
|
-function orderChange (e){
|
|
|
749
|
+function orderChange(e) {
|
|
662
|
750
|
console.log(e)
|
|
663
|
|
- if (e.length>0) {
|
|
|
751
|
+ if (e.length > 0) {
|
|
664
|
752
|
createtime.value = moment(e[0]).format('YYYY-MM-DD HH:mm:ss')
|
|
665
|
|
- endtime.value = moment(e[1]).format('YYYY-MM-DD HH:mm:ss')
|
|
|
753
|
+ endtime.value = moment(e[1]).format('YYYY-MM-DD HH:mm:ss')
|
|
666
|
754
|
}
|
|
667
|
755
|
pageIndex.value = 1
|
|
668
|
756
|
displayedActivities.value = []
|
|
|
@@ -678,7 +766,7 @@ const getWorkOrders = async () => {
|
|
678
|
766
|
}
|
|
679
|
767
|
if (createtime.value) {
|
|
680
|
768
|
params.createtime = createtime.value
|
|
681
|
|
- }
|
|
|
769
|
+ }
|
|
682
|
770
|
if (endtime.value) {
|
|
683
|
771
|
params.endtime = endtime.value
|
|
684
|
772
|
}
|
|
|
@@ -689,7 +777,7 @@ const getWorkOrders = async () => {
|
|
689
|
777
|
console.log(displayedActivities.value, 'activities');
|
|
690
|
778
|
if (displayedActivities.value.length === 0) {
|
|
691
|
779
|
noMore.value = true;
|
|
692
|
|
- } else{
|
|
|
780
|
+ } else {
|
|
693
|
781
|
noMore.value = false;
|
|
694
|
782
|
}
|
|
695
|
783
|
return result.data;
|
|
|
@@ -700,15 +788,483 @@ const getWorkOrders = async () => {
|
|
700
|
788
|
const tabChange = (name) => {
|
|
701
|
789
|
console.log(name, 'tabChange');
|
|
702
|
790
|
if (name === '1') {
|
|
703
|
|
- pageIndex.value = 1
|
|
|
791
|
+ pageIndex.value = 1
|
|
704
|
792
|
callPageIndex.value = 1
|
|
705
|
793
|
callData.value = []
|
|
706
|
|
- getCalllog();
|
|
|
794
|
+ getCalllog();
|
|
707
|
795
|
} else if (name === '2') {
|
|
708
|
796
|
pageIndex.value = 1
|
|
709
|
797
|
displayedActivities.value = []
|
|
710
|
798
|
getWorkOrders();
|
|
711
|
|
- }
|
|
|
799
|
+ }
|
|
|
800
|
+
|
|
|
801
|
+}
|
|
|
802
|
+
|
|
|
803
|
+
|
|
|
804
|
+// 语音识别状态
|
|
|
805
|
+const voiceStatus = ref('active');
|
|
|
806
|
+// 识别记录
|
|
|
807
|
+const transcripts111 = ref([
|
|
|
808
|
+ {
|
|
|
809
|
+ role: 'customer',
|
|
|
810
|
+ timestamp: '14:30:24',
|
|
|
811
|
+ content: '您好,我想咨询一下这个智能客服系统的具体使用方法',
|
|
|
812
|
+ emotion: 'positive'
|
|
|
813
|
+ },
|
|
|
814
|
+ {
|
|
|
815
|
+ role: 'agent',
|
|
|
816
|
+ timestamp: '14:30:35',
|
|
|
817
|
+ content: '您好,很高兴为您介绍。我们的智能客服系统主要包含自动应答、多渠道接入、数据分析等核心功能,可以帮助企业提升客服效率',
|
|
|
818
|
+ emotion: 'positive'
|
|
|
819
|
+ },
|
|
|
820
|
+ {
|
|
|
821
|
+ role: 'customer',
|
|
|
822
|
+ timestamp: '14:30:48',
|
|
|
823
|
+ content: '听起来不错,那具体怎么配置自动应答功能呢?',
|
|
|
824
|
+ emotion: 'neutral'
|
|
|
825
|
+ },
|
|
|
826
|
+ {
|
|
|
827
|
+ role: 'agent',
|
|
|
828
|
+ timestamp: '14:31:02',
|
|
|
829
|
+ content: '好的,我来为您详细介绍。首先,您需要登录管理后台,点击右上角的"智能配置"按钮,在弹出的面板中选择"自动应答管理"',
|
|
|
830
|
+ emotion: 'positive'
|
|
|
831
|
+ },
|
|
|
832
|
+ {
|
|
|
833
|
+ role: 'customer',
|
|
|
834
|
+ timestamp: '14:31:15',
|
|
|
835
|
+ content: '找到了,这个界面设计得很直观',
|
|
|
836
|
+ emotion: 'positive'
|
|
|
837
|
+ },
|
|
|
838
|
+ {
|
|
|
839
|
+ role: 'agent',
|
|
|
840
|
+ timestamp: '14:31:28',
|
|
|
841
|
+ content: '是的,接下来您可以在知识库中添加常见问题和答案,系统会自动学习和匹配相似问题',
|
|
|
842
|
+ emotion: 'positive'
|
|
|
843
|
+ },
|
|
|
844
|
+ {
|
|
|
845
|
+ role: 'customer',
|
|
|
846
|
+ timestamp: '14:31:45',
|
|
|
847
|
+ content: '那如果遇到系统无法识别的问题怎么办?',
|
|
|
848
|
+ emotion: 'neutral'
|
|
|
849
|
+ },
|
|
|
850
|
+ {
|
|
|
851
|
+ role: 'agent',
|
|
|
852
|
+ timestamp: '14:32:00',
|
|
|
853
|
+ content: '这种情况系统会自动转人工客服处理,确保客户问题都能得到及时解决。同时系统会记录这些问题,用于后续知识库的优化',
|
|
|
854
|
+ emotion: 'positive'
|
|
|
855
|
+ },
|
|
|
856
|
+ {
|
|
|
857
|
+ role: 'customer',
|
|
|
858
|
+ timestamp: '14:32:18',
|
|
|
859
|
+ content: '明白了,那数据分析功能都包含哪些报表呢?',
|
|
|
860
|
+ emotion: 'positive'
|
|
|
861
|
+ },
|
|
|
862
|
+ {
|
|
|
863
|
+ role: 'agent',
|
|
|
864
|
+ timestamp: '14:32:35',
|
|
|
865
|
+ content: '我们提供了多维度的数据分析报表,包括客服效率分析、问题分类统计、客户满意度趋势、热点问题分析等',
|
|
|
866
|
+ emotion: 'positive'
|
|
|
867
|
+ },
|
|
|
868
|
+ {
|
|
|
869
|
+ role: 'customer',
|
|
|
870
|
+ timestamp: '14:32:52',
|
|
|
871
|
+ content: '这些报表数据可以导出吗?',
|
|
|
872
|
+ emotion: 'neutral'
|
|
|
873
|
+ },
|
|
|
874
|
+ {
|
|
|
875
|
+ role: 'agent',
|
|
|
876
|
+ timestamp: '14:33:08',
|
|
|
877
|
+ content: '可以的,系统支持Excel、PDF等多种格式的报表导出。您可以在报表页面右上角找到导出按钮',
|
|
|
878
|
+ emotion: 'positive'
|
|
|
879
|
+ },
|
|
|
880
|
+ {
|
|
|
881
|
+ role: 'customer',
|
|
|
882
|
+ timestamp: '14:33:25',
|
|
|
883
|
+ content: '太好了,这些功能正是我们需要的',
|
|
|
884
|
+ emotion: 'positive'
|
|
|
885
|
+ },
|
|
|
886
|
+ {
|
|
|
887
|
+ role: 'agent',
|
|
|
888
|
+ timestamp: '14:33:40',
|
|
|
889
|
+ content: '很高兴能帮到您。如果后续使用中有任何问题,随时都可以联系我们的技术支持团队',
|
|
|
890
|
+ emotion: 'positive'
|
|
|
891
|
+ },
|
|
|
892
|
+ {
|
|
|
893
|
+ role: 'customer',
|
|
|
894
|
+ timestamp: '14:33:55',
|
|
|
895
|
+ content: '好的,谢谢你细心的讲解',
|
|
|
896
|
+ emotion: 'positive'
|
|
|
897
|
+ },
|
|
|
898
|
+ {
|
|
|
899
|
+ role: 'agent',
|
|
|
900
|
+ timestamp: '14:34:10',
|
|
|
901
|
+ content: '不客气,为您提供优质的服务是我们的责任。祝您使用愉快!',
|
|
|
902
|
+ emotion: 'positive'
|
|
|
903
|
+ }
|
|
|
904
|
+]);
|
|
|
905
|
+
|
|
|
906
|
+const transcripts = ref([
|
|
|
907
|
+ {
|
|
|
908
|
+ direction: 1,
|
|
|
909
|
+ timestamp: '14:30:24',
|
|
|
910
|
+ page_content: '你好,中国热线请假。',
|
|
|
911
|
+ }, {
|
|
|
912
|
+ direction: 2,
|
|
|
913
|
+ timestamp: '14:30:24',
|
|
|
914
|
+ page_content: '喂,你好,嗯,听这样哦,我是林州这个横岛岳湖人呃',
|
|
|
915
|
+ }, {
|
|
|
916
|
+ direction: 1,
|
|
|
917
|
+ timestamp: '14:30:24',
|
|
|
918
|
+ page_content: '什么问题啊?你确实是无法统一工作呃,按照说实体上头到现在都没有工作了',
|
|
|
919
|
+ }, {
|
|
|
920
|
+ direction: 2,
|
|
|
921
|
+ timestamp: '14:30:24',
|
|
|
922
|
+ page_content: '我来问违约金怎么事儿?嗯,他说他他说是月底工资,',
|
|
|
923
|
+ }, {
|
|
|
924
|
+ direction: 1,
|
|
|
925
|
+ timestamp: '14:30:24',
|
|
|
926
|
+ page_content: '这个麻问您一下,我这边给您登记反映,您是林州市哪里的?',
|
|
|
927
|
+ }, {
|
|
|
928
|
+ direction: 2,
|
|
|
929
|
+ timestamp: '14:30:24',
|
|
|
930
|
+ page_content: '林州恒大悦府,恒大悦府',
|
|
|
931
|
+ }, {
|
|
|
932
|
+ direction: 1,
|
|
|
933
|
+ timestamp: '14:30:24',
|
|
|
934
|
+ page_content: '嗯,乐府乐是那个舒心一个舒心,一个对那个乐是吧?',
|
|
|
935
|
+ }, {
|
|
|
936
|
+ direction: 2,
|
|
|
937
|
+ timestamp: '14:30:24',
|
|
|
938
|
+ page_content: '嗯,对嗯',
|
|
|
939
|
+ }, {
|
|
|
940
|
+ direction: 1,
|
|
|
941
|
+ timestamp: '14:30:24',
|
|
|
942
|
+ page_content: '恒大乐府分几七部分啊,',
|
|
|
943
|
+ }, {
|
|
|
944
|
+ direction: 2,
|
|
|
945
|
+ timestamp: '14:30:24',
|
|
|
946
|
+ page_content: '分级,你们一期二期分不分呃,一期同大月付一期几号楼几单元,十七号楼一单元幺九零一十七号楼一单元幺九零一',
|
|
|
947
|
+ }, {
|
|
|
948
|
+ direction: 1,
|
|
|
949
|
+ timestamp: '14:30:24',
|
|
|
950
|
+ page_content: '嗯,您怎么称呼先生啊?',
|
|
|
951
|
+ }, {
|
|
|
952
|
+ direction: 2,
|
|
|
953
|
+ timestamp: '14:30:24',
|
|
|
954
|
+ page_content: '免贵?我就我我姓李李松,你让李先生吧',
|
|
|
955
|
+ }, {
|
|
|
956
|
+ direction: 1,
|
|
|
957
|
+ timestamp: '14:30:24',
|
|
|
958
|
+ page_content: '给您单心恒大悦府在林州市的哪个路段。您说一下',
|
|
|
959
|
+ }, {
|
|
|
960
|
+ direction: 2,
|
|
|
961
|
+ timestamp: '14:30:24',
|
|
|
962
|
+ page_content: '在这个这个是河南一个湖,也就他就一个湖,河南一个湖,我也不知道这个是什么路段',
|
|
|
963
|
+ }, {
|
|
|
964
|
+ direction: 1,
|
|
|
965
|
+ timestamp: '14:30:24',
|
|
|
966
|
+ page_content: '那您没有路段的话,这个位置不明确,我这边就暂不给您反映啊',
|
|
|
967
|
+ }, {
|
|
|
968
|
+ direction: 2,
|
|
|
969
|
+ timestamp: '14:30:24',
|
|
|
970
|
+ page_content: '嗯,那个是可能是那个呃华城相府,',
|
|
|
971
|
+ }, {
|
|
|
972
|
+ direction: 1,
|
|
|
973
|
+ timestamp: '14:30:24',
|
|
|
974
|
+ page_content: '哪个路段?',
|
|
|
975
|
+ }, {
|
|
|
976
|
+ direction: 2,
|
|
|
977
|
+ timestamp: '14:30:24',
|
|
|
978
|
+ page_content: '不是那个那个是呃鲁八大道,',
|
|
|
979
|
+ }, {
|
|
|
980
|
+ direction: 1,
|
|
|
981
|
+ timestamp: '14:30:24',
|
|
|
982
|
+ page_content: '鲁八大道中段什么大道,',
|
|
|
983
|
+ }, {
|
|
|
984
|
+ direction: 2,
|
|
|
985
|
+ timestamp: '14:30:24',
|
|
|
986
|
+ page_content: '鲁班哪两个字卢班鲁班嗯,',
|
|
|
987
|
+ }, {
|
|
|
988
|
+ direction: 1,
|
|
|
989
|
+ timestamp: '14:30:24',
|
|
|
990
|
+ page_content: '鲁班大道与什么路交叉口?',
|
|
|
991
|
+ }, {
|
|
|
992
|
+ direction: 2,
|
|
|
993
|
+ timestamp: '14:30:24',
|
|
|
994
|
+ page_content: '嗯,鲁班大道一零米,',
|
|
|
995
|
+ },
|
|
|
996
|
+ {
|
|
|
997
|
+ direction: 1,
|
|
|
998
|
+ timestamp: '14:30:24',
|
|
|
999
|
+ page_content: '你们是是什么路来的,',
|
|
|
1000
|
+ }, {
|
|
|
1001
|
+ direction: 2,
|
|
|
1002
|
+ timestamp: '14:30:24',
|
|
|
1003
|
+ page_content: '你就说恒大悦府就行了。',
|
|
|
1004
|
+ }, {
|
|
|
1005
|
+ direction: 1,
|
|
|
1006
|
+ timestamp: '14:30:24',
|
|
|
1007
|
+ page_content: '那个那个零六就你一个恒大悦府必须哪个路段,',
|
|
|
1008
|
+ }, {
|
|
|
1009
|
+ direction: 2,
|
|
|
1010
|
+ timestamp: '14:30:24',
|
|
|
1011
|
+ page_content: '我们这边也得清楚,鲁班大鲁班大道开元街道,',
|
|
|
1012
|
+ }, {
|
|
|
1013
|
+ direction: 1,
|
|
|
1014
|
+ timestamp: '14:30:24',
|
|
|
1015
|
+ page_content: '恒大悦府是吗?',
|
|
|
1016
|
+ }, {
|
|
|
1017
|
+ direction: 2,
|
|
|
1018
|
+ timestamp: '14:30:24',
|
|
|
1019
|
+ page_content: '哦,对,康阳街道的嗯,',
|
|
|
1020
|
+ }, {
|
|
|
1021
|
+ direction: 1,
|
|
|
1022
|
+ timestamp: '14:30:24',
|
|
|
1023
|
+ page_content: '行好的,这边给您登记一下啊。',
|
|
|
1024
|
+ }, {
|
|
|
1025
|
+ direction: 2,
|
|
|
1026
|
+ timestamp: '14:30:24',
|
|
|
1027
|
+ page_content: '嗯,好了,不歉。',
|
|
|
1028
|
+ }, {
|
|
|
1029
|
+ direction: 1,
|
|
|
1030
|
+ timestamp: '14:30:24',
|
|
|
1031
|
+ page_content: '喂,哎,可以了。',
|
|
|
1032
|
+ }, {
|
|
|
1033
|
+ direction: 2,
|
|
|
1034
|
+ timestamp: '14:30:24',
|
|
|
1035
|
+ page_content: '呃',
|
|
|
1036
|
+ }, {
|
|
|
1037
|
+ direction: 1,
|
|
|
1038
|
+ timestamp: '14:30:24',
|
|
|
1039
|
+ page_content: '刚才我给你他们啊嗯已经给您登记了,稍后反应呃。',
|
|
|
1040
|
+ }, {
|
|
|
1041
|
+ direction: 2,
|
|
|
1042
|
+ timestamp: '14:30:24',
|
|
|
1043
|
+ page_content: '登登记了,你登记的,我是你知道我反映的什么吗?',
|
|
|
1044
|
+ }, {
|
|
|
1045
|
+ direction: 1,
|
|
|
1046
|
+ timestamp: '14:30:24',
|
|
|
1047
|
+ page_content: '你不是家中暖气没热吗?不热吗?',
|
|
|
1048
|
+ }, {
|
|
|
1049
|
+ direction: 2,
|
|
|
1050
|
+ timestamp: '14:30:24',
|
|
|
1051
|
+ page_content: '这不是不热,是没充暖,我给物业,我给物业联系了,物业,说是呃,他们这里公司就两个人,一共十七栋楼,他问他说这个还能等待几天一栋一栋的开呢,我们的我我们的供暖费都交了。',
|
|
|
1052
|
+ }, {
|
|
|
1053
|
+ direction: 2,
|
|
|
1054
|
+ page_content: '看他几天,你看他几天',
|
|
|
1055
|
+ }, {
|
|
|
1056
|
+ direction: 1,
|
|
|
1057
|
+ page_content: '你给我说个定是不是嗯就是人员少,担心无法正常供暖,是不是啊?',
|
|
|
1058
|
+ }, {
|
|
|
1059
|
+ direction: 2,
|
|
|
1060
|
+ page_content: '他说还得几天,你现在十五号都是显示统一功能。你到现在就你你说排了几天,还得几天的。',
|
|
|
1061
|
+ }, {
|
|
|
1062
|
+ direction: 1,
|
|
|
1063
|
+ page_content: '嗯,好的,这边已经有您同小弟业主反映过了,跟您跟他同甘办理啊。',
|
|
|
1064
|
+ }, {
|
|
|
1065
|
+ direction: 2,
|
|
|
1066
|
+ page_content: '嗯嗯,行好的。',
|
|
|
1067
|
+ }
|
|
|
1068
|
+])
|
|
|
1069
|
+// 关键词提示
|
|
|
1070
|
+const keywords = ref([
|
|
|
1071
|
+ {
|
|
|
1072
|
+ text: '产品使用方法',
|
|
|
1073
|
+ type: 'primary'
|
|
|
1074
|
+ },
|
|
|
1075
|
+ {
|
|
|
1076
|
+ text: '操作流程',
|
|
|
1077
|
+ type: 'success'
|
|
|
1078
|
+ },
|
|
|
1079
|
+ {
|
|
|
1080
|
+ text: '正面反馈',
|
|
|
1081
|
+ type: 'success'
|
|
|
1082
|
+ },
|
|
|
1083
|
+ {
|
|
|
1084
|
+ text: '满意度高',
|
|
|
1085
|
+ type: 'success'
|
|
|
1086
|
+ }
|
|
|
1087
|
+]);
|
|
|
1088
|
+// 建议话术
|
|
|
1089
|
+const suggestions = ref([
|
|
|
1090
|
+ '很高兴能帮到您,如果还有其他问题随时询问',
|
|
|
1091
|
+ '这个功能确实很实用,您可以根据实际需求来调整配置',
|
|
|
1092
|
+ '感谢您的正面反馈,我们会继续努力提供更好的服务',
|
|
|
1093
|
+ '建议您也可以查看我们的帮助文档,里面有更详细的使用说明'
|
|
|
1094
|
+]);
|
|
|
1095
|
+const transcriptContainer = ref<HTMLElement | null>(null);
|
|
|
1096
|
+// 监听新消息,自动滚动到底部
|
|
|
1097
|
+// 监听新消息和内容变化,自动滚动到底部
|
|
|
1098
|
+watch(
|
|
|
1099
|
+ [
|
|
|
1100
|
+ () => transcripts.value.length,
|
|
|
1101
|
+ () => transcripts.value.map(t => t.page_content)
|
|
|
1102
|
+ ],
|
|
|
1103
|
+ () => {
|
|
|
1104
|
+ nextTick(() => {
|
|
|
1105
|
+ console.log(transcriptContainer.value, 'transcriptContainer');
|
|
|
1106
|
+ if (transcriptContainer.value) {
|
|
|
1107
|
+ transcriptContainer.value.scrollTop = transcriptContainer.value.scrollHeight;
|
|
|
1108
|
+ }
|
|
|
1109
|
+ });
|
|
|
1110
|
+ },
|
|
|
1111
|
+ { deep: true }
|
|
|
1112
|
+);
|
|
|
1113
|
+
|
|
|
1114
|
+window.addEventListener('AsrMessageEvent', (msg: any) => {
|
|
|
1115
|
+ console.log(msg.detail, '接收asr消息');
|
|
|
1116
|
+ if (!msg?.detail || !msg.detail.Result) return;
|
|
|
1117
|
+ const msgInfo = {
|
|
|
1118
|
+ direction: msg.detail.Number.toString().length > 4 ? 2 : 1,
|
|
|
1119
|
+ page_content: msg.detail.Speech || '',
|
|
|
1120
|
+ timestamp: '',
|
|
|
1121
|
+ };
|
|
|
1122
|
+ console.log(msg.detail.Time, 'msgInfo');
|
|
|
1123
|
+ if (msg.detail.Time) {
|
|
|
1124
|
+ const times = JSON.parse(msg.detail.Time);
|
|
|
1125
|
+
|
|
|
1126
|
+ console.log(times, 'msgInfo');
|
|
|
1127
|
+ if (times?.length) {
|
|
|
1128
|
+ const time = flatten(times)?.[0];
|
|
|
1129
|
+ if (time) msgInfo.timestamp = formatMilliseconds(time);
|
|
|
1130
|
+ }
|
|
|
1131
|
+ }
|
|
|
1132
|
+
|
|
|
1133
|
+ console.log(msgInfo, 'msgInfo');
|
|
|
1134
|
+
|
|
|
1135
|
+ transcripts.value.push(msgInfo);
|
|
|
1136
|
+})
|
|
|
1137
|
+
|
|
|
1138
|
+function formatMilliseconds(milliseconds) {
|
|
|
1139
|
+ const totalSeconds = Math.floor(milliseconds / 1000);
|
|
|
1140
|
+ const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
|
|
|
1141
|
+ const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
|
|
|
1142
|
+ const seconds = (totalSeconds % 60).toString().padStart(2, '0');
|
|
|
1143
|
+ return `${minutes}:${seconds}`;
|
|
|
1144
|
+}
|
|
|
1145
|
+
|
|
|
1146
|
+const aiSubmit = async () => {
|
|
|
1147
|
+ aiLoading.value = true;
|
|
|
1148
|
+ let text = transcripts.value.map((o) => o.page_content).join('');
|
|
|
1149
|
+ if (text?.length > 10) {
|
|
|
1150
|
+ const tx = `请从以下咨询对话中提取关键信息生成结构化工单:
|
|
|
1151
|
+ 咨询对话内容:${text}
|
|
|
1152
|
+地理位置提取:识别来访者明确提到的居住地或当前位置
|
|
|
1153
|
+格式:[省/市/区/街道/楼栋号/房间号](如未提及则标记"未知")
|
|
|
1154
|
+注意模糊表述(如"北方城市""江浙地区"等需保留原话)
|
|
|
1155
|
+通话内容总结:
|
|
|
1156
|
+用第三人称概括核心问题(100字内)
|
|
|
1157
|
+保留以下关键要素:
|
|
|
1158
|
+• 主要症状描述(情绪/躯体/行为表现)
|
|
|
1159
|
+• 持续时间(使用"约X周/月/年"格式)
|
|
|
1160
|
+• 社会功能影响(工作/学习/人际关系)
|
|
|
1161
|
+• 既往病史(如提及)
|
|
|
1162
|
+过滤无关对话(寒暄、重复表述等)
|
|
|
1163
|
+工单类型分类:
|
|
|
1164
|
+根据DSM-5标准匹配最相关分类(单选):
|
|
|
1165
|
+[抑郁障碍] [焦虑障碍] [强迫症] [创伤应激] [人格障碍] [适应障碍] [人际关系] [发展性问题] [其他]
|
|
|
1166
|
+【处理规范】
|
|
|
1167
|
+优先识别直接症状陈述(如"失眠三个月""不敢见人")
|
|
|
1168
|
+注意隐喻表达(如"心里压着石头""像被困住")
|
|
|
1169
|
+存在自伤/伤人表述时自动触发危机预警协议
|
|
|
1170
|
+多问题并存时按主诉优先级排序
|
|
|
1171
|
+示例对话:
|
|
|
1172
|
+[咨询师]:可以说说最近困扰您的事吗?
|
|
|
1173
|
+[来访者]:我在杭州工作两年了,最近三个月每天失眠,开会时手抖得厉害,上周在地铁里突然喘不过气...
|
|
|
1174
|
+
|
|
|
1175
|
+示例输出:
|
|
|
1176
|
+【地理位置】:浙江省杭州市
|
|
|
1177
|
+【内容总结】:来访者诉持续三个月失眠症状,伴有社交场合手抖等躯体化表现,提及近期出现惊恐发作经历(地铁喘不过气),社会功能受损(工作受影响)
|
|
|
1178
|
+【工单类型】: 焦虑障碍
|
|
|
1179
|
+`;
|
|
|
1180
|
+ const res = await getSearchDocs(tx);
|
|
|
1181
|
+ console.log(res, 'res');
|
|
|
1182
|
+
|
|
|
1183
|
+ res.split('\n').forEach((o) => {
|
|
|
1184
|
+ if (o.startsWith('【内容总结】')) form.value.description = o.replace('【内容总结】', '').replace(':', '');
|
|
|
1185
|
+ // if (o.startsWith('【事件地址】')) form.value.address = o.replace('【事件地址】', '').replace(':', '');
|
|
|
1186
|
+ // if (o.startsWith('【姓名】')) form.value.customerName = o.replace('【姓名】', '').replace(':', '');
|
|
|
1187
|
+
|
|
|
1188
|
+ })
|
|
|
1189
|
+
|
|
|
1190
|
+ aiLoading.value = false;
|
|
|
1191
|
+
|
|
|
1192
|
+ } else {
|
|
|
1193
|
+ aiLoading.value = false;
|
|
|
1194
|
+ }
|
|
|
1195
|
+}
|
|
|
1196
|
+async function getSearchDocs (text) {
|
|
|
1197
|
+ const params = {
|
|
|
1198
|
+ query: text,
|
|
|
1199
|
+ mode: "local_kb",
|
|
|
1200
|
+ kb_name: "mszsk",
|
|
|
1201
|
+ top_k: 1,
|
|
|
1202
|
+ score_threshold: 0.5,
|
|
|
1203
|
+ stream: true,
|
|
|
1204
|
+ model: "glm-4",
|
|
|
1205
|
+ temperature: 0.7,
|
|
|
1206
|
+ max_tokens: 0,
|
|
|
1207
|
+ prompt_name: "default",
|
|
|
1208
|
+ return_direct: false
|
|
|
1209
|
+ }
|
|
|
1210
|
+ try {
|
|
|
1211
|
+ // 发送请求
|
|
|
1212
|
+ let response = await fetch("http://192.168.1.89:7861/chat/chat",
|
|
|
1213
|
+ {
|
|
|
1214
|
+ method: "post",
|
|
|
1215
|
+ // responseType: "stream",
|
|
|
1216
|
+ headers: {
|
|
|
1217
|
+ "Content-Type": "application/json",
|
|
|
1218
|
+ },
|
|
|
1219
|
+ body: JSON.stringify(params),
|
|
|
1220
|
+ }
|
|
|
1221
|
+ );
|
|
|
1222
|
+
|
|
|
1223
|
+ let resultStr = '';
|
|
|
1224
|
+
|
|
|
1225
|
+ // ok字段判断是否成功获取到数据流
|
|
|
1226
|
+ if (!response.ok) {
|
|
|
1227
|
+ throw new Error("Network response was not ok");
|
|
|
1228
|
+ }
|
|
|
1229
|
+ // 用来获取一个可读的流的读取器(Reader)以流的方式处理响应体数据
|
|
|
1230
|
+ const reader = response.body.getReader();
|
|
|
1231
|
+ // 将流中的字节数据解码为文本字符串
|
|
|
1232
|
+ const textDecoder = new TextDecoder();
|
|
|
1233
|
+ let result = true;
|
|
|
1234
|
+ let sqlValue = ''
|
|
|
1235
|
+
|
|
|
1236
|
+ while (result) {
|
|
|
1237
|
+ // done表示流是否已经完成读取 value包含读取到的数据块
|
|
|
1238
|
+ const { done, value } = await reader.read();
|
|
|
1239
|
+ if (done) {
|
|
|
1240
|
+ result = false;
|
|
|
1241
|
+ // console.log(resultStr, 'resultStr');
|
|
|
1242
|
+ return resultStr;
|
|
|
1243
|
+ break;
|
|
|
1244
|
+ }
|
|
|
1245
|
+ // 拿到的value就是后端分段返回的数据,大多是以data:开头的字符串
|
|
|
1246
|
+ // 需要通过decode方法处理数据块,例如转换为文本或进行其他操作
|
|
|
1247
|
+ const chunkText = textDecoder.decode(value).split("\n").forEach((val) => {
|
|
|
1248
|
+ if (!val) return;
|
|
|
1249
|
+ try {
|
|
|
1250
|
+ // 后端返回的流式数据一般都是以data:开头的字符,排除掉data:后就是需要的数据
|
|
|
1251
|
+ // 具体返回结构可以跟后端约定
|
|
|
1252
|
+ let text = val?.replace("data:", "") || ""
|
|
|
1253
|
+ const resultData = JSON.parse(text)
|
|
|
1254
|
+ let resultText = resultData.choices[0].delta.content
|
|
|
1255
|
+
|
|
|
1256
|
+ resultStr += resultText
|
|
|
1257
|
+ } catch (err) {
|
|
|
1258
|
+ // console.log(err)
|
|
|
1259
|
+ }
|
|
|
1260
|
+ });
|
|
|
1261
|
+ }
|
|
|
1262
|
+
|
|
|
1263
|
+ } catch (err) {
|
|
|
1264
|
+ console.log(err)
|
|
|
1265
|
+
|
|
|
1266
|
+ return err;
|
|
|
1267
|
+ }
|
|
712
|
1268
|
|
|
713
|
1269
|
}
|
|
714
|
1270
|
|