@fv-ui/ai-chat 0.1.1 → 0.2.0

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/lib/index.js CHANGED
@@ -1542,8 +1542,8 @@
1542
1542
  }
1543
1543
  }
1544
1544
 
1545
- const _hoisted_1$h = ["innerHTML"];
1546
- var _sfc_main$h = /* @__PURE__ */ vue.defineComponent({
1545
+ const _hoisted_1$i = ["innerHTML"];
1546
+ var _sfc_main$i = /* @__PURE__ */ vue.defineComponent({
1547
1547
  ...{ name: "FvMarkdown" },
1548
1548
  __name: "index",
1549
1549
  props: {
@@ -1566,12 +1566,12 @@
1566
1566
  return vue.openBlock(), vue.createElementBlock("div", {
1567
1567
  class: "fv-xmarkdown markdown-body",
1568
1568
  innerHTML: html.value
1569
- }, null, 8, _hoisted_1$h);
1569
+ }, null, 8, _hoisted_1$i);
1570
1570
  };
1571
1571
  }
1572
1572
  });
1573
1573
 
1574
- const _hoisted_1$g = {
1574
+ const _hoisted_1$h = {
1575
1575
  key: 1,
1576
1576
  class: "fv-typewriter-text"
1577
1577
  };
@@ -1579,7 +1579,7 @@
1579
1579
  key: 0,
1580
1580
  class: "fv-typewriter-suffix"
1581
1581
  };
