@fecp/designer 5.6.18 → 5.6.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/designer/package.json.mjs +1 -1
- package/es/designer.css +141 -63
- package/es/node_modules/@vant/use/dist/index.esm.mjs +1 -1
- package/es/node_modules/d3-sankey/node_modules/d3-path/src/path.mjs +73 -0
- package/es/node_modules/d3-sankey/node_modules/d3-shape/src/array.mjs +4 -0
- package/es/node_modules/d3-sankey/node_modules/d3-shape/src/constant.mjs +8 -0
- package/es/node_modules/d3-sankey/node_modules/d3-shape/src/link/index.mjs +45 -0
- package/es/node_modules/d3-sankey/node_modules/d3-shape/src/point.mjs +10 -0
- package/es/node_modules/d3-sankey/src/sankeyLinkHorizontal.mjs +1 -1
- package/es/node_modules/vant/es/toast/function-call.mjs +1 -1
- package/es/packages/mobile/src/assets/images/ai2.png.mjs +4 -0
- package/es/packages/mobile/src/components/ai/AiChat/AiChat.vue.mjs +155 -23
- package/es/packages/mobile/src/components/ai/ChatInput/ChatInput.vue.mjs +4 -6
- package/es/packages/mobile/src/components/ai/ChatPanel/ChatPanel.vue.mjs +71 -11
- package/es/packages/mobile/src/components/ai/MarkdownRenderer/MarkdownRenderer.vue.mjs +37 -3
- package/es/packages/mobile/src/components/custom/appDetail/AppDetail.vue.mjs +2 -2
- package/es/packages/mobile/src/components/form/uploader/Uploader.vue.mjs +1 -2
- package/es/packages/mobile/src/composables/useAI/adapters/h5.mjs +256 -0
- package/es/packages/mobile/src/composables/useAI/adapters/index.mjs +3 -3
- package/es/packages/mobile/src/composables/useAI/useChat.mjs +10 -3
- package/es/packages/mobile/src/composables/usePageEvents.mjs +1 -1
- package/es/packages/mobile/src/composables/useVoiceTouch.mjs +2 -0
- package/es/packages/mobile/src/index.vue.mjs +20 -8
- package/es/packages/mobile/src/utils/datasource.mjs +1 -1
- package/es/packages/vue/src/components/bus/approvalHistory/ApproveIdea.vue.mjs +2 -2
- package/es/packages/vue/src/components/dialog/DialogRenderer.vue2.mjs +0 -1
- package/es/packages/vue/src/components/forms/number/Number.vue.mjs +0 -1
- package/es/packages/vue/src/components/forms/text/Text.vue.mjs +0 -1
- package/es/packages/vue/src/components/table/TableColumn.vue.mjs +0 -1
- package/es/packages/vue/src/composables/usePageEvents.mjs +0 -1
- package/es/packages/vue/src/utils/datasource.mjs +0 -20
- package/lib/designer/package.json.js +1 -1
- package/lib/designer.css +141 -63
- package/lib/node_modules/d3-sankey/node_modules/d3-path/src/path.js +73 -0
- package/lib/node_modules/d3-sankey/node_modules/d3-shape/src/array.js +4 -0
- package/lib/node_modules/d3-sankey/node_modules/d3-shape/src/constant.js +8 -0
- package/lib/node_modules/d3-sankey/node_modules/d3-shape/src/link/index.js +45 -0
- package/lib/node_modules/d3-sankey/node_modules/d3-shape/src/point.js +10 -0
- package/lib/node_modules/d3-sankey/src/sankeyLinkHorizontal.js +1 -1
- package/lib/packages/mobile/src/assets/images/ai2.png.js +4 -0
- package/lib/packages/mobile/src/components/ai/AiChat/AiChat.vue.js +154 -22
- package/lib/packages/mobile/src/components/ai/ChatInput/ChatInput.vue.js +4 -6
- package/lib/packages/mobile/src/components/ai/ChatPanel/ChatPanel.vue.js +73 -13
- package/lib/packages/mobile/src/components/ai/MarkdownRenderer/MarkdownRenderer.vue.js +37 -3
- package/lib/packages/mobile/src/components/custom/appDetail/AppDetail.vue.js +2 -2
- package/lib/packages/mobile/src/components/form/uploader/Uploader.vue.js +1 -2
- package/lib/packages/mobile/src/composables/useAI/adapters/h5.js +256 -0
- package/lib/packages/mobile/src/composables/useAI/adapters/index.js +3 -3
- package/lib/packages/mobile/src/composables/useAI/useChat.js +10 -3
- package/lib/packages/mobile/src/composables/usePageEvents.js +1 -1
- package/lib/packages/mobile/src/composables/useVoiceTouch.js +2 -0
- package/lib/packages/mobile/src/index.vue.js +19 -7
- package/lib/packages/mobile/src/utils/datasource.js +1 -1
- package/lib/packages/vue/src/components/bus/approvalHistory/ApproveIdea.vue.js +2 -2
- package/lib/packages/vue/src/components/dialog/DialogRenderer.vue2.js +0 -1
- package/lib/packages/vue/src/components/forms/number/Number.vue.js +0 -1
- package/lib/packages/vue/src/components/forms/text/Text.vue.js +0 -1
- package/lib/packages/vue/src/components/table/TableColumn.vue.js +0 -1
- package/lib/packages/vue/src/composables/usePageEvents.js +0 -1
- package/lib/packages/vue/src/utils/datasource.js +2 -22
- package/package.json +1 -1
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
;/* empty css */
|
|
4
|
+
;/* empty css */
|
|
4
5
|
;/* empty css */
|
|
5
6
|
;/* empty css */
|
|
6
|
-
;/* empty css */
|
|
7
7
|
;/* empty css */
|
|
8
8
|
const Vue = require("vue");
|
|
9
9
|
const index$1 = require("../MarkdownRenderer/index.js");
|
|
10
10
|
;/* empty css */
|
|
11
11
|
const _pluginVue_exportHelper = require("../../../../../../_virtual/_plugin-vue_export-helper.js");
|
|
12
12
|
const index = require("../../../../../../node_modules/vant/es/empty/index.js");
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const functionCall = require("../../../../../../node_modules/vant/es/toast/function-call.js");
|
|
14
|
+
require("../../../../../../node_modules/vant/es/toast/index.js");
|
|
15
|
+
const index$2 = require("../../../../../../node_modules/vant/es/icon/index.js");
|
|
16
|
+
const index$3 = require("../../../../../../node_modules/vant/es/loading/index.js");
|
|
15
17
|
const _hoisted_1 = {
|
|
16
18
|
key: 0,
|
|
17
19
|
class: "chat-panel__empty"
|
|
@@ -39,13 +41,23 @@ const _hoisted_7 = {
|
|
|
39
41
|
};
|
|
40
42
|
const _hoisted_8 = {
|
|
41
43
|
key: 2,
|
|
44
|
+
class: "chat-panel__actions"
|
|
45
|
+
};
|
|
46
|
+
const _hoisted_9 = ["onClick"];
|
|
47
|
+
const _hoisted_10 = ["onClick"];
|
|
48
|
+
const _hoisted_11 = {
|
|
49
|
+
key: 1,
|
|
50
|
+
class: "iconfont icon-mobileyinliang"
|
|
51
|
+
};
|
|
52
|
+
const _hoisted_12 = {
|
|
53
|
+
key: 3,
|
|
42
54
|
class: "chat-panel__status"
|
|
43
55
|
};
|
|
44
|
-
const
|
|
56
|
+
const _hoisted_13 = {
|
|
45
57
|
key: 1,
|
|
46
58
|
class: "chat-panel__streaming-dot"
|
|
47
59
|
};
|
|
48
|
-
const
|
|
60
|
+
const _hoisted_14 = ["onClick"];
|
|
49
61
|
const _sfc_main = {
|
|
50
62
|
__name: "ChatPanel",
|
|
51
63
|
props: {
|
|
@@ -56,9 +68,11 @@ const _sfc_main = {
|
|
|
56
68
|
/** 是否自动滚动到底部 */
|
|
57
69
|
autoScroll: { type: Boolean, default: true },
|
|
58
70
|
/** 两条消息间隔多久显示时间戳(ms),默认 5 分钟 */
|
|
59
|
-
timeGap: { type: Number, default: 5 * 60 * 1e3 }
|
|
71
|
+
timeGap: { type: Number, default: 5 * 60 * 1e3 },
|
|
72
|
+
/** 正在朗读的消息 id(用于高亮"停止"态) */
|
|
73
|
+
speakingId: { type: String, default: "" }
|
|
60
74
|
},
|
|
61
|
-
emits: ["retry", "scroll-to-bottom", "navigate"],
|
|
75
|
+
emits: ["retry", "scroll-to-bottom", "navigate", "speak"],
|
|
62
76
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
63
77
|
const props = __props;
|
|
64
78
|
const emit = __emit;
|
|
@@ -81,6 +95,34 @@ const _sfc_main = {
|
|
|
81
95
|
const D = d.getDate();
|
|
82
96
|
return `${M}/${D} ${hh}:${mm}`;
|
|
83
97
|
}
|
|
98
|
+
async function onCopy(msg) {
|
|
99
|
+
var _a;
|
|
100
|
+
try {
|
|
101
|
+
const text = msg.content || "";
|
|
102
|
+
if ((_a = navigator.clipboard) == null ? void 0 : _a.writeText) {
|
|
103
|
+
await navigator.clipboard.writeText(text);
|
|
104
|
+
} else {
|
|
105
|
+
const ta = document.createElement("textarea");
|
|
106
|
+
ta.value = text;
|
|
107
|
+
ta.style.position = "fixed";
|
|
108
|
+
ta.style.opacity = "0";
|
|
109
|
+
document.body.appendChild(ta);
|
|
110
|
+
ta.select();
|
|
111
|
+
document.execCommand("copy");
|
|
112
|
+
document.body.removeChild(ta);
|
|
113
|
+
}
|
|
114
|
+
msg.__copied = true;
|
|
115
|
+
functionCall.showToast("已复制");
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
msg.__copied = false;
|
|
118
|
+
}, 1500);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
functionCall.showToast("复制失败");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function onSpeak(msg) {
|
|
124
|
+
emit("speak", msg);
|
|
125
|
+
}
|
|
84
126
|
function scrollToBottom() {
|
|
85
127
|
if (!panelRef.value) return;
|
|
86
128
|
const el = panelRef.value;
|
|
@@ -137,8 +179,8 @@ const _sfc_main = {
|
|
|
137
179
|
__expose({ scrollToBottom });
|
|
138
180
|
return (_ctx, _cache) => {
|
|
139
181
|
const _component_van_empty = index.Empty;
|
|
140
|
-
const
|
|
141
|
-
const
|
|
182
|
+
const _component_van_icon = index$2.Icon;
|
|
183
|
+
const _component_van_loading = index$3.Loading;
|
|
142
184
|
return Vue.openBlock(), Vue.createElementBlock("div", {
|
|
143
185
|
class: "chat-panel",
|
|
144
186
|
ref_key: "panelRef",
|
|
@@ -185,11 +227,29 @@ const _sfc_main = {
|
|
|
185
227
|
onNavigate: _cache[0] || (_cache[0] = ($event) => emit("navigate", $event))
|
|
186
228
|
}, null, 8, ["content", "is-streaming"])
|
|
187
229
|
])) : (Vue.openBlock(), Vue.createElementBlock("div", _hoisted_7, Vue.toDisplayString(msg.content), 1)),
|
|
188
|
-
|
|
230
|
+
msg.role === "assistant" && msg.status === "done" && msg.content && !msg.isWelcome ? (Vue.openBlock(), Vue.createElementBlock("div", _hoisted_8, [
|
|
231
|
+
Vue.createElementVNode("span", {
|
|
232
|
+
class: "chat-panel__action",
|
|
233
|
+
onClick: ($event) => onCopy(msg)
|
|
234
|
+
}, _cache[3] || (_cache[3] = [
|
|
235
|
+
Vue.createElementVNode("i", { class: "iconfont icon-mobilefuzhi" }, null, -1)
|
|
236
|
+
]), 8, _hoisted_9),
|
|
237
|
+
Vue.createElementVNode("span", {
|
|
238
|
+
class: Vue.normalizeClass(["chat-panel__action", { "chat-panel__action--active": __props.speakingId === msg.id }]),
|
|
239
|
+
onClick: ($event) => onSpeak(msg)
|
|
240
|
+
}, [
|
|
241
|
+
__props.speakingId === msg.id ? (Vue.openBlock(), Vue.createBlock(_component_van_icon, {
|
|
242
|
+
key: 0,
|
|
243
|
+
name: "pause",
|
|
244
|
+
size: "14"
|
|
245
|
+
})) : (Vue.openBlock(), Vue.createElementBlock("i", _hoisted_11))
|
|
246
|
+
], 10, _hoisted_10)
|
|
247
|
+
])) : Vue.createCommentVNode("", true),
|
|
248
|
+
["sending", "streaming", "error"].includes(msg.status) ? (Vue.openBlock(), Vue.createElementBlock("div", _hoisted_12, [
|
|
189
249
|
msg.status === "sending" ? (Vue.openBlock(), Vue.createBlock(_component_van_loading, {
|
|
190
250
|
key: 0,
|
|
191
251
|
size: "14"
|
|
192
|
-
})) : msg.status === "streaming" ? (Vue.openBlock(), Vue.createElementBlock("span",
|
|
252
|
+
})) : msg.status === "streaming" ? (Vue.openBlock(), Vue.createElementBlock("span", _hoisted_13, _cache[4] || (_cache[4] = [
|
|
193
253
|
Vue.createElementVNode("i", null, null, -1),
|
|
194
254
|
Vue.createElementVNode("i", null, null, -1),
|
|
195
255
|
Vue.createElementVNode("i", null, null, -1)
|
|
@@ -204,7 +264,7 @@ const _sfc_main = {
|
|
|
204
264
|
size: "14"
|
|
205
265
|
}),
|
|
206
266
|
Vue.createTextVNode(" " + Vue.toDisplayString(msg.error || "发送失败"), 1)
|
|
207
|
-
], 8,
|
|
267
|
+
], 8, _hoisted_14)) : Vue.createCommentVNode("", true)
|
|
208
268
|
])) : Vue.createCommentVNode("", true)
|
|
209
269
|
], 2))
|
|
210
270
|
], 2);
|
|
@@ -228,5 +288,5 @@ const _sfc_main = {
|
|
|
228
288
|
};
|
|
229
289
|
}
|
|
230
290
|
};
|
|
231
|
-
const _ChatPanel = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-
|
|
291
|
+
const _ChatPanel = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-c2dc87cf"]]);
|
|
232
292
|
exports.default = _ChatPanel;
|
|
@@ -3,6 +3,7 @@ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toString
|
|
|
3
3
|
const Vue = require("vue");
|
|
4
4
|
const index = require("../../../../../../node_modules/markdown-it/lib/index.js");
|
|
5
5
|
const index$1 = require("../../../../../../_virtual/index.js");
|
|
6
|
+
const purify_es = require("../../../../../../node_modules/dompurify/dist/purify.es.js");
|
|
6
7
|
;/* empty css */
|
|
7
8
|
;/* empty css */
|
|
8
9
|
const _pluginVue_exportHelper = require("../../../../../../_virtual/_plugin-vue_export-helper.js");
|
|
@@ -20,7 +21,7 @@ const _sfc_main = {
|
|
|
20
21
|
const props = __props;
|
|
21
22
|
const md = new index.default({
|
|
22
23
|
html: false,
|
|
23
|
-
// 安全:不允许原始 HTML
|
|
24
|
+
// 安全:不允许原始 HTML(标题样式在渲染后字符串层面转换,见 enhanceTitles)
|
|
24
25
|
linkify: true,
|
|
25
26
|
// 自动将 URL 转为链接
|
|
26
27
|
breaks: true,
|
|
@@ -45,16 +46,40 @@ const _sfc_main = {
|
|
|
45
46
|
});
|
|
46
47
|
const emit = __emit;
|
|
47
48
|
const contentRef = Vue.ref(null);
|
|
49
|
+
let _purifyHookInstalled = false;
|
|
50
|
+
function installPurifyHook() {
|
|
51
|
+
if (_purifyHookInstalled) return;
|
|
52
|
+
_purifyHookInstalled = true;
|
|
53
|
+
purify_es.default.addHook("uponSanitizeAttribute", (node, data) => {
|
|
54
|
+
if (data && data.attrName === "href" && data.attrValue && data.attrValue.startsWith("navigateToPage:")) {
|
|
55
|
+
data.keepAttr = true;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
installPurifyHook();
|
|
48
60
|
const renderedHtml = Vue.computed(() => {
|
|
49
61
|
if (!props.content) return "";
|
|
50
62
|
try {
|
|
51
|
-
|
|
63
|
+
const html = md.render(props.content);
|
|
64
|
+
return purify_es.default.sanitize(html, {
|
|
65
|
+
// 保留 navigateToPage: 自定义跳转协议链接(markdown 生成的跳转链接不受影响)
|
|
66
|
+
ALLOWED_URI_REGEXP: /^(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|navigateToPage):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$)/i
|
|
67
|
+
});
|
|
52
68
|
} catch (e) {
|
|
53
69
|
console.warn("[MarkdownRenderer] 渲染失败:", e.message);
|
|
54
70
|
return md.utils.escapeHtml(props.content);
|
|
55
71
|
}
|
|
56
72
|
});
|
|
57
73
|
function onClick(e) {
|
|
74
|
+
const jumpBlock = e.target.closest("blockquote.jump-block");
|
|
75
|
+
if (jumpBlock) {
|
|
76
|
+
const link2 = jumpBlock.querySelector('a[href^="navigateToPage:"]');
|
|
77
|
+
if (link2) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
emit("navigate", link2.getAttribute("href"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
58
83
|
const link = e.target.closest("a");
|
|
59
84
|
if (!link) return;
|
|
60
85
|
const href = link.getAttribute("href");
|
|
@@ -62,6 +87,14 @@ const _sfc_main = {
|
|
|
62
87
|
e.preventDefault();
|
|
63
88
|
emit("navigate", href);
|
|
64
89
|
}
|
|
90
|
+
function markJumpBlocks(el) {
|
|
91
|
+
if (!el) return;
|
|
92
|
+
el.querySelectorAll("blockquote").forEach((bq) => {
|
|
93
|
+
if (bq.querySelector('a[href^="navigateToPage:"]')) {
|
|
94
|
+
bq.classList.add("jump-block");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
65
98
|
let _mermaidInstance = null;
|
|
66
99
|
let _mermaidLoaded = false;
|
|
67
100
|
async function ensureMermaid() {
|
|
@@ -110,6 +143,7 @@ const _sfc_main = {
|
|
|
110
143
|
() => {
|
|
111
144
|
Vue.nextTick(() => {
|
|
112
145
|
renderMermaidBlocks(contentRef.value);
|
|
146
|
+
markJumpBlocks(contentRef.value);
|
|
113
147
|
});
|
|
114
148
|
},
|
|
115
149
|
{ immediate: true }
|
|
@@ -125,5 +159,5 @@ const _sfc_main = {
|
|
|
125
159
|
};
|
|
126
160
|
}
|
|
127
161
|
};
|
|
128
|
-
const _MarkdownRenderer = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-
|
|
162
|
+
const _MarkdownRenderer = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-18a7f752"]]);
|
|
129
163
|
exports.default = _MarkdownRenderer;
|
|
@@ -93,7 +93,7 @@ const _sfc_main = {
|
|
|
93
93
|
}
|
|
94
94
|
operationPageId.value = data.mobileAppPage;
|
|
95
95
|
canMobileApproval.value = data.mobileApproval != "1";
|
|
96
|
-
opinionTypeOptions.value = data.approvalBtn.filter(
|
|
96
|
+
opinionTypeOptions.value = (data.approvalBtn || []).filter(
|
|
97
97
|
(item) => item.approveType != "C1"
|
|
98
98
|
);
|
|
99
99
|
expandingTabOptions.value = JSON.parse(data.mobileDetailTabs || "[]").map(
|
|
@@ -187,5 +187,5 @@ const _sfc_main = {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
};
|
|
190
|
-
const _AppDetail = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-
|
|
190
|
+
const _AppDetail = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-87ab39f1"]]);
|
|
191
191
|
exports.default = _AppDetail;
|
|
@@ -16,9 +16,8 @@ const dataSourceUtil = require("../../../utils/dataSourceUtil.js");
|
|
|
16
16
|
;/* empty css */
|
|
17
17
|
const _pluginVue_exportHelper = require("../../../../../../_virtual/_plugin-vue_export-helper.js");
|
|
18
18
|
const index = require("../../../../../../node_modules/vant/es/uploader/index.js");
|
|
19
|
-
const functionCall = require("../../../../../../node_modules/vant/es/toast/function-call.js");
|
|
20
|
-
require("../../../../../../node_modules/vant/es/toast/index.js");
|
|
21
19
|
const index$1 = require("../../../../../../node_modules/vant/es/icon/index.js");
|
|
20
|
+
const functionCall = require("../../../../../../node_modules/vant/es/toast/function-call.js");
|
|
22
21
|
const _hoisted_1 = {
|
|
23
22
|
key: 0,
|
|
24
23
|
class: "preview-cover van-ellipsis"
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function checkGetUserMedia() {
|
|
4
|
+
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
|
|
5
|
+
}
|
|
6
|
+
function checkMediaRecorder() {
|
|
7
|
+
return typeof MediaRecorder !== "undefined";
|
|
8
|
+
}
|
|
9
|
+
function getSupportedMimeType() {
|
|
10
|
+
const types = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg;codecs=opus"];
|
|
11
|
+
for (const type of types) {
|
|
12
|
+
if (MediaRecorder.isTypeSupported(type)) {
|
|
13
|
+
return type;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function createH5Adapter(sessionId, ctx) {
|
|
19
|
+
function createStreamRequest(options) {
|
|
20
|
+
const {
|
|
21
|
+
url,
|
|
22
|
+
method = "POST",
|
|
23
|
+
headers = {},
|
|
24
|
+
body = null,
|
|
25
|
+
onMessage,
|
|
26
|
+
onComplete,
|
|
27
|
+
onError,
|
|
28
|
+
timeout = 6e4
|
|
29
|
+
} = options;
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
controller.abort();
|
|
33
|
+
onError(new Error("请求超时"));
|
|
34
|
+
}, timeout);
|
|
35
|
+
(async () => {
|
|
36
|
+
var _a, _b, _c;
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(url, {
|
|
39
|
+
method,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
...headers
|
|
43
|
+
},
|
|
44
|
+
body: body ? JSON.stringify(body) : null,
|
|
45
|
+
signal: controller.signal
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
49
|
+
}
|
|
50
|
+
const reader = res.body.getReader();
|
|
51
|
+
const decoder = new TextDecoder("utf-8");
|
|
52
|
+
let buffer = "";
|
|
53
|
+
while (true) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done) break;
|
|
56
|
+
buffer += decoder.decode(value, { stream: true });
|
|
57
|
+
const lines = buffer.split("\n");
|
|
58
|
+
buffer = lines.pop() || "";
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
const trimmed = line.trim();
|
|
61
|
+
if (!trimmed || !trimmed.startsWith("data:")) continue;
|
|
62
|
+
const data = trimmed.slice(5).trim();
|
|
63
|
+
if (data === "[DONE]") {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
onComplete();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(data);
|
|
70
|
+
const chunk = ((_c = (_b = (_a = parsed.choices) == null ? void 0 : _a[0]) == null ? void 0 : _b.delta) == null ? void 0 : _c.content) || parsed.content || parsed.text || "";
|
|
71
|
+
if (chunk) onMessage(chunk);
|
|
72
|
+
} catch {
|
|
73
|
+
onMessage(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
onComplete();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
if (err.name !== "AbortError") {
|
|
82
|
+
onError(err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
return {
|
|
87
|
+
abort: () => {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
controller.abort();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
let mediaStream = null;
|
|
94
|
+
let mediaRecorder = null;
|
|
95
|
+
let recordedChunks = [];
|
|
96
|
+
let isRecording = false;
|
|
97
|
+
let recordStartTime = 0;
|
|
98
|
+
let maxDurationTimer = null;
|
|
99
|
+
async function startRecord(options = {}) {
|
|
100
|
+
if (!checkGetUserMedia()) {
|
|
101
|
+
throw new Error("当前浏览器不支持麦克风录音,请使用 HTTPS 或 localhost 访问");
|
|
102
|
+
}
|
|
103
|
+
if (!checkMediaRecorder()) {
|
|
104
|
+
throw new Error("当前浏览器不支持 MediaRecorder,请升级浏览器或使用 Chrome/Edge");
|
|
105
|
+
}
|
|
106
|
+
if (isRecording) {
|
|
107
|
+
throw new Error("已在录制中");
|
|
108
|
+
}
|
|
109
|
+
const { maxDuration = 6e4 } = options;
|
|
110
|
+
try {
|
|
111
|
+
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
112
|
+
const mimeType = getSupportedMimeType();
|
|
113
|
+
const recorderOptions = mimeType ? { mimeType } : {};
|
|
114
|
+
mediaRecorder = new MediaRecorder(mediaStream, recorderOptions);
|
|
115
|
+
recordedChunks = [];
|
|
116
|
+
mediaRecorder.ondataavailable = (event) => {
|
|
117
|
+
if (event.data && event.data.size > 0) {
|
|
118
|
+
recordedChunks.push(event.data);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
mediaRecorder.onerror = (event) => {
|
|
122
|
+
console.error("[H5Adapter] MediaRecorder 错误:", event.error);
|
|
123
|
+
cleanupRecording();
|
|
124
|
+
};
|
|
125
|
+
mediaRecorder.start();
|
|
126
|
+
isRecording = true;
|
|
127
|
+
recordStartTime = Date.now();
|
|
128
|
+
if (maxDuration > 0) {
|
|
129
|
+
maxDurationTimer = setTimeout(() => {
|
|
130
|
+
if (isRecording) {
|
|
131
|
+
stopRecord().catch(() => {
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}, maxDuration);
|
|
135
|
+
}
|
|
136
|
+
console.log("[H5Adapter] 开始录音");
|
|
137
|
+
} catch (err) {
|
|
138
|
+
cleanupRecording();
|
|
139
|
+
if (err.name === "NotAllowedError") {
|
|
140
|
+
throw new Error("麦克风权限被拒绝,请在浏览器设置中允许访问麦克风");
|
|
141
|
+
}
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function stopRecord() {
|
|
146
|
+
if (!isRecording || !mediaRecorder) {
|
|
147
|
+
throw new Error("未在录制中");
|
|
148
|
+
}
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
mediaRecorder.onstop = () => {
|
|
151
|
+
const duration = Date.now() - recordStartTime;
|
|
152
|
+
const mimeType = mediaRecorder.mimeType || "audio/webm";
|
|
153
|
+
mimeType.includes("mp4") ? "mp4" : "webm";
|
|
154
|
+
const audioBlob = new Blob(recordedChunks, { type: mimeType });
|
|
155
|
+
cleanupRecording();
|
|
156
|
+
console.log(`[H5Adapter] 录音结束,时长 ${duration}ms,大小 ${(audioBlob.size / 1024).toFixed(1)}KB`);
|
|
157
|
+
resolve({ audioBlob, duration });
|
|
158
|
+
};
|
|
159
|
+
mediaRecorder.onerror = (event) => {
|
|
160
|
+
var _a;
|
|
161
|
+
cleanupRecording();
|
|
162
|
+
reject(new Error(`录制错误: ${((_a = event.error) == null ? void 0 : _a.message) || "未知错误"}`));
|
|
163
|
+
};
|
|
164
|
+
if (mediaRecorder.state === "recording") {
|
|
165
|
+
mediaRecorder.requestData();
|
|
166
|
+
}
|
|
167
|
+
mediaRecorder.stop();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function cancelRecord() {
|
|
171
|
+
if (mediaRecorder && mediaRecorder.state === "recording") {
|
|
172
|
+
mediaRecorder.onstop = null;
|
|
173
|
+
mediaRecorder.stop();
|
|
174
|
+
}
|
|
175
|
+
cleanupRecording();
|
|
176
|
+
console.log("[H5Adapter] 录音已取消");
|
|
177
|
+
}
|
|
178
|
+
function cleanupRecording() {
|
|
179
|
+
if (maxDurationTimer) {
|
|
180
|
+
clearTimeout(maxDurationTimer);
|
|
181
|
+
maxDurationTimer = null;
|
|
182
|
+
}
|
|
183
|
+
if (mediaStream) {
|
|
184
|
+
mediaStream.getTracks().forEach((track) => track.stop());
|
|
185
|
+
mediaStream = null;
|
|
186
|
+
}
|
|
187
|
+
mediaRecorder = null;
|
|
188
|
+
recordedChunks = [];
|
|
189
|
+
isRecording = false;
|
|
190
|
+
}
|
|
191
|
+
function getStream() {
|
|
192
|
+
return mediaStream;
|
|
193
|
+
}
|
|
194
|
+
async function voiceToText(audioBlob) {
|
|
195
|
+
if (!audioBlob || audioBlob.size === 0) {
|
|
196
|
+
throw new Error("音频数据为空");
|
|
197
|
+
}
|
|
198
|
+
if (!ctx.$http) {
|
|
199
|
+
throw new Error("H5 适配器需要传入 httpClient(如 ctx.$http)才能调用后端 ASR 接口");
|
|
200
|
+
}
|
|
201
|
+
const formData = new FormData();
|
|
202
|
+
const mimeType = audioBlob.type || "audio/webm";
|
|
203
|
+
const ext = mimeType.includes("mp4") ? "mp4" : "webm";
|
|
204
|
+
formData.append("file", audioBlob, `recording.${ext}`);
|
|
205
|
+
formData.append("sessionId", sessionId);
|
|
206
|
+
try {
|
|
207
|
+
const data = await ctx.$http.postForm(`${ctx.$envConfig.servers.ai}/speech/transcribe`, formData, false, true);
|
|
208
|
+
return data.text;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
throw new Error(`语音转文字请求失败: ${err.message || "未知错误"}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function getCapabilities() {
|
|
214
|
+
var _a, _b;
|
|
215
|
+
return {
|
|
216
|
+
supportStreamRequest: true,
|
|
217
|
+
supportVoiceRecord: checkGetUserMedia() && checkMediaRecorder(),
|
|
218
|
+
supportVoiceToText: !!((_b = (_a = ctx == null ? void 0 : ctx.$envConfig) == null ? void 0 : _a.servers) == null ? void 0 : _b.base),
|
|
219
|
+
supportFileUpload: true
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
let status = "idle";
|
|
223
|
+
function init() {
|
|
224
|
+
if (!checkGetUserMedia()) {
|
|
225
|
+
console.warn("[H5Adapter] getUserMedia 不可用 — 请使用 HTTPS 或 localhost");
|
|
226
|
+
}
|
|
227
|
+
if (!checkMediaRecorder()) {
|
|
228
|
+
console.warn("[H5Adapter] MediaRecorder 不可用 — 请升级浏览器");
|
|
229
|
+
}
|
|
230
|
+
status = "ready";
|
|
231
|
+
console.log("[H5Adapter] 初始化完成");
|
|
232
|
+
}
|
|
233
|
+
function destroy() {
|
|
234
|
+
if (isRecording) {
|
|
235
|
+
cancelRecord();
|
|
236
|
+
}
|
|
237
|
+
status = "idle";
|
|
238
|
+
console.log("[H5Adapter] 已销毁");
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
name: "h5",
|
|
242
|
+
get status() {
|
|
243
|
+
return status;
|
|
244
|
+
},
|
|
245
|
+
init,
|
|
246
|
+
destroy,
|
|
247
|
+
createStreamRequest,
|
|
248
|
+
startRecord,
|
|
249
|
+
stopRecord,
|
|
250
|
+
cancelRecord,
|
|
251
|
+
voiceToText,
|
|
252
|
+
getStream,
|
|
253
|
+
getCapabilities
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
exports.createH5Adapter = createH5Adapter;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const detector = require("./detector.js");
|
|
4
|
-
const
|
|
4
|
+
const h5 = require("./h5.js");
|
|
5
5
|
const wecom = require("./wecom.js");
|
|
6
6
|
const miniprogram = require("./miniprogram.js");
|
|
7
7
|
let cachedAdapter = null;
|
|
@@ -19,7 +19,7 @@ function getAdapter(options = {}) {
|
|
|
19
19
|
break;
|
|
20
20
|
case detector.PlatformType.WEB:
|
|
21
21
|
default:
|
|
22
|
-
cachedAdapter =
|
|
22
|
+
cachedAdapter = h5.createH5Adapter(options.sessionId, options.ctx);
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
25
25
|
return cachedAdapter;
|
|
@@ -27,7 +27,7 @@ function getAdapter(options = {}) {
|
|
|
27
27
|
exports.PlatformType = detector.PlatformType;
|
|
28
28
|
exports.detectPlatform = detector.detectPlatform;
|
|
29
29
|
exports.detectPlatformAsync = detector.detectPlatformAsync;
|
|
30
|
-
exports.
|
|
30
|
+
exports.createH5Adapter = h5.createH5Adapter;
|
|
31
31
|
exports.createWeComAdapter = wecom.createWeComAdapter;
|
|
32
32
|
exports.createMiniProgramAdapter = miniprogram.createMiniProgramAdapter;
|
|
33
33
|
exports.getAdapter = getAdapter;
|
|
@@ -118,10 +118,17 @@ function useChat(options = {}) {
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
function stop() {
|
|
121
|
-
if (streamController)
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
if (!streamController) return;
|
|
122
|
+
streamController.abort();
|
|
123
|
+
const lastMsg = messages.value[messages.value.length - 1];
|
|
124
|
+
const isEmptyReply = lastMsg && lastMsg.role === types.MessageRole.ASSISTANT && lastMsg.status === types.MessageStatus.STREAMING && !lastMsg.content.trim();
|
|
125
|
+
if (isEmptyReply) {
|
|
126
|
+
messages.value.pop();
|
|
127
|
+
isStreaming.value = false;
|
|
128
|
+
streamController = null;
|
|
129
|
+
return;
|
|
124
130
|
}
|
|
131
|
+
finishStream(types.MessageStatus.CANCELLED, "用户取消");
|
|
125
132
|
}
|
|
126
133
|
async function retry() {
|
|
127
134
|
const lastMsg = messages.value[messages.value.length - 1];
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const Vue = require("vue");
|
|
4
4
|
;/* empty css */
|
|
5
|
-
;/* empty css */
|
|
6
5
|
;/* empty css */
|
|
7
6
|
;/* empty css */
|
|
7
|
+
;/* empty css */
|
|
8
8
|
require("../utils/eventBus.js");
|
|
9
9
|
require("../utils/pageHistory.js");
|
|
10
10
|
;/* empty css */
|
|
@@ -26,6 +26,8 @@ function useVoiceTouch({ emit, voiceStatus, disabled }) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function onVoiceStart(e) {
|
|
29
|
+
window.__aiVoiceLog = window.__aiVoiceLog || [];
|
|
30
|
+
window.__aiVoiceLog.push(`[useVoiceTouch] onVoiceStart 触发 disabled=${disabled == null ? void 0 : disabled.value} _voiceActive=${_voiceActive}`);
|
|
29
31
|
if ((disabled == null ? void 0 : disabled.value) || _voiceActive) return;
|
|
30
32
|
e.preventDefault();
|
|
31
33
|
_voiceActive = true;
|
|
@@ -17,6 +17,7 @@ require("../../../node_modules/element-plus/es/index.js");
|
|
|
17
17
|
const Vue = require("vue");
|
|
18
18
|
const page = require("./page.vue.js");
|
|
19
19
|
const index$4 = require("./components/ai/AiChat/index.js");
|
|
20
|
+
const ai2 = require("./assets/images/ai2.png.js");
|
|
20
21
|
const index$5 = require("./api/index.js");
|
|
21
22
|
const eventBus = require("./utils/eventBus.js");
|
|
22
23
|
const pageHistory = require("./utils/pageHistory.js");
|
|
@@ -32,6 +33,7 @@ const index$2 = require("../../../node_modules/vant/es/floating-bubble/index.js"
|
|
|
32
33
|
const index$6 = require("../../../node_modules/element-plus/es/components/message/index.js");
|
|
33
34
|
const index$1 = require("../../../node_modules/vant/es/empty/index.js");
|
|
34
35
|
const index$3 = require("../../../node_modules/vant/es/popup/index.js");
|
|
36
|
+
const _hoisted_1 = ["src"];
|
|
35
37
|
const _sfc_main = {
|
|
36
38
|
__name: "index",
|
|
37
39
|
props: {
|
|
@@ -171,8 +173,8 @@ const _sfc_main = {
|
|
|
171
173
|
}
|
|
172
174
|
const targetPage = common.findPageByKey(pageList.value, page2);
|
|
173
175
|
showChatPopup.value = false;
|
|
174
|
-
loadPage(targetPage.id);
|
|
175
176
|
pageParams.value = params;
|
|
177
|
+
loadPage(targetPage.id);
|
|
176
178
|
}
|
|
177
179
|
Vue.provide("pageList", pageList);
|
|
178
180
|
Vue.onMounted(async () => {
|
|
@@ -225,23 +227,33 @@ const _sfc_main = {
|
|
|
225
227
|
]),
|
|
226
228
|
_: 1
|
|
227
229
|
}, 512)) : Vue.createCommentVNode("", true),
|
|
228
|
-
|
|
230
|
+
Vue.unref(currentPage) ? (Vue.openBlock(), Vue.createBlock(_component_van_floating_bubble, {
|
|
229
231
|
key: 1,
|
|
230
|
-
style: { "z-index": "100" },
|
|
232
|
+
style: { "z-index": "100", "background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" },
|
|
231
233
|
axis: "xy",
|
|
232
|
-
icon: "chat",
|
|
233
234
|
magnetic: "x",
|
|
234
235
|
gap: 12,
|
|
235
236
|
offset: Vue.unref(offset),
|
|
236
237
|
"onUpdate:offset": _cache[0] || (_cache[0] = ($event) => Vue.isRef(offset) ? offset.value = $event : null),
|
|
237
238
|
onClick: openChatPopup
|
|
238
|
-
},
|
|
239
|
+
}, {
|
|
240
|
+
default: Vue.withCtx(() => [
|
|
241
|
+
Vue.createElementVNode("img", {
|
|
242
|
+
src: Vue.unref(ai2.default),
|
|
243
|
+
class: "ai-bubble-icon"
|
|
244
|
+
}, null, 8, _hoisted_1)
|
|
245
|
+
]),
|
|
246
|
+
_: 1
|
|
247
|
+
}, 8, ["offset"])) : Vue.createCommentVNode("", true),
|
|
239
248
|
Vue.createVNode(_component_van_popup, {
|
|
240
249
|
teleport: "body",
|
|
241
250
|
show: Vue.unref(showChatPopup),
|
|
242
251
|
"onUpdate:show": _cache[2] || (_cache[2] = ($event) => Vue.isRef(showChatPopup) ? showChatPopup.value = $event : null),
|
|
243
252
|
position: "right",
|
|
244
|
-
style: {
|
|
253
|
+
style: {
|
|
254
|
+
width: "100%",
|
|
255
|
+
height: "100%"
|
|
256
|
+
},
|
|
245
257
|
"lock-scroll": true
|
|
246
258
|
}, {
|
|
247
259
|
default: Vue.withCtx(() => [
|
|
@@ -257,5 +269,5 @@ const _sfc_main = {
|
|
|
257
269
|
};
|
|
258
270
|
}
|
|
259
271
|
};
|
|
260
|
-
const MobileApp = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-
|
|
272
|
+
const MobileApp = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-197d0cb3"]]);
|
|
261
273
|
exports.default = MobileApp;
|