1582
- var _sfc_main$g = /* @__PURE__ */ vue.defineComponent({
1582
+ var _sfc_main$h = /* @__PURE__ */ vue.defineComponent({
1583
1583
  ...{ name: "FvTypewriter" },
1584
1584
  __name: "index",
1585
1585
  props: {
@@ -1669,11 +1669,11 @@
1669
1669
  },
1670
1670
  [
1671
1671
  vue.createCommentVNode(" markdown \u6A21\u5F0F:suffix \u4E0D\u8FFD\u52A0(element-plus-x \u884C\u4E3A),isTyping \u900F\u4F20\u7ED9 XMarkdown \u4F5C\u6837\u5F0F\u94A9\u5B50 "),
1672
- __props.isMarkdown ? (vue.openBlock(), vue.createBlock(_sfc_main$h, {
1672
+ __props.isMarkdown ? (vue.openBlock(), vue.createBlock(_sfc_main$i, {
1673
1673
  key: 0,
1674
1674
  content: renderedContent.value,
1675
1675
  "is-typing": isTyping.value
1676
- }, null, 8, ["content", "is-typing"])) : (vue.openBlock(), vue.createElementBlock("span", _hoisted_1$g, [
1676
+ }, null, 8, ["content", "is-typing"])) : (vue.openBlock(), vue.createElementBlock("span", _hoisted_1$h, [
1677
1677
  vue.createTextVNode(
1678
1678
  vue.toDisplayString(renderedContent.value),
1679
1679
  1
@@ -1708,7 +1708,7 @@
1708
1708
  }
1709
1709
  });
1710
1710
 
1711
- const _hoisted_1$f = {
1711
+ const _hoisted_1$g = {
1712
1712
  key: 0,
1713
1713
  class: "fv-bubble-avatar"
1714
1714
  };
@@ -1729,7 +1729,7 @@
1729
1729
  key: 1,
1730
1730
  class: "fv-bubble-footer"
1731
1731
  };
1732
- var _sfc_main$f = /* @__PURE__ */ vue.defineComponent({
1732
+ var _sfc_main$g = /* @__PURE__ */ vue.defineComponent({
1733
1733
  ...{ name: "FvBubble" },
1734
1734
  __name: "index",
1735
1735
  props: {
@@ -1796,7 +1796,7 @@
1796
1796
  },
1797
1797
  [
1798
1798
  vue.createCommentVNode(" \u5934\u50CF:placement \u540C\u7F18\u6392\u5217(end \u7ECF row-reverse \u7FFB\u8F6C\u5230\u53F3\u4FA7),#avatar \u63D2\u69FD\u4F18\u5148 "),
1799
- _ctx.$slots.avatar || __props.avatar || __props.avatarIcon ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$f, [
1799
+ _ctx.$slots.avatar || __props.avatar || __props.avatarIcon ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$g, [
1800
1800
  _ctx.$slots.avatar ? vue.renderSlot(_ctx.$slots, "avatar", {}, void 0, void 0, 0) : (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElAvatar), {
1801
1801
  key: 1,
1802
1802
  size: __props.avatarSize || void 0,
@@ -1854,7 +1854,7 @@
1854
1854
  { key: 2 },
1855
1855
  [
1856
1856
  vue.createCommentVNode(" typing \u900F\u4F20 Typewriter;\u975E typing:isMarkdown \u2192 XMarkdown,\u5426\u5219\u7EAF\u6587\u672C "),
1857
- vue.createVNode(_sfc_main$g, {
1857
+ vue.createVNode(_sfc_main$h, {
1858
1858
  ref_key: "twRef",
1859
1859
  ref: twRef,
1860
1860
  content: __props.content,
@@ -1868,7 +1868,7 @@
1868
1868
  ],
1869
1869
  2112
1870
1870
  /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */
1871
- )) : __props.isMarkdown ? (vue.openBlock(), vue.createBlock(_sfc_main$h, {
1871
+ )) : __props.isMarkdown ? (vue.openBlock(), vue.createBlock(_sfc_main$i, {
1872
1872
  key: 3,
1873
1873
  content: __props.content
1874
1874
  }, null, 8, ["content"])) : (vue.openBlock(), vue.createElementBlock(
@@ -1894,8 +1894,8 @@
1894
1894
  }
1895
1895
  });
1896
1896
 
1897
- const _hoisted_1$e = { class: "fv-bubble-list" };
1898
- var _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
1897
+ const _hoisted_1$f = { class: "fv-bubble-list" };
1898
+ var _sfc_main$f = /* @__PURE__ */ vue.defineComponent({
1899
1899
  ...{ name: "FvBubbleList" },
1900
1900
  __name: "index",
1901
1901
  props: {
@@ -1995,7 +1995,7 @@
1995
1995
  }));
1996
1996
  __expose({ scrollToTop, scrollToBottom, scrollToBubble });
1997
1997
  return (_ctx, _cache) => {
1998
- return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$e, [
1998
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$f, [
1999
1999
  vue.createElementVNode(
2000
2000
  "div",
2001
2001
  {
@@ -2010,7 +2010,7 @@
2010
2010
  vue.Fragment,
2011
2011
  null,
2012
2012
  vue.renderList(__props.list, (item, index) => {
2013
- return vue.openBlock(), vue.createBlock(_sfc_main$f, vue.mergeProps({
2013
+ return vue.openBlock(), vue.createBlock(_sfc_main$g, vue.mergeProps({
2014
2014
  key: item.key,
2015
2015
  ref_for: true,
2016
2016
  ref: (el) => setBubbleRef(el, index)
@@ -2118,6 +2118,139 @@
2118
2118
  }
2119
2119
  });
2120
2120
 
2121
+ const _hoisted_1$e = {
2122
+ class: "fv-stop-icon",
2123
+ xmlns: "http://www.w3.org/2000/svg",
2124
+ viewBox: "0 0 24 24",
2125
+ width: "1em",
2126
+ height: "1em",
2127
+ "aria-hidden": "true",
2128
+ focusable: "false"
2129
+ };
2130
+ var _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
2131
+ ...{ name: "FvStopIcon" },
2132
+ __name: "StopIcon",
2133
+ setup(__props) {
2134
+ return (_ctx, _cache) => {
2135
+ return vue.openBlock(), vue.createElementBlock("svg", _hoisted_1$e, [..._cache[0] || (_cache[0] = [
2136
+ vue.createElementVNode(
2137
+ "rect",
2138
+ {
2139
+ x: "5.5",
2140
+ y: "5.5",
2141
+ width: "13",
2142
+ height: "13",
2143
+ rx: "2.5",
2144
+ fill: "currentColor"
2145
+ },
2146
+ null,
2147
+ -1
2148
+ /* CACHED */
2149
+ )
2150
+ ])]);
2151
+ };
2152
+ }
2153
+ });
2154
+
2155
+ function getSpeechRecognitionCtor() {
2156
+ if (typeof window === "undefined") return void 0;
2157
+ const w = window;
2158
+ const ctor = w.SpeechRecognition ?? w.webkitSpeechRecognition;
2159
+ return typeof ctor === "function" ? ctor : void 0;
2160
+ }
2161
+ function useRecord() {
2162
+ const isRecording = vue.ref(false);
2163
+ const text = vue.ref("");
2164
+ const audioUrl = vue.ref(null);
2165
+ const error = vue.ref(null);
2166
+ let mediaStream = null;
2167
+ let mediaRecorder = null;
2168
+ let chunks = [];
2169
+ let recognition = null;
2170
+ let finalText = "";
2171
+ function revokeAudioUrl() {
2172
+ if (audioUrl.value) {
2173
+ URL.revokeObjectURL(audioUrl.value);
2174
+ audioUrl.value = null;
2175
+ }
2176
+ }
2177
+ async function start() {
2178
+ if (isRecording.value) return;
2179
+ error.value = null;
2180
+ text.value = "";
2181
+ finalText = "";
2182
+ chunks = [];
2183
+ revokeAudioUrl();
2184
+ if (typeof MediaRecorder === "undefined" || !navigator.mediaDevices?.getUserMedia) {
2185
+ error.value = new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u97F3\u9891\u5F55\u5236");
2186
+ return;
2187
+ }
2188
+ try {
2189
+ mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
2190
+ } catch (e) {
2191
+ error.value = e instanceof Error ? e : new Error(String(e));
2192
+ return;
2193
+ }
2194
+ mediaRecorder = new MediaRecorder(mediaStream);
2195
+ mediaRecorder.ondataavailable = (e) => {
2196
+ if (e.data && e.data.size > 0) chunks.push(e.data);
2197
+ };
2198
+ mediaRecorder.onstop = () => {
2199
+ if (chunks.length > 0) {
2200
+ const blob = new Blob(chunks, { type: mediaRecorder?.mimeType || "audio/webm" });
2201
+ audioUrl.value = URL.createObjectURL(blob);
2202
+ }
2203
+ mediaStream?.getTracks().forEach((t) => t.stop());
2204
+ mediaStream = null;
2205
+ };
2206
+ const SRCtor = getSpeechRecognitionCtor();
2207
+ if (SRCtor) {
2208
+ try {
2209
+ recognition = new SRCtor();
2210
+ recognition.continuous = true;
2211
+ recognition.interimResults = true;
2212
+ recognition.onresult = (event) => {
2213
+ let interim = "";
2214
+ for (let i = event.resultIndex; i < event.results.length; i++) {
2215
+ const r = event.results[i];
2216
+ if (r.isFinal) finalText += r[0].transcript;
2217
+ else interim += r[0].transcript;
2218
+ }
2219
+ text.value = finalText + interim;
2220
+ };
2221
+ recognition.onerror = (ev) => {
2222
+ error.value = new Error(`\u8BED\u97F3\u8BC6\u522B\u9519\u8BEF: ${ev?.error ?? "unknown"}`);
2223
+ };
2224
+ recognition.start();
2225
+ } catch {
2226
+ recognition = null;
2227
+ }
2228
+ }
2229
+ mediaRecorder.start();
2230
+ isRecording.value = true;
2231
+ }
2232
+ function stop() {
2233
+ if (!isRecording.value) return;
2234
+ isRecording.value = false;
2235
+ try {
2236
+ recognition?.stop();
2237
+ } catch {
2238
+ }
2239
+ recognition = null;
2240
+ try {
2241
+ if (mediaRecorder && mediaRecorder.state !== "inactive") mediaRecorder.stop();
2242
+ } catch {
2243
+ }
2244
+ }
2245
+ if (vue.getCurrentInstance()) {
2246
+ vue.onBeforeUnmount(() => {
2247
+ stop();
2248
+ revokeAudioUrl();
2249
+ });
2250
+ }
2251
+ return { start, stop, isRecording, text, audioUrl, error };
2252
+ }
2253
+
2121
2254
  const _hoisted_1$d = {
2122
2255
  key: 0,
2123
2256
  class: "fv-sender-header"
@@ -2132,24 +2265,38 @@
2132
2265
  key: 0,
2133
2266
  class: "fv-sender-prefix"
2134
2267
  };
2135
- const _hoisted_6$5 = ["value", "placeholder", "disabled", "readonly"];
2136
- const _hoisted_7$4 = {
2137
- key: 1,
2268
+ const _hoisted_6$5 = {
2269
+ class: "fv-sender-recording-wave",
2270
+ "aria-hidden": "true"
2271
+ };
2272
+ const _hoisted_7$4 = { class: "fv-sender-recording-duration" };
2273
+ const _hoisted_8$3 = ["value", "placeholder", "disabled", "readonly"];
2274
+ const _hoisted_9$2 = {
2275
+ key: 3,
2138
2276
  class: "fv-sender-actions"
2139
2277
  };
2140
- const _hoisted_8$3 = {
2141
- key: 2,
2278
+ const _hoisted_10$2 = {
2279
+ key: 4,
2142
2280
  class: "fv-sender-actions"
2143
2281
  };
2144
- const _hoisted_9$2 = {
2282
+ const _hoisted_11$2 = {
2145
2283
  key: 0,
2146
2284
  class: "fv-sender-action-left"
2147
2285
  };
2148
- const _hoisted_10$2 = {
2286
+ const _hoisted_12$2 = {
2287
+ class: "fv-sender-stop-ring",
2288
+ "aria-hidden": "true"
2289
+ };
2290
+ const _hoisted_13$2 = ["rx"];
2291
+ const _hoisted_14$2 = {
2149
2292
  key: 1,
2150
2293
  class: "fv-sender-footer"
2151
2294
  };
2152
2295
  const ROW_HEIGHT = 22;
2296
+ const WAVE_BARS = 48;
2297
+ const RING_HEAD = 10;
2298
+ const RING_TAIL = 40;
2299
+ const RING_LAYERS = 6;
2153
2300
  var _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
2154
2301
  ...{ name: "FvSender" },
2155
2302
  __name: "index",
@@ -2163,6 +2310,8 @@
2163
2310
  disabled: { type: Boolean, required: false, default: false },
2164
2311
  readOnly: { type: Boolean, required: false, default: false },
2165
2312
  clearable: { type: Boolean, required: false, default: false },
2313
+ round: { type: Boolean, required: false, default: true },
2314
+ shadow: { type: Boolean, required: false, default: false },
2166
2315
  inputWidth: { type: String, required: false, default: "" },
2167
2316
  variant: { type: String, required: false, default: "default" },
2168
2317
  submitBtnDisabled: { type: Boolean, required: false, default: false },
@@ -2335,11 +2484,136 @@
2335
2484
  for (const f of Array.from(files)) emit("paste-file", f);
2336
2485
  }
2337
2486
  }
2338
- function onSpeechClick() {
2339
- console.warn("useRecord \u672A\u63A5\u5165,Task 15 \u63A5\u7EBF");
2487
+ const { start: startRecord, stop: stopRecord, isRecording, text: recordText, error: recordError } = useRecord();
2488
+ async function exitRecordingFocusInput() {
2489
+ await vue.nextTick();
2490
+ focus("end");
2491
+ }
2492
+ function insertTextAtCursor(insert) {
2493
+ if (!insert) return;
2494
+ const el = textareaRef.value;
2495
+ const pos = el?.selectionStart ?? value.value.length;
2496
+ const next = value.value.slice(0, pos) + insert + value.value.slice(el?.selectionEnd ?? pos);
2497
+ setValue(next);
2498
+ vue.nextTick(() => {
2499
+ const t = textareaRef.value;
2500
+ if (t) t.setSelectionRange(pos + insert.length, pos + insert.length);
2501
+ });
2502
+ }
2503
+ async function onSpeechClick() {
2504
+ if (isRecording.value) return;
2505
+ await startRecord();
2506
+ if (recordError.value) {
2507
+ elementPlus.ElMessage.error(recordError.value.message || "\u65E0\u6CD5\u8BBF\u95EE\u9EA6\u514B\u98CE");
2508
+ return;
2509
+ }
2510
+ emit("recording-change", true);
2511
+ }
2512
+ async function onRecordConfirm() {
2513
+ stopRecord();
2514
+ emit("recording-change", false);
2515
+ insertTextAtCursor(recordText.value);
2516
+ await exitRecordingFocusInput();
2340
2517
  }
2518
+ async function onRecordCancel() {
2519
+ stopRecord();
2520
+ emit("recording-change", false);
2521
+ await exitRecordingFocusInput();
2522
+ }
2523
+ function onRecordingKeydown(e) {
2524
+ if (e.key === "Escape") {
2525
+ e.preventDefault();
2526
+ onRecordCancel();
2527
+ }
2528
+ }
2529
+ const waveLevels = vue.ref(Array(WAVE_BARS).fill(0.04));
2530
+ let audioCtx = null;
2531
+ let analyserSource = null;
2532
+ let waveRafId = 0;
2533
+ function normalizeRms(rms) {
2534
+ const scaled = Math.pow(rms * 10, 1.5);
2535
+ return Math.min(1, Math.max(0.04, scaled));
2536
+ }
2537
+ async function startWaveVisualizer() {
2538
+ if (typeof AudioContext === "undefined" || !navigator.mediaDevices?.getUserMedia) return;
2539
+ try {
2540
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2541
+ audioCtx = new AudioContext();
2542
+ analyserSource = audioCtx.createMediaStreamSource(stream);
2543
+ const analyser = audioCtx.createAnalyser();
2544
+ analyser.minDecibels = -45;
2545
+ analyserSource.connect(analyser);
2546
+ const timeData = new Uint8Array(analyser.fftSize);
2547
+ const processFrame = () => {
2548
+ analyser.getByteTimeDomainData(timeData);
2549
+ let sumSquares = 0;
2550
+ for (let i = 0; i < timeData.length; i++) {
2551
+ const v = (timeData[i] - 128) / 128;
2552
+ sumSquares += v * v;
2553
+ }
2554
+ const level = normalizeRms(Math.sqrt(sumSquares / timeData.length));
2555
+ waveLevels.value = [...waveLevels.value.slice(1), level];
2556
+ waveRafId = requestAnimationFrame(processFrame);
2557
+ };
2558
+ waveRafId = requestAnimationFrame(processFrame);
2559
+ stream.getTracks().forEach((t) => t.addEventListener("ended", () => stopWaveVisualizer()));
2560
+ analyserSourceStream = stream;
2561
+ } catch {
2562
+ }
2563
+ }
2564
+ let analyserSourceStream = null;
2565
+ function stopWaveVisualizer() {
2566
+ cancelAnimationFrame(waveRafId);
2567
+ waveRafId = 0;
2568
+ analyserSource?.disconnect();
2569
+ analyserSource = null;
2570
+ analyserSourceStream?.getTracks().forEach((t) => t.stop());
2571
+ analyserSourceStream = null;
2572
+ audioCtx?.close().catch(() => {
2573
+ });
2574
+ audioCtx = null;
2575
+ waveLevels.value = Array(WAVE_BARS).fill(0.04);
2576
+ }
2577
+ vue.watch(isRecording, (rec) => {
2578
+ if (rec) startWaveVisualizer();
2579
+ else stopWaveVisualizer();
2580
+ });
2581
+ const recordSeconds = vue.ref(0);
2582
+ let recordTimer = null;
2583
+ vue.watch(isRecording, (rec) => {
2584
+ if (rec) {
2585
+ recordSeconds.value = 0;
2586
+ recordTimer = setInterval(() => {
2587
+ recordSeconds.value++;
2588
+ }, 1e3);
2589
+ } else if (recordTimer) {
2590
+ clearInterval(recordTimer);
2591
+ recordTimer = null;
2592
+ }
2593
+ });
2594
+ const recordDuration = vue.computed(() => {
2595
+ const m = Math.floor(recordSeconds.value / 60);
2596
+ const s = recordSeconds.value % 60;
2597
+ return `${m}:${String(s).padStart(2, "0")}`;
2598
+ });
2599
+ vue.onBeforeUnmount(() => {
2600
+ stopWaveVisualizer();
2601
+ if (recordTimer) clearInterval(recordTimer);
2602
+ });
2341
2603
  const inputDisabled = vue.computed(() => props.disabled || props.loading);
2342
2604
  const inputReadonly = vue.computed(() => props.readOnly);
2605
+ const ringLayers = vue.computed(
2606
+ () => Array.from({ length: RING_LAYERS }, (_, i) => {
2607
+ const len = RING_HEAD + RING_TAIL * i / (RING_LAYERS - 1);
2608
+ const alpha = 0.9 * Math.pow(0.72, i);
2609
+ return {
2610
+ style: {
2611
+ "--ring-len": len.toFixed(2),
2612
+ "--ring-alpha": alpha.toFixed(3)
2613
+ }
2614
+ };
2615
+ })
2616
+ );
2343
2617
  const slots = vue.useSlots();
2344
2618
  const hasActionListSlot = vue.computed(() => !!slots["action-list"]);
2345
2619
  __expose({ submit, cancel, clear, focus, blur, openHeader, closeHeader });
@@ -2347,7 +2621,7 @@
2347
2621
  return vue.openBlock(), vue.createElementBlock(
2348
2622
  "div",
2349
2623
  {
2350
- class: vue.normalizeClass(["fv-sender", [`variant-${__props.variant}`, { "is-disabled": __props.disabled, "is-loading": __props.loading, "is-focused": isFocused.value }]])
2624
+ class: vue.normalizeClass(["fv-sender", [`variant-${__props.variant}`, { "is-disabled": __props.disabled, "is-loading": __props.loading, "is-focused": isFocused.value, "has-shadow": __props.shadow }]])
2351
2625
  },
2352
2626
  [
2353
2627
  vue.createCommentVNode(" \u5934\u90E8:#header \u63D2\u69FD,openHeader/closeHeader \u63A7\u5236 "),
@@ -2365,9 +2639,89 @@
2365
2639
  _ctx.$slots.prefix ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$7, [
2366
2640
  vue.renderSlot(_ctx.$slots, "prefix")
2367
2641
  ])) : vue.createCommentVNode("v-if", true),
2368
- vue.createElementVNode(
2642
+ vue.createCommentVNode(" \u8BED\u97F3\u5F55\u97F3\u4E2D:\u8F93\u5165\u533A\u539F\u4F4D\u66FF\u6362\u4E3A\u5F55\u97F3\u6761(\u53D6\u6D88/\u6CE2\u5F62+\u65F6\u957F/\u786E\u8BA4);Esc \u53D6\u6D88 "),
2643
+ vue.unref(isRecording) ? (vue.openBlock(), vue.createElementBlock(
2369
2644
  "div",
2370
2645
  {
2646
+ key: 1,
2647
+ class: "fv-sender-recording",
2648
+ tabindex: "-1",
2649
+ onKeydown: onRecordingKeydown
2650
+ },
2651
+ [
2652
+ vue.createVNode(vue.unref(elementPlus.ElButton), {
2653
+ class: "fv-sender-recording-cancel",
2654
+ text: "",
2655
+ circle: "",
2656
+ "aria-label": "\u53D6\u6D88\u5F55\u97F3",
2657
+ onClick: onRecordCancel
2658
+ }, {
2659
+ default: vue.withCtx(() => [
2660
+ vue.createVNode(vue.unref(elementPlus.ElIcon), null, {
2661
+ default: vue.withCtx(() => [
2662
+ vue.createVNode(vue.unref(ElementPlusIconsVue.Close))
2663
+ ]),
2664
+ _: 1
2665
+ /* STABLE */
2666
+ })
2667
+ ]),
2668
+ _: 1
2669
+ /* STABLE */
2670
+ }),
2671
+ vue.createElementVNode("div", _hoisted_6$5, [
2672
+ (vue.openBlock(true), vue.createElementBlock(
2673
+ vue.Fragment,
2674
+ null,
2675
+ vue.renderList(waveLevels.value, (lv, i) => {
2676
+ return vue.openBlock(), vue.createElementBlock(
2677
+ "span",
2678
+ {
2679
+ key: i,
2680
+ class: "fv-sender-recording-bar",
2681
+ style: vue.normalizeStyle({ height: `${Math.round(lv * 100)}%` })
2682
+ },
2683
+ null,
2684
+ 4
2685
+ /* STYLE */
2686
+ );
2687
+ }),
2688
+ 128
2689
+ /* KEYED_FRAGMENT */
2690
+ ))
2691
+ ]),
2692
+ vue.createElementVNode(
2693
+ "span",
2694
+ _hoisted_7$4,
2695
+ vue.toDisplayString(recordDuration.value),
2696
+ 1
2697
+ /* TEXT */
2698
+ ),
2699
+ vue.createVNode(vue.unref(elementPlus.ElButton), {
2700
+ class: "fv-sender-recording-confirm",
2701
+ type: "primary",
2702
+ circle: "",
2703
+ "aria-label": "\u786E\u8BA4\u5F55\u97F3",
2704
+ onClick: onRecordConfirm
2705
+ }, {
2706
+ default: vue.withCtx(() => [
2707
+ vue.createVNode(vue.unref(elementPlus.ElIcon), null, {
2708
+ default: vue.withCtx(() => [
2709
+ vue.createVNode(vue.unref(ElementPlusIconsVue.Check))
2710
+ ]),
2711
+ _: 1
2712
+ /* STABLE */
2713
+ })
2714
+ ]),
2715
+ _: 1
2716
+ /* STABLE */
2717
+ })
2718
+ ],
2719
+ 32
2720
+ /* NEED_HYDRATION */
2721
+ )) : (vue.openBlock(), vue.createElementBlock(
2722
+ "div",
2723
+ {
2724
+ key: 2,
2371
2725
  class: "fv-sender-input",
2372
2726
  style: vue.normalizeStyle(__props.inputWidth && __props.variant === "default" ? { width: __props.inputWidth } : void 0)
2373
2727
  },
@@ -2387,7 +2741,7 @@
2387
2741
  onPaste,
2388
2742
  onFocus: _cache[0] || (_cache[0] = ($event) => isFocused.value = true),
2389
2743
  onBlur: onTextareaBlur
2390
- }, null, 40, _hoisted_6$5),
2744
+ }, null, 40, _hoisted_8$3),
2391
2745
  vue.createCommentVNode(" \u6307\u4EE4\u5F39\u6846:\u5185\u7F6E el-popover,\u5B9A\u4F4D\u6309 triggerPopover* \u7CFB\u5217 "),
2392
2746
  vue.createVNode(vue.unref(elementPlus.ElPopover), {
2393
2747
  visible: triggerVisible.value && __props.triggerStrings.length > 0,
@@ -2418,12 +2772,12 @@
2418
2772
  ],
2419
2773
  4
2420
2774
  /* STYLE */
2421
- ),
2775
+ )),
2422
2776
  vue.createCommentVNode(" \u64CD\u4F5C\u533A:#action-list \u5B58\u5728\u65F6\u5168\u81EA\u5B9A\u4E49,\u9690\u85CF\u5185\u7F6E\u6309\u94AE;#action-left \u7F6E\u4E8E\u64CD\u4F5C\u680F\u5DE6\u4FA7(\u4E0E\u63D0\u4EA4\u6309\u94AE\u540C\u884C) "),
2423
- hasActionListSlot.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_7$4, [
2777
+ hasActionListSlot.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_9$2, [
2424
2778
  vue.renderSlot(_ctx.$slots, "action-list")
2425
- ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_8$3, [
2426
- _ctx.$slots["action-left"] ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_9$2, [
2779
+ ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_10$2, [
2780
+ _ctx.$slots["action-left"] ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_11$2, [
2427
2781
  vue.renderSlot(_ctx.$slots, "action-left")
2428
2782
  ])) : vue.createCommentVNode("v-if", true),
2429
2783
  __props.clearable && value.value ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
@@ -2446,7 +2800,7 @@
2446
2800
  _: 1
2447
2801
  /* STABLE */
2448
2802
  }, 8, ["disabled"])) : vue.createCommentVNode("v-if", true),
2449
- __props.allowSpeech ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
2803
+ __props.allowSpeech && !vue.unref(isRecording) ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
2450
2804
  key: 2,
2451
2805
  class: "fv-sender-speech",
2452
2806
  text: "",
@@ -2466,29 +2820,49 @@
2466
2820
  _: 1
2467
2821
  /* STABLE */
2468
2822
  }, 8, ["disabled"])) : vue.createCommentVNode("v-if", true),
2469
- vue.createCommentVNode(" loading \u2192 \u505C\u6B62\u6309\u94AE;\u5426\u5219\u53D1\u9001\u6309\u94AE "),
2823
+ vue.createCommentVNode(" loading \u2192 \u505C\u6B62\u6309\u94AE:SVG \u5149\u5E26\u6CBF\u8FB9\u6846\u5300\u901F\u6D41\u8F6C,\u65B9/\u5706\u6309\u94AE\u5747\u9002\u7528 "),
2470
2824
  __props.loading ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
2471
2825
  key: 3,
2472
2826
  class: "fv-sender-stop",
2473
2827
  type: "primary",
2828
+ circle: __props.round,
2474
2829
  "aria-label": "\u505C\u6B62",
2475
2830
  onClick: cancel
2476
2831
  }, {
2477
2832
  default: vue.withCtx(() => [
2478
2833
  vue.createVNode(vue.unref(elementPlus.ElIcon), null, {
2479
2834
  default: vue.withCtx(() => [
2480
- vue.createVNode(vue.unref(ElementPlusIconsVue.VideoPause))
2835
+ vue.createVNode(_sfc_main$e)
2481
2836
  ]),
2482
2837
  _: 1
2483
2838
  /* STABLE */
2484
- })
2839
+ }),
2840
+ vue.createCommentVNode('\r\n \u6CBF\u8FB9\u6846\u6D41\u8F6C\u7684\u6E10\u53D8\u5149\u5E26(\u8682\u8681\u7EBF\u539F\u7406,pathLength=100 \u5F52\u4E00\u5468\u957F,dashoffset \u5300\u901F\u5E73\u79FB)\u3002\r\n \u6E10\u53D8\u96BE\u70B9:linearGradient \u6CBF\u76F4\u7EBF\u8DEF\u5F84\u5B9A\u4E49,\u65E0\u6CD5\u6CBF\u95ED\u5408\u6298\u7EBF\u5468\u957F\u6E10\u53D8 \u2014\u2014\r\n \u6545\u7528 6 \u5C42\u957F\u77ED/\u900F\u660E\u5EA6\u53CC\u9012\u51CF\u7684\u540C\u76F8\u4F4D dash \u53E0\u52A0,\u79BB\u6563\u9636\u68AF\u903C\u8FD1\u8FDE\u7EED\u6E10\u53D8:\r\n \u6BCF\u5C42\u957F\u5EA6\u4F9D\u6B21\u9012\u589E(\u5E26\u5934\u6700\u77ED\u6700\u5B9E,\u5E26\u5C3E\u6700\u957F\u6700\u865A),\u900F\u660E\u5EA6\u4F9D\u6B21\u9012\u51CF,\r\n \u53E0\u52A0\u540E\u7684\u900F\u660E\u5EA6\u5256\u9762\u4E3A\u591A\u7EA7\u53F0\u9636 \u2192 \u8FD0\u52A8\u4E2D\u8BFB\u4F5C"\u5934\u4EAE\u5C3E\u9690"\u7684\u8FDE\u7EED\u62D6\u5F71\u3002\r\n \u5C42\u6570\u7531 RING_LAYERS \u5E38\u91CF\u9A71\u52A8,\u900F\u660E\u5EA6\u63D2\u503C\u5728 CSS \u91CC\u6309 --ring-alpha \u53D8\u91CF\u7B97\u3002\r\n '),
2841
+ (vue.openBlock(), vue.createElementBlock("svg", _hoisted_12$2, [
2842
+ (vue.openBlock(true), vue.createElementBlock(
2843
+ vue.Fragment,
2844
+ null,
2845
+ vue.renderList(ringLayers.value, (layer, i) => {
2846
+ return vue.openBlock(), vue.createElementBlock("rect", {
2847
+ key: i,
2848
+ class: "fv-sender-stop-ring-rect",
2849
+ style: vue.normalizeStyle(layer.style),
2850
+ rx: __props.round ? "50%" : 2.5,
2851
+ pathLength: "100"
2852
+ }, null, 12, _hoisted_13$2);
2853
+ }),
2854
+ 128
2855
+ /* KEYED_FRAGMENT */
2856
+ ))
2857
+ ]))
2485
2858
  ]),
2486
2859
  _: 1
2487
2860
  /* STABLE */
2488
- })) : (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
2861
+ }, 8, ["circle"])) : (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
2489
2862
  key: 4,
2490
2863
  class: "fv-sender-submit",
2491
2864
  type: "primary",
2865
+ circle: __props.round,
2492
2866
  disabled: !submitable(),
2493
2867
  "aria-label": "\u53D1\u9001",
2494
2868
  onClick: doSubmit
@@ -2504,13 +2878,13 @@
2504
2878
  ]),
2505
2879
  _: 1
2506
2880
  /* STABLE */
2507
- }, 8, ["disabled"]))
2881
+ }, 8, ["circle", "disabled"]))
2508
2882
  ]))
2509
2883
  ]),
2510
2884
  vue.createCommentVNode(" /fv-sender-row ")
2511
2885
  ]),
2512
2886
  vue.createCommentVNode(" \u5E95\u90E8 "),
2513
- _ctx.$slots.footer ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_10$2, [
2887
+ _ctx.$slots.footer ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_14$2, [
2514
2888
  vue.renderSlot(_ctx.$slots, "footer")
2515
2889
  ])) : vue.createCommentVNode("v-if", true)
2516
2890
  ],
@@ -5815,7 +6189,7 @@ ${fileParts.join("\n")}` });
5815
6189
  "auto-collapse": __props.autoCollapse
5816
6190
  }, {
5817
6191
  content: vue.withCtx(() => [
5818
- vue.createVNode(_sfc_main$h, {
6192
+ vue.createVNode(_sfc_main$i, {
5819
6193
  content: __props.message.reasoning ?? ""
5820
6194
  }, null, 8, ["content"])
5821
6195
  ]),
@@ -5830,7 +6204,7 @@ ${fileParts.join("\n")}` });
5830
6204
  vue.toDisplayString(__props.message.error || "\u751F\u6210\u5931\u8D25"),
5831
6205
  1
5832
6206
  /* TEXT */
5833
- )) : __props.message.content ? (vue.openBlock(), vue.createBlock(_sfc_main$h, {
6207
+ )) : __props.message.content ? (vue.openBlock(), vue.createBlock(_sfc_main$i, {
5834
6208
  key: 1,
5835
6209
  content: __props.message.content
5836
6210
  }, null, 8, ["content"])) : __props.message.status === "pending" || __props.message.status === "streaming" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_12, [
@@ -6401,7 +6775,10 @@ ${fileParts.join("\n")}` });
6401
6775
  }
6402
6776
  });
6403
6777
 
6404
- const _hoisted_1 = { class: "fv-ai-chat-header" };
6778
+ const _hoisted_1 = {
6779
+ key: 0,
6780
+ class: "fv-ai-chat-header"
6781
+ };
6405
6782
  const _hoisted_2 = { class: "fv-ai-chat-title" };
6406
6783
  const _hoisted_3 = { class: "fv-ai-chat-body" };
6407
6784
  const _hoisted_4 = { class: "fv-ai-chat-footer" };
@@ -6416,7 +6793,10 @@ ${fileParts.join("\n")}` });
6416
6793
  adapter: { type: Object, required: false, default: void 0 },
6417
6794
  messages: { type: Array, required: false, default: void 0 },
6418
6795
  title: { type: String, required: false, default: "" },
6796
+ showHeader: { type: Boolean, required: false, default: false },
6797
+ centerWelcome: { type: Boolean, required: false, default: true },
6419
6798
  placeholder: { type: String, required: false, default: "" },
6799
+ senderShadow: { type: Boolean, required: false, default: true },
6420
6800
  suggestions: { type: Array, required: false, default: () => [] },
6421
6801
  features: { type: Object, required: false, default: () => ({}) },
6422
6802
  allowAttachments: { type: Boolean, required: false, default: false },
@@ -6464,6 +6844,7 @@ ${fileParts.join("\n")}` });
6464
6844
  const { send, stop: stopChat, regenerate: regenerateChat, msgs, isLoading } = chat;
6465
6845
  const nodeTable = chat.messages;
6466
6846
  const chain = chat.chain;
6847
+ const isCenterWelcome = vue.computed(() => props.centerWelcome && chain.value.length === 0);
6467
6848
  let flushTimer = null;
6468
6849
  vue.watch(chain, (c) => {
6469
6850
  if (flushTimer) clearTimeout(flushTimer);
@@ -6629,11 +7010,11 @@ ${fileParts.join("\n")}` });
6629
7010
  return vue.openBlock(), vue.createElementBlock(
6630
7011
  "div",
6631
7012
  {
6632
- class: vue.normalizeClass(["fv-ai-chat", { "is-dark": isDark.value }])
7013
+ class: vue.normalizeClass(["fv-ai-chat", { "is-dark": isDark.value, "is-center-welcome": isCenterWelcome.value }])
6633
7014
  },
6634
7015
  [
6635
7016
  vue.createCommentVNode(" \u9876\u680F:\u6807\u9898 + \u6A21\u578B\u9009\u62E9\u5668(models \u6709\u503C\u65F6 multiple) "),
6636
- vue.createElementVNode("div", _hoisted_1, [
7017
+ __props.showHeader ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
6637
7018
  vue.createElementVNode(
6638
7019
  "div",
6639
7020
  _hoisted_2,
@@ -6669,7 +7050,7 @@ ${fileParts.join("\n")}` });
6669
7050
  _: 1
6670
7051
  /* STABLE */
6671
7052
  }, 8, ["modelValue"])) : vue.createCommentVNode("v-if", true)
6672
- ]),
7053
+ ])) : vue.createCommentVNode("v-if", true),
6673
7054
  vue.createCommentVNode(" \u6D88\u606F\u533A:\u7A7A\u65F6 Welcome + Prompts(AiMessages \u5185\u7F6E);\u5F00\u542F artifacts \u65F6\u53F3\u4FA7\u62FC\u9884\u89C8\u9762\u677F "),
6674
7055
  vue.createElementVNode("div", _hoisted_3, [
6675
7056
  vue.createVNode(_sfc_main$2, {
@@ -6720,6 +7101,7 @@ ${fileParts.join("\n")}` });
6720
7101
  vue.createVNode(_sfc_main$d, {
6721
7102
  placeholder: __props.placeholder,
6722
7103
  loading: vue.unref(isLoading),
7104
+ shadow: __props.senderShadow,
6723
7105
  variant: "updown",
6724
7106
  maxlength: 8e3,
6725
7107
  "allow-empty-submit": __props.allowAttachments && !!pendingFiles.value.length,
@@ -6764,7 +7146,7 @@ ${fileParts.join("\n")}` });
6764
7146
  ]),
6765
7147
  key: "2"
6766
7148
  } : void 0
6767
- ]), 1032, ["placeholder", "loading", "allow-empty-submit"])
7149
+ ]), 1032, ["placeholder", "loading", "shadow", "allow-empty-submit"])
6768
7150
  ])
6769
7151
  ],
6770
7152
  2
@@ -6895,105 +7277,6 @@ ${fileParts.join("\n")}` });
6895
7277
  });
6896
7278
  }
6897
7279
 
6898
- function getSpeechRecognitionCtor() {
6899
- if (typeof window === "undefined") return void 0;
6900
- const w = window;
6901
- const ctor = w.SpeechRecognition ?? w.webkitSpeechRecognition;
6902
- return typeof ctor === "function" ? ctor : void 0;
6903
- }
6904
- function useRecord() {
6905
- const isRecording = vue.ref(false);
6906
- const text = vue.ref("");
6907
- const audioUrl = vue.ref(null);
6908
- const error = vue.ref(null);
6909
- let mediaStream = null;
6910
- let mediaRecorder = null;
6911
- let chunks = [];
6912
- let recognition = null;
6913
- let finalText = "";
6914
- function revokeAudioUrl() {
6915
- if (audioUrl.value) {
6916
- URL.revokeObjectURL(audioUrl.value);
6917
- audioUrl.value = null;
6918
- }
6919
- }
6920
- async function start() {
6921
- if (isRecording.value) return;
6922
- error.value = null;
6923
- text.value = "";
6924
- finalText = "";
6925
- chunks = [];
6926
- revokeAudioUrl();
6927
- if (typeof MediaRecorder === "undefined" || !navigator.mediaDevices?.getUserMedia) {
6928
- error.value = new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u97F3\u9891\u5F55\u5236");
6929
- return;
6930
- }
6931
- try {
6932
- mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
6933
- } catch (e) {
6934
- error.value = e instanceof Error ? e : new Error(String(e));
6935
- return;
6936
- }
6937
- mediaRecorder = new MediaRecorder(mediaStream);
6938
- mediaRecorder.ondataavailable = (e) => {
6939
- if (e.data && e.data.size > 0) chunks.push(e.data);
6940
- };
6941
- mediaRecorder.onstop = () => {
6942
- if (chunks.length > 0) {
6943
- const blob = new Blob(chunks, { type: mediaRecorder?.mimeType || "audio/webm" });
6944
- audioUrl.value = URL.createObjectURL(blob);
6945
- }
6946
- mediaStream?.getTracks().forEach((t) => t.stop());
6947
- mediaStream = null;
6948
- };
6949
- const SRCtor = getSpeechRecognitionCtor();
6950
- if (SRCtor) {
6951
- try {
6952
- recognition = new SRCtor();
6953
- recognition.continuous = true;
6954
- recognition.interimResults = true;
6955
- recognition.onresult = (event) => {
6956
- let interim = "";
6957
- for (let i = event.resultIndex; i < event.results.length; i++) {
6958
- const r = event.results[i];
6959
- if (r.isFinal) finalText += r[0].transcript;
6960
- else interim += r[0].transcript;
6961
- }
6962
- text.value = finalText + interim;
6963
- };
6964
- recognition.onerror = (ev) => {
6965
- error.value = new Error(`\u8BED\u97F3\u8BC6\u522B\u9519\u8BEF: ${ev?.error ?? "unknown"}`);
6966
- };
6967
- recognition.start();
6968
- } catch {
6969
- recognition = null;
6970
- }
6971
- }
6972
- mediaRecorder.start();
6973
- isRecording.value = true;
6974
- }
6975
- function stop() {
6976
- if (!isRecording.value) return;
6977
- isRecording.value = false;
6978
- try {
6979
- recognition?.stop();
6980
- } catch {
6981
- }
6982
- recognition = null;
6983
- try {
6984
- if (mediaRecorder && mediaRecorder.state !== "inactive") mediaRecorder.stop();
6985
- } catch {
6986
- }
6987
- }
6988
- if (vue.getCurrentInstance()) {
6989
- vue.onBeforeUnmount(() => {
6990
- stop();
6991
- revokeAudioUrl();
6992
- });
6993
- }
6994
- return { start, stop, isRecording, text, audioUrl, error };
6995
- }
6996
-
6997
7280
  class DetOmsError extends Error {
6998
7281
  constructor(message, code) {
6999
7282
  super(message);
@@ -7196,18 +7479,18 @@ ${fileParts.join("\n")}` });
7196
7479
  exports.FvAiMessage = _sfc_main$3;
7197
7480
  exports.FvAiMessages = _sfc_main$2;
7198
7481
  exports.FvAttachments = _sfc_main$5;
7199
- exports.FvBubble = _sfc_main$f;
7200
- exports.FvBubbleList = _sfc_main$e;
7482
+ exports.FvBubble = _sfc_main$g;
7483
+ exports.FvBubbleList = _sfc_main$f;
7201
7484
  exports.FvConversations = _sfc_main$4;
7202
7485
  exports.FvEditorSender = _sfc_main$b;
7203
7486
  exports.FvFilesCard = _sfc_main$6;
7204
- exports.FvMarkdown = _sfc_main$h;
7487
+ exports.FvMarkdown = _sfc_main$i;
7205
7488
  exports.FvMentionSender = _sfc_main$c;
7206
7489
  exports.FvPrompts = _sfc_main$7;
7207
7490
  exports.FvSender = _sfc_main$d;
7208
7491
  exports.FvThinking = _sfc_main$a;
7209
7492
  exports.FvThoughtChain = _sfc_main$9;
7210
- exports.FvTypewriter = _sfc_main$g;
7493
+ exports.FvTypewriter = _sfc_main$h;
7211
7494
  exports.FvWelcome = _sfc_main$8;
7212
7495
  exports.OpenAIAdapter = OpenAIAdapter;
7213
7496
  exports.XRequest = XRequest;