@seer-ai/seer-vue 0.1.0-beta.10 → 0.1.0-beta.11

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/README.md CHANGED
@@ -41,6 +41,10 @@ import { SeerChat } from '@seer-ai/seer-vue'
41
41
  60 秒;SenseVoice 转写结果只回填输入框,不会自动发送。标准 browser chat endpoint 会自动
42
42
  推导 transcription endpoint,也可显式配置;传 `voiceEnabled: false` 可关闭话筒。
43
43
 
44
+ 输入框旁的回形针按钮用来上传附件(下单模板 xlsx/xls/csv、图片、PDF),上传地址同样按 endpoint 推导成
45
+ `/v1/browser/files`,也可用 `uploadEndpoint` 显式配置;传 `uploadEnabled: false` 隐藏按钮。自定义 UI 用
46
+ `useSeerChat()` 的 `attach(file)` / `removeAttachment(fileId)` / `attachments`,`send(text)` 会带上待发送的附件。
47
+
44
48
  ## Logged-in customer (`orgOwner`)
45
49
 
46
50
  传入已登录用户的多发货 `orgOwner`,询价时 seer 直接用它当客户编码,不再追问。登录态会变时传函数;
package/dist/client.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface SeerClientOptions {
9
9
  endpoint: string;
10
10
  sessionEndpoint?: string;
11
11
  transcriptionEndpoint?: string;
12
+ /** 附件上传地址;不传时按 endpoint 推成同域的 /v1/browser/files,传空字符串关闭上传。 */
13
+ uploadEndpoint?: string;
12
14
  challengeTokenProvider?: () => Promise<string>;
13
15
  /** 已登录用户的多发货 orgOwner;报价时自动当作客户编码。登录态会变时传函数。 */
14
16
  orgOwner?: SeerOrgOwner;
@@ -22,6 +24,15 @@ export interface SeerChatRequest {
22
24
  stream?: boolean;
23
25
  conversationId?: string;
24
26
  metadata?: Record<string, unknown>;
27
+ /** 先 upload() 拿到的 fileId,随这条消息一起提交。 */
28
+ attachments?: string[];
29
+ }
30
+ export interface SeerUploadedFile {
31
+ fileId: string;
32
+ filename: string;
33
+ kind: 'spreadsheet' | 'image' | 'document' | string;
34
+ contentType: string;
35
+ size: number;
25
36
  }
26
37
  export interface SeerChatCallbacks {
27
38
  signal?: AbortSignal;
@@ -42,8 +53,10 @@ export interface SeerChatResult {
42
53
  }
43
54
  export interface SeerClient {
44
55
  transcriptionEnabled: boolean;
56
+ uploadEnabled: boolean;
45
57
  chat(request: SeerChatRequest, callbacks?: SeerChatCallbacks): Promise<SeerChatResult>;
46
58
  transcribe(audio: Blob, filename: string, callbacks?: SeerTranscriptionCallbacks): Promise<SeerTranscriptionResult>;
59
+ upload(file: Blob, filename: string, callbacks?: SeerTranscriptionCallbacks): Promise<SeerUploadedFile>;
47
60
  }
48
61
  export declare function createSeerClient(options: SeerClientOptions): SeerClient;
49
62
  export declare function resolveOrgOwner(orgOwner: SeerOrgOwner | undefined): string | undefined;
@@ -3,6 +3,8 @@ type __VLS_Props = {
3
3
  endpoint?: string;
4
4
  sessionEndpoint?: string;
5
5
  transcriptionEndpoint?: string;
6
+ uploadEndpoint?: string;
7
+ uploadEnabled?: boolean;
6
8
  orgOwner?: string;
7
9
  model?: string;
8
10
  stream?: boolean;
@@ -17,6 +19,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
17
19
  title: string;
18
20
  placeholder: string;
19
21
  voiceEnabled: boolean;
22
+ uploadEnabled: boolean;
20
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
24
  declare const _default: typeof __VLS_export;
22
25
  export default _default;
package/dist/index.css CHANGED
@@ -1,2 +1,2 @@
1
- .seer-chat[data-v-ccb67bdd]{color:#18212f;background:radial-gradient(circle at 18% 0,#476aff24,#0000 32%),linear-gradient(#fff 0%,#f7f9fd 100%);border:1px solid #7f8faa47;border-radius:28px;gap:18px;max-width:720px;min-height:640px;padding:0;display:grid;overflow:hidden;box-shadow:0 24px 70px #151f3429}.seer-chat__header[data-v-ccb67bdd]{color:#fff;background:linear-gradient(135deg,#141f34f5,#243f76f0),#172033;justify-content:space-between;align-items:flex-start;gap:16px;padding:22px 22px 18px;display:flex}.seer-chat__header h2[data-v-ccb67bdd]{letter-spacing:-.03em;margin:0;font-size:22px}.seer-chat__header p[data-v-ccb67bdd],.seer-chat__empty[data-v-ccb67bdd],.seer-chat__status[data-v-ccb67bdd]{color:#5f6f89;margin:4px 0 0;font-size:14px}.seer-chat__header p[data-v-ccb67bdd]{color:#ffffffb8;max-width:34ch}.seer-chat__messages[data-v-ccb67bdd]{align-content:start;gap:12px;min-height:330px;max-height:470px;padding:18px 18px 4px;display:grid;overflow-y:auto}.seer-chat__empty[data-v-ccb67bdd]{color:#66748a;text-align:center;background:#ffffff9e;border:1px dashed #7f8faa61;border-radius:22px;place-items:center;min-height:260px;padding:0 28px;display:grid}.seer-chat__message[data-v-ccb67bdd]{background:#ffffffe0;border:1px solid #7f8faa29;border-radius:18px 18px 18px 6px;gap:6px;max-width:min(86%,520px);padding:13px 15px;display:grid;box-shadow:0 12px 28px #151f3414}.seer-chat__message--user[data-v-ccb67bdd]{color:#fff;background:linear-gradient(135deg,#285cff,#1d45c9);border-color:#0000;border-radius:18px 18px 6px;justify-self:end;box-shadow:0 14px 30px #1f5eff3d}.seer-chat__role[data-v-ccb67bdd]{opacity:.75;font-size:12px;font-weight:700}.seer-chat__body[data-v-ccb67bdd]{flex-direction:column;display:flex}.seer-chat__line[data-v-ccb67bdd]{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.seer-chat__line[data-v-ccb67bdd]:not(:last-child){margin-bottom:5px}.seer-chat__line[data-v-ccb67bdd]:empty{height:7px}.seer-chat__strong[data-v-ccb67bdd]{font-weight:700}.seer-chat__price[data-v-ccb67bdd]{color:#e0331d;font-weight:600}.seer-chat__message--user .seer-chat__price[data-v-ccb67bdd]{color:#ffe2dc}.seer-chat__link[data-v-ccb67bdd]{color:#1f5eff;text-underline-offset:2px;overflow-wrap:anywhere;font-weight:600;text-decoration:underline}.seer-chat__link[data-v-ccb67bdd]:hover{color:#173fb3}.seer-chat__message--user .seer-chat__link[data-v-ccb67bdd]{color:#fff}.seer-chat__image-button[data-v-ccb67bdd]{cursor:zoom-in;background:0 0;border:0;padding:0;display:block}.seer-chat__image-button[data-v-ccb67bdd]:not(:disabled):hover{transform:none}.seer-chat__image[data-v-ccb67bdd]{border:1px solid #7f8faa38;border-radius:12px;max-width:100%;margin:6px 0;transition:filter .16s;display:block}.seer-chat__image-button:hover .seer-chat__image[data-v-ccb67bdd]{filter:brightness(.96)}.seer-chat__lightbox[data-v-ccb67bdd]{z-index:1000;cursor:zoom-out;background:#0a0f1ad1;place-items:center;padding:32px;animation:.14s seer-chat__fade-in-ccb67bdd;display:grid;position:fixed;inset:0}.seer-chat__lightbox-image[data-v-ccb67bdd]{object-fit:contain;border-radius:12px;max-width:min(92vw,1200px);max-height:90vh;box-shadow:0 30px 80px #00000080}.seer-chat__lightbox-close[data-v-ccb67bdd]{color:#fff;background:#ffffff24;border-radius:50%;place-items:center;width:40px;height:40px;font-size:26px;line-height:1;display:grid;position:fixed;top:18px;right:22px}.seer-chat__lightbox-close[data-v-ccb67bdd]:not(:disabled):hover{background:#ffffff3d}@keyframes seer-chat__fade-in-ccb67bdd{0%{opacity:0}to{opacity:1}}.seer-chat__error[data-v-ccb67bdd],.seer-chat__handoff[data-v-ccb67bdd]{border-radius:14px;margin:0 18px;padding:10px 12px}.seer-chat__error[data-v-ccb67bdd]{color:#8a1f11;background:#fff0ed}.seer-chat__handoff[data-v-ccb67bdd]{color:#704600;background:#fff7db}.seer-chat__form[data-v-ccb67bdd]{background:#fff;border:1px solid #7f8faa42;border-radius:22px;gap:11px;margin:0 18px 18px;padding:14px;display:grid;box-shadow:0 18px 38px #151f3417}.seer-chat__label[data-v-ccb67bdd]{font-size:13px;font-weight:700}.seer-chat textarea[data-v-ccb67bdd]{box-sizing:border-box;width:100%;min-height:92px;color:inherit;resize:vertical;background:#f8fafd;border:1px solid #0000;border-radius:16px;outline:none;padding:14px;line-height:1.55;transition:background .16s,border-color .16s,box-shadow .16s}.seer-chat button[data-v-ccb67bdd]{color:inherit;cursor:pointer;border:0;font-weight:700;transition:transform .16s,box-shadow .16s,opacity .16s}.seer-chat button[data-v-ccb67bdd]:not(:disabled):hover{transform:translateY(-1px)}.seer-chat button[data-v-ccb67bdd]:disabled{cursor:not-allowed;opacity:.5}.seer-chat button[data-v-ccb67bdd]:focus-visible,.seer-chat textarea[data-v-ccb67bdd]:focus-visible{border-color:#1f5eff6b;box-shadow:0 0 0 4px #1f5eff21}.seer-chat__clear-button[data-v-ccb67bdd]{color:#ffffffc7;background:0 0;border-radius:12px;flex:none;padding:8px 11px;font-size:12px;border:1px solid #ffffff2e!important}.seer-chat__clear-button[data-v-ccb67bdd]:not(:disabled):hover{color:#fff;box-shadow:none;background:#ffffff1a}.seer-chat__stop-button[data-v-ccb67bdd]{color:#6c3b12;background:#fff1df;border-radius:12px;padding:8px 11px;font-size:12px}.seer-chat__mic-button[data-v-ccb67bdd]{color:#285cff;background:#edf2ff;border-radius:50%;flex:none;place-items:center;width:40px;height:40px;padding:0;display:grid}.seer-chat__mic-button[data-v-ccb67bdd]:not(:disabled):hover{background:#dfe8ff}.seer-chat__mic-button--recording[data-v-ccb67bdd]{color:#fff;background:#d9342b;animation:1.4s ease-in-out infinite seer-chat__recording-pulse-ccb67bdd;box-shadow:0 0 0 5px #d9342b24}.seer-chat__mic-button--recording[data-v-ccb67bdd]:not(:disabled):hover{background:#bd271f}.seer-chat__stop-icon[data-v-ccb67bdd]{background:currentColor;border-radius:3px;width:12px;height:12px}@keyframes seer-chat__recording-pulse-ccb67bdd{50%{box-shadow:0 0 0 9px #d9342b14}}.seer-chat__send-button[data-v-ccb67bdd]{color:#fff;background:linear-gradient(135deg,#1f5eff,#173fb3);border-radius:14px;justify-content:center;align-items:center;gap:8px;min-width:86px;padding:10px 14px;display:inline-flex;box-shadow:0 12px 24px #1f5eff38}.seer-chat__send-button[data-v-ccb67bdd]:not(:disabled):hover{box-shadow:0 14px 28px #1f5eff47}.seer-chat__hint[data-v-ccb67bdd]{color:#7a879a;margin-right:auto;font-size:12px}.seer-chat__composer-footer[data-v-ccb67bdd]{justify-content:flex-end;align-items:center;gap:8px;display:flex}
1
+ .seer-chat[data-v-adaeb672]{color:#18212f;background:radial-gradient(circle at 18% 0,#476aff24,#0000 32%),linear-gradient(#fff 0%,#f7f9fd 100%);border:1px solid #7f8faa47;border-radius:28px;gap:18px;max-width:720px;min-height:640px;padding:0;display:grid;overflow:hidden;box-shadow:0 24px 70px #151f3429}.seer-chat__header[data-v-adaeb672]{color:#fff;background:linear-gradient(135deg,#141f34f5,#243f76f0),#172033;justify-content:space-between;align-items:flex-start;gap:16px;padding:22px 22px 18px;display:flex}.seer-chat__header h2[data-v-adaeb672]{letter-spacing:-.03em;margin:0;font-size:22px}.seer-chat__header p[data-v-adaeb672],.seer-chat__empty[data-v-adaeb672],.seer-chat__status[data-v-adaeb672]{color:#5f6f89;margin:4px 0 0;font-size:14px}.seer-chat__header p[data-v-adaeb672]{color:#ffffffb8;max-width:34ch}.seer-chat__messages[data-v-adaeb672]{align-content:start;gap:12px;min-height:330px;max-height:470px;padding:18px 18px 4px;display:grid;overflow-y:auto}.seer-chat__empty[data-v-adaeb672]{color:#66748a;text-align:center;background:#ffffff9e;border:1px dashed #7f8faa61;border-radius:22px;place-items:center;min-height:260px;padding:0 28px;display:grid}.seer-chat__message[data-v-adaeb672]{background:#ffffffe0;border:1px solid #7f8faa29;border-radius:18px 18px 18px 6px;gap:6px;max-width:min(86%,520px);padding:13px 15px;display:grid;box-shadow:0 12px 28px #151f3414}.seer-chat__message--user[data-v-adaeb672]{color:#fff;background:linear-gradient(135deg,#285cff,#1d45c9);border-color:#0000;border-radius:18px 18px 6px;justify-self:end;box-shadow:0 14px 30px #1f5eff3d}.seer-chat__role[data-v-adaeb672]{opacity:.75;font-size:12px;font-weight:700}.seer-chat__body[data-v-adaeb672]{flex-direction:column;display:flex}.seer-chat__line[data-v-adaeb672]{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.seer-chat__line[data-v-adaeb672]:not(:last-child){margin-bottom:5px}.seer-chat__line[data-v-adaeb672]:empty{height:7px}.seer-chat__strong[data-v-adaeb672]{font-weight:700}.seer-chat__price[data-v-adaeb672]{color:#e0331d;font-weight:600}.seer-chat__message--user .seer-chat__price[data-v-adaeb672]{color:#ffe2dc}.seer-chat__link[data-v-adaeb672]{color:#1f5eff;text-underline-offset:2px;overflow-wrap:anywhere;font-weight:600;text-decoration:underline}.seer-chat__link[data-v-adaeb672]:hover{color:#173fb3}.seer-chat__message--user .seer-chat__link[data-v-adaeb672]{color:#fff}.seer-chat__image-button[data-v-adaeb672]{cursor:zoom-in;background:0 0;border:0;padding:0;display:block}.seer-chat__image-button[data-v-adaeb672]:not(:disabled):hover{transform:none}.seer-chat__image[data-v-adaeb672]{border:1px solid #7f8faa38;border-radius:12px;max-width:100%;margin:6px 0;transition:filter .16s;display:block}.seer-chat__image-button:hover .seer-chat__image[data-v-adaeb672]{filter:brightness(.96)}.seer-chat__lightbox[data-v-adaeb672]{z-index:1000;cursor:zoom-out;background:#0a0f1ad1;place-items:center;padding:32px;animation:.14s seer-chat__fade-in-adaeb672;display:grid;position:fixed;inset:0}.seer-chat__lightbox-image[data-v-adaeb672]{object-fit:contain;border-radius:12px;max-width:min(92vw,1200px);max-height:90vh;box-shadow:0 30px 80px #00000080}.seer-chat__lightbox-close[data-v-adaeb672]{color:#fff;background:#ffffff24;border-radius:50%;place-items:center;width:40px;height:40px;font-size:26px;line-height:1;display:grid;position:fixed;top:18px;right:22px}.seer-chat__lightbox-close[data-v-adaeb672]:not(:disabled):hover{background:#ffffff3d}@keyframes seer-chat__fade-in-adaeb672{0%{opacity:0}to{opacity:1}}.seer-chat__error[data-v-adaeb672],.seer-chat__handoff[data-v-adaeb672]{border-radius:14px;margin:0 18px;padding:10px 12px}.seer-chat__error[data-v-adaeb672]{color:#8a1f11;background:#fff0ed}.seer-chat__handoff[data-v-adaeb672]{color:#704600;background:#fff7db}.seer-chat__form[data-v-adaeb672]{background:#fff;border:1px solid #7f8faa42;border-radius:22px;gap:11px;margin:0 18px 18px;padding:14px;display:grid;box-shadow:0 18px 38px #151f3417}.seer-chat__label[data-v-adaeb672]{font-size:13px;font-weight:700}.seer-chat textarea[data-v-adaeb672]{box-sizing:border-box;width:100%;min-height:92px;color:inherit;resize:vertical;background:#f8fafd;border:1px solid #0000;border-radius:16px;outline:none;padding:14px;line-height:1.55;transition:background .16s,border-color .16s,box-shadow .16s}.seer-chat button[data-v-adaeb672]{color:inherit;cursor:pointer;border:0;font-weight:700;transition:transform .16s,box-shadow .16s,opacity .16s}.seer-chat button[data-v-adaeb672]:not(:disabled):hover{transform:translateY(-1px)}.seer-chat button[data-v-adaeb672]:disabled{cursor:not-allowed;opacity:.5}.seer-chat button[data-v-adaeb672]:focus-visible,.seer-chat textarea[data-v-adaeb672]:focus-visible{border-color:#1f5eff6b;box-shadow:0 0 0 4px #1f5eff21}.seer-chat__clear-button[data-v-adaeb672]{color:#ffffffc7;background:0 0;border-radius:12px;flex:none;padding:8px 11px;font-size:12px;border:1px solid #ffffff2e!important}.seer-chat__clear-button[data-v-adaeb672]:not(:disabled):hover{color:#fff;box-shadow:none;background:#ffffff1a}.seer-chat__stop-button[data-v-adaeb672]{color:#6c3b12;background:#fff1df;border-radius:12px;padding:8px 11px;font-size:12px}.seer-chat__mic-button[data-v-adaeb672],.seer-chat__attach-button[data-v-adaeb672]{color:#285cff;background:#edf2ff;border-radius:50%;flex:none;place-items:center;width:40px;height:40px;padding:0;display:grid}.seer-chat__mic-button[data-v-adaeb672]:not(:disabled):hover,.seer-chat__attach-button[data-v-adaeb672]:not(:disabled):hover{background:#dfe8ff}.seer-chat__file-input[data-v-adaeb672]{display:none}.seer-chat__attachments[data-v-adaeb672]{flex-wrap:wrap;gap:6px;margin:0 0 8px;padding:0;list-style:none;display:flex}.seer-chat__attachment[data-v-adaeb672]{color:#1f2a44;background:#edf2ff;border-radius:999px;align-items:center;gap:4px;max-width:100%;padding:4px 6px 4px 10px;font-size:13px;display:inline-flex}.seer-chat__attachment-name[data-v-adaeb672]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.seer-chat__attachment-remove[data-v-adaeb672]{color:#4b5878;background:0 0;border-radius:50%;width:20px;height:20px;padding:0;line-height:1}.seer-chat__attachment-remove[data-v-adaeb672]:hover{background:#dfe8ff}.seer-chat__mic-button--recording[data-v-adaeb672]{color:#fff;background:#d9342b;animation:1.4s ease-in-out infinite seer-chat__recording-pulse-adaeb672;box-shadow:0 0 0 5px #d9342b24}.seer-chat__mic-button--recording[data-v-adaeb672]:not(:disabled):hover{background:#bd271f}.seer-chat__stop-icon[data-v-adaeb672]{background:currentColor;border-radius:3px;width:12px;height:12px}@keyframes seer-chat__recording-pulse-adaeb672{50%{box-shadow:0 0 0 9px #d9342b14}}.seer-chat__send-button[data-v-adaeb672]{color:#fff;background:linear-gradient(135deg,#1f5eff,#173fb3);border-radius:14px;justify-content:center;align-items:center;gap:8px;min-width:86px;padding:10px 14px;display:inline-flex;box-shadow:0 12px 24px #1f5eff38}.seer-chat__send-button[data-v-adaeb672]:not(:disabled):hover{box-shadow:0 14px 28px #1f5eff47}.seer-chat__hint[data-v-adaeb672]{color:#7a879a;margin-right:auto;font-size:12px}.seer-chat__composer-footer[data-v-adaeb672]{justify-content:flex-end;align-items:center;gap:8px;display:flex}
2
2
  /*$vite$:1*/
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@ export { linkAnchorLabel, toContentLines, toContentSegments, type ContentOptions
3
3
  export { SeerChatPlugin } from './plugin';
4
4
  export { useSeerChat } from './useSeerChat';
5
5
  export { default as SeerChat } from './components/SeerChat.vue';
6
- export type { SeerChatCallbacks, SeerChatMessage, SeerChatRequest, SeerChatResult, SeerChatRole, SeerClient, SeerClientOptions, SeerTranscriptionCallbacks, SeerTranscriptionResult, } from './client';
6
+ export type { SeerChatCallbacks, SeerChatMessage, SeerChatRequest, SeerChatResult, SeerChatRole, SeerClient, SeerClientOptions, SeerTranscriptionCallbacks, SeerTranscriptionResult, SeerUploadedFile, } from './client';
7
7
  export type { SeerChatPluginOptions } from './plugin';
8
8
  export type { UseSeerChatOptions } from './useSeerChat';
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { Fragment as e, computed as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, defineComponent as a, hasInjectionContext as o, inject as s, normalizeClass as c, onBeforeUnmount as l, openBlock as u, ref as d, renderList as f, toDisplayString as p, unref as m, vModelText as h, withDirectives as g, withKeys as _, withModifiers as v } from "vue";
2
2
  // src/client.ts
3
3
  function y(e) {
4
- let t = e.fetcher ?? fetch, n = e.model ?? "seer", r = x(e), i = e.now ?? (() => Math.floor(Date.now() / 1e3)), a, o = 0, s;
4
+ let t = e.fetcher ?? fetch, n = e.model ?? "seer", r = x(e.endpoint, e.transcriptionEndpoint, "/v1/browser/audio/transcriptions"), i = x(e.endpoint, e.uploadEndpoint, "/v1/browser/files"), a = e.now ?? (() => Math.floor(Date.now() / 1e3)), o, s = 0, c;
5
5
  return {
6
6
  transcriptionEnabled: !!r,
7
+ uploadEnabled: !!i,
7
8
  async chat(r, i = {}) {
8
- let a = r.stream ?? !1, o = await c(), s = { "Content-Type": "application/json" };
9
+ let a = r.stream ?? !1, o = await l(), s = { "Content-Type": "application/json" };
9
10
  o && (s.Authorization = `Bearer ${o}`);
10
- let l = await t(e.endpoint, {
11
+ let c = await t(e.endpoint, {
11
12
  method: "POST",
12
13
  headers: s,
13
14
  signal: i.signal,
@@ -16,43 +17,67 @@ function y(e) {
16
17
  messages: r.messages,
17
18
  stream: a,
18
19
  conversation_id: r.conversationId,
19
- metadata: r.metadata
20
+ metadata: r.metadata,
21
+ ...r.attachments?.length ? { attachments: r.attachments } : {}
20
22
  })
21
23
  });
22
- if (!l.ok) throw Error(await T(l));
23
- return a ? C(l, i.onDelta) : S(await l.json());
24
+ if (!c.ok) throw Error(await T(c));
25
+ return a ? C(c, i.onDelta) : S(await c.json());
24
26
  },
25
27
  async transcribe(e, n, i = {}) {
26
28
  if (!r) throw Error("Seer audio transcription endpoint is not configured");
27
- let a = await c(), o = {};
29
+ let a = await l(), o = {};
28
30
  a && (o.Authorization = `Bearer ${a}`);
29
31
  let s = new FormData();
30
32
  s.append("file", e, n);
31
- let l = await t(r, {
33
+ let c = await t(r, {
32
34
  method: "POST",
33
35
  headers: o,
34
36
  signal: i.signal,
35
37
  body: s
36
38
  });
37
- if (!l.ok) throw Error(await T(l));
38
- let u = await l.json(), d = typeof u.text == "string" ? u.text.trim() : "";
39
+ if (!c.ok) throw Error(await T(c));
40
+ let u = await c.json(), d = typeof u.text == "string" ? u.text.trim() : "";
39
41
  if (!d) throw Error("Seer audio transcription returned empty text");
40
42
  return { text: d };
43
+ },
44
+ async upload(e, n, r = {}) {
45
+ if (!i) throw Error("Seer file upload endpoint is not configured");
46
+ let a = await l(), o = {};
47
+ a && (o.Authorization = `Bearer ${a}`);
48
+ let s = new FormData();
49
+ s.append("file", e, n);
50
+ let c = await t(i, {
51
+ method: "POST",
52
+ headers: o,
53
+ signal: r.signal,
54
+ body: s
55
+ });
56
+ if (!c.ok) throw Error(await T(c));
57
+ let u = await c.json();
58
+ if (!u.file_id) throw Error("Seer file upload returned no file_id");
59
+ return {
60
+ fileId: u.file_id,
61
+ filename: u.filename ?? n,
62
+ kind: u.kind ?? "document",
63
+ contentType: u.content_type ?? "",
64
+ size: u.size ?? e.size
65
+ };
41
66
  }
42
67
  };
43
- async function c() {
68
+ async function l() {
44
69
  if (!e.sessionEndpoint) return;
45
- let n = i(), r = b(e.orgOwner);
46
- if (a && s === r && o - 30 > n) return a;
47
- let c = await t(e.sessionEndpoint, {
70
+ let n = a(), r = b(e.orgOwner);
71
+ if (o && c === r && s - 30 > n) return o;
72
+ let i = await t(e.sessionEndpoint, {
48
73
  method: "GET",
49
- headers: await l(r)
74
+ headers: await u(r)
50
75
  });
51
- if (!c.ok) throw Error(await T(c));
52
- let u = await c.json();
53
- return a = u.token, o = u.expires_at, s = r, a;
76
+ if (!i.ok) throw Error(await T(i));
77
+ let l = await i.json();
78
+ return o = l.token, s = l.expires_at, c = r, o;
54
79
  }
55
- async function l(t) {
80
+ async function u(t) {
56
81
  let n = {};
57
82
  return e.challengeTokenProvider && (n["X-Seer-Challenge-Token"] = await e.challengeTokenProvider()), t && (n["X-Seer-Org-Owner"] = t), n;
58
83
  }
@@ -60,9 +85,9 @@ function y(e) {
60
85
  function b(e) {
61
86
  return (typeof e == "function" ? e() : e)?.trim() || void 0;
62
87
  }
63
- function x(e) {
64
- if (e.transcriptionEndpoint !== void 0) return e.transcriptionEndpoint || void 0;
65
- if (e.endpoint.endsWith("/v1/browser/chat/completions")) return `${e.endpoint.slice(0, -28)}/v1/browser/audio/transcriptions`;
88
+ function x(e, t, n) {
89
+ if (t !== void 0) return t || void 0;
90
+ if (e.endsWith("/v1/browser/chat/completions")) return `${e.slice(0, -28)}${n}`;
66
91
  }
67
92
  function S(e) {
68
93
  let t = e.choices?.[0];
@@ -200,33 +225,49 @@ function B(e = {}) {
200
225
  endpoint: r.endpoint,
201
226
  sessionEndpoint: r.sessionEndpoint,
202
227
  transcriptionEndpoint: r.transcriptionEndpoint,
228
+ uploadEndpoint: r.uploadEndpoint,
203
229
  challengeTokenProvider: r.challengeTokenProvider,
204
230
  orgOwner: () => b(e.orgOwner) ?? b(t?.orgOwner),
205
231
  model: r.model,
206
232
  fetcher: r.fetcher
207
- }), a = d([]), c = d(!1), l = d(null), u = d(), f = d([]), p = d(!1), m = r.voiceEnabled !== !1 && i.transcriptionEnabled, h = r.maxRecordingSeconds ?? 60, g = null;
208
- async function _(e) {
209
- let t = e.trim();
210
- if (!t || c.value) return;
211
- let n = {
233
+ }), a = d([]), c = d(!1), l = d(null), u = d(), f = d([]), p = d(!1), m = r.voiceEnabled !== !1 && i.transcriptionEnabled, h = r.uploadEnabled !== !1 && i.uploadEnabled, g = r.maxRecordingSeconds ?? 60, _ = d([]), v = d(!1), x = null;
234
+ async function S(e) {
235
+ v.value = !0, l.value = null;
236
+ try {
237
+ let t = await i.upload(e, e.name);
238
+ _.value = [..._.value, t];
239
+ } catch (e) {
240
+ l.value = e instanceof Error ? e.message : String(e);
241
+ } finally {
242
+ v.value = !1;
243
+ }
244
+ }
245
+ function C(e) {
246
+ _.value = _.value.filter((t) => t.fileId !== e);
247
+ }
248
+ async function w(e) {
249
+ let t = e.trim(), n = _.value;
250
+ if (!t && n.length === 0 || c.value || v.value) return;
251
+ let o = {
212
252
  role: "user",
213
- content: t
253
+ content: [t, n.map((e) => `[附件:${e.filename}]`).join("\n")].filter(Boolean).join("\n")
214
254
  };
215
- a.value = [...a.value, n], c.value = !0, l.value = null, g = new AbortController();
216
- let o = {
255
+ _.value = [], a.value = [...a.value, o], c.value = !0, l.value = null, x = new AbortController();
256
+ let s = {
217
257
  role: "assistant",
218
258
  content: ""
219
259
  };
220
- r.stream && (a.value = [...a.value, o]);
260
+ r.stream && (a.value = [...a.value, s]);
221
261
  try {
222
262
  let e = await i.chat({
223
263
  messages: a.value.filter((e) => e.content),
224
264
  stream: r.stream ?? !1,
225
- conversationId: u.value
265
+ conversationId: u.value,
266
+ attachments: n.map((e) => e.fileId)
226
267
  }, {
227
- signal: g.signal,
268
+ signal: x.signal,
228
269
  onDelta(e) {
229
- o.content += e, a.value = [...a.value];
270
+ s.content += e, a.value = [...a.value];
230
271
  }
231
272
  });
232
273
  r.stream || (a.value = [...a.value, {
@@ -236,14 +277,14 @@ function B(e = {}) {
236
277
  } catch (e) {
237
278
  e.name !== "AbortError" && (l.value = e instanceof Error ? e.message : String(e));
238
279
  } finally {
239
- c.value = !1, g = null;
280
+ c.value = !1, x = null;
240
281
  }
241
282
  }
242
- function v() {
243
- a.value = [], l.value = null, u.value = void 0, f.value = [], p.value = !1;
283
+ function T() {
284
+ a.value = [], _.value = [], l.value = null, u.value = void 0, f.value = [], p.value = !1;
244
285
  }
245
- function x() {
246
- g?.abort();
286
+ function E() {
287
+ x?.abort();
247
288
  }
248
289
  return {
249
290
  messages: a,
@@ -253,61 +294,70 @@ function B(e = {}) {
253
294
  citations: f,
254
295
  handoffRequested: p,
255
296
  transcriptionEnabled: m,
256
- maxRecordingSeconds: h,
297
+ maxRecordingSeconds: g,
257
298
  transcribe: i.transcribe,
258
- send: _,
259
- reset: v,
260
- abort: x
299
+ uploadEnabled: h,
300
+ attachments: _,
301
+ uploading: v,
302
+ attach: S,
303
+ removeAttachment: C,
304
+ send: w,
305
+ reset: T,
306
+ abort: E
261
307
  };
262
308
  }
263
309
  // src/components/SeerChat.vue?vue&type=script&setup=true&lang.ts
264
- var V = {
310
+ var ee = {
265
311
  class: "seer-chat",
266
312
  "aria-label": "多发货 AI 客服聊天"
267
- }, H = { class: "seer-chat__header" }, U = {
313
+ }, te = { class: "seer-chat__header" }, ne = {
268
314
  class: "seer-chat__messages",
269
315
  "aria-live": "polite"
270
- }, W = {
316
+ }, re = {
271
317
  key: 0,
272
318
  class: "seer-chat__empty"
273
- }, G = { class: "seer-chat__role" }, K = { class: "seer-chat__body" }, q = ["href"], J = ["aria-label", "onClick"], Y = ["src", "alt"], X = {
319
+ }, V = { class: "seer-chat__role" }, H = { class: "seer-chat__body" }, U = ["href"], W = ["aria-label", "onClick"], G = ["src", "alt"], K = {
274
320
  key: 2,
275
321
  class: "seer-chat__strong"
276
- }, ee = {
322
+ }, q = {
277
323
  key: 3,
278
324
  class: "seer-chat__price"
279
- }, te = { key: 4 }, ne = {
325
+ }, J = { key: 4 }, Y = {
280
326
  key: 0,
281
327
  class: "seer-chat__status"
282
- }, re = {
328
+ }, ie = {
283
329
  key: 1,
284
330
  class: "seer-chat__error",
285
331
  role: "alert"
286
- }, ie = {
332
+ }, ae = {
287
333
  key: 2,
288
334
  class: "seer-chat__handoff"
289
- }, ae = [
335
+ }, oe = {
336
+ key: 0,
337
+ class: "seer-chat__attachments",
338
+ "aria-label": "待发送的附件"
339
+ }, se = { class: "seer-chat__attachment-name" }, ce = ["aria-label", "onClick"], le = [
290
340
  "placeholder",
291
341
  "disabled",
292
342
  "onKeydown"
293
- ], oe = { class: "seer-chat__composer-footer" }, se = {
343
+ ], ue = { class: "seer-chat__composer-footer" }, de = {
294
344
  class: "seer-chat__hint",
295
345
  "aria-live": "polite"
296
- }, ce = [
346
+ }, fe = ["disabled"], pe = [
297
347
  "disabled",
298
348
  "aria-label",
299
349
  "title"
300
- ], le = {
350
+ ], me = {
301
351
  key: 0,
302
352
  class: "seer-chat__stop-icon",
303
353
  "aria-hidden": "true"
304
- }, ue = {
354
+ }, he = {
305
355
  key: 1,
306
356
  viewBox: "0 0 24 24",
307
357
  width: "20",
308
358
  height: "20",
309
359
  "aria-hidden": "true"
310
- }, de = ["disabled"], fe = ["aria-label"], pe = ["src", "alt"], Z = /*#__PURE__*/ ((e, t) => {
360
+ }, ge = ["disabled"], X = ["aria-label"], _e = ["src", "alt"], ve = ".xlsx,.xls,.csv,.png,.jpg,.jpeg,.webp,.pdf", Z = /*#__PURE__*/ ((e, t) => {
311
361
  let n = e.__vccOpts || e;
312
362
  for (let [e, r] of t) n[e] = r;
313
363
  return n;
@@ -317,6 +367,11 @@ var V = {
317
367
  endpoint: {},
318
368
  sessionEndpoint: {},
319
369
  transcriptionEndpoint: {},
370
+ uploadEndpoint: {},
371
+ uploadEnabled: {
372
+ type: Boolean,
373
+ default: void 0
374
+ },
320
375
  orgOwner: {},
321
376
  model: {},
322
377
  stream: {
@@ -337,77 +392,85 @@ var V = {
337
392
  endpoint: o.endpoint,
338
393
  sessionEndpoint: o.sessionEndpoint,
339
394
  transcriptionEndpoint: o.transcriptionEndpoint,
395
+ uploadEndpoint: o.uploadEndpoint,
396
+ uploadEnabled: o.uploadEnabled,
340
397
  orgOwner: () => o.orgOwner,
341
398
  model: o.model,
342
399
  stream: o.stream,
343
400
  fetcher: o.fetcher,
344
401
  voiceEnabled: o.voiceEnabled,
345
402
  maxRecordingSeconds: o.maxRecordingSeconds
346
- }), C = t(() => s.value.trim().length > 0 && !S.loading.value && !y.value && !b.value), w = t(() => S.transcriptionEnabled && typeof navigator < "u" && typeof navigator.mediaDevices?.getUserMedia == "function" && typeof MediaRecorder < "u"), T = null, E = null, D = [], O, k = !1, A = d(null);
347
- function j(e) {
348
- e.key === "Escape" && N();
403
+ }), C = d(null), w = t(() => (s.value.trim().length > 0 || S.attachments.value.length > 0) && !S.loading.value && !S.uploading.value && !y.value && !b.value);
404
+ async function T(e) {
405
+ let t = e.target, n = Array.from(t.files ?? []);
406
+ t.value = "";
407
+ for (let e of n) await S.attach(e);
408
+ }
409
+ let E = t(() => S.transcriptionEnabled && typeof navigator < "u" && typeof navigator.mediaDevices?.getUserMedia == "function" && typeof MediaRecorder < "u"), D = null, O = null, k = [], A, j = !1, M = d(null);
410
+ function N(e) {
411
+ e.key === "Escape" && F();
349
412
  }
350
- function M(e, t) {
351
- A.value = {
413
+ function P(e, t) {
414
+ M.value = {
352
415
  src: e,
353
416
  alt: t
354
- }, window.addEventListener("keydown", j);
417
+ }, window.addEventListener("keydown", N);
355
418
  }
356
- function N() {
357
- A.value = null, window.removeEventListener("keydown", j);
419
+ function F() {
420
+ M.value = null, window.removeEventListener("keydown", N);
358
421
  }
359
422
  l(() => {
360
- k = !0, window.removeEventListener("keydown", j), Q(), T && T.state !== "inactive" && (T.ondataavailable = null, T.onstop = null, T.onerror = null, T.stop()), $();
423
+ j = !0, window.removeEventListener("keydown", N), Q(), D && D.state !== "inactive" && (D.ondataavailable = null, D.onstop = null, D.onerror = null, D.stop()), $();
361
424
  });
362
- async function P() {
363
- if (!C.value) return;
425
+ async function I() {
426
+ if (!w.value) return;
364
427
  let e = s.value;
365
428
  s.value = "", await S.send(e);
366
429
  }
367
- async function F() {
368
- y.value ? R() : await I();
430
+ async function R() {
431
+ y.value ? Z() : await z();
369
432
  }
370
- async function I() {
371
- if (!(!w.value || b.value || S.loading.value)) {
433
+ async function z() {
434
+ if (!(!E.value || b.value || S.loading.value)) {
372
435
  x.value = null;
373
436
  try {
374
- if (E = await navigator.mediaDevices.getUserMedia({
437
+ if (O = await navigator.mediaDevices.getUserMedia({
375
438
  audio: {
376
439
  echoCancellation: !0,
377
440
  noiseSuppression: !0
378
441
  },
379
442
  video: !1
380
- }), k) {
443
+ }), j) {
381
444
  $();
382
445
  return;
383
446
  }
384
- let e = Z();
385
- T = e ? new MediaRecorder(E, {
447
+ let e = be();
448
+ D = e ? new MediaRecorder(O, {
386
449
  mimeType: e,
387
450
  audioBitsPerSecond: 64e3
388
- }) : new MediaRecorder(E), D = [], T.ondataavailable = (e) => {
389
- e.data.size > 0 && D.push(e.data);
390
- }, T.onstop = () => {
391
- z(T?.mimeType || e || "audio/webm");
392
- }, T.onerror = () => {
451
+ }) : new MediaRecorder(O), k = [], D.ondataavailable = (e) => {
452
+ e.data.size > 0 && k.push(e.data);
453
+ }, D.onstop = () => {
454
+ ye(D?.mimeType || e || "audio/webm");
455
+ }, D.onerror = () => {
393
456
  x.value = "录音失败,请重试或用文字输入。", y.value = !1, b.value = !1, Q(), $();
394
- }, T.start(), y.value = !0, O = setTimeout(R, Math.max(1, S.maxRecordingSeconds) * 1e3);
457
+ }, D.start(), y.value = !0, A = setTimeout(Z, Math.max(1, S.maxRecordingSeconds) * 1e3);
395
458
  } catch (e) {
396
- x.value = he(e), $();
459
+ x.value = Se(e), $();
397
460
  }
398
461
  }
399
462
  }
400
- function R() {
401
- T && T.state !== "inactive" && (y.value = !1, b.value = !0, Q(), T.stop());
463
+ function Z() {
464
+ D && D.state !== "inactive" && (y.value = !1, b.value = !0, Q(), D.stop());
402
465
  }
403
- async function z(e) {
466
+ async function ye(e) {
404
467
  Q(), $();
405
- let t = D;
406
- if (D = [], T = null, k) return;
468
+ let t = k;
469
+ if (k = [], D = null, j) return;
407
470
  let n = new Blob(t, { type: e });
408
471
  if (n.size === 0) x.value = "没有录到声音,请重试。", b.value = !1;
409
472
  else try {
410
- let t = await S.transcribe(n, me(e));
473
+ let t = await S.transcribe(n, xe(e));
411
474
  s.value = s.value.trim() ? `${s.value.trim()} ${t.text}` : t.text;
412
475
  } catch {
413
476
  x.value = "语音识别失败,请重试或用文字输入。";
@@ -415,7 +478,7 @@ var V = {
415
478
  b.value = !1;
416
479
  }
417
480
  }
418
- function Z() {
481
+ function be() {
419
482
  if (typeof MediaRecorder.isTypeSupported == "function") return [
420
483
  "audio/webm;codecs=opus",
421
484
  "audio/mp4",
@@ -423,32 +486,32 @@ var V = {
423
486
  "audio/webm"
424
487
  ].find((e) => MediaRecorder.isTypeSupported(e));
425
488
  }
426
- function me(e) {
489
+ function xe(e) {
427
490
  return `recording.${{
428
491
  "audio/mp4": "m4a",
429
492
  "audio/ogg": "ogg",
430
493
  "audio/webm": "webm"
431
494
  }[e.split(";", 1)[0].toLowerCase()] || "webm"}`;
432
495
  }
433
- function he(e) {
496
+ function Se(e) {
434
497
  return e instanceof DOMException && e.name === "NotAllowedError" ? "请允许浏览器使用麦克风。" : e instanceof DOMException && e.name === "NotFoundError" ? "未检测到可用的麦克风。" : "无法启动麦克风,请重试或用文字输入。";
435
498
  }
436
499
  function Q() {
437
- O !== void 0 && (clearTimeout(O), O = void 0);
500
+ A !== void 0 && (clearTimeout(A), A = void 0);
438
501
  }
439
502
  function $() {
440
- E?.getTracks().forEach((e) => e.stop()), E = null;
503
+ O?.getTracks().forEach((e) => e.stop()), O = null;
441
504
  }
442
- return (t, o) => (u(), r("section", V, [
443
- i("header", H, [i("div", null, [i("h2", null, p(a.title), 1), o[3] ||= i("p", null, "可咨询渠道报价、时效、下单资料、入仓和售后问题。", -1)]), i("button", {
505
+ return (t, o) => (u(), r("section", ee, [
506
+ i("header", te, [i("div", null, [i("h2", null, p(a.title), 1), o[4] ||= i("p", null, "可咨询渠道报价、时效、下单资料、入仓和售后问题。", -1)]), i("button", {
444
507
  type: "button",
445
508
  class: "seer-chat__clear-button",
446
509
  onClick: o[0] ||= (e) => m(S).reset()
447
510
  }, " 清空对话 ")]),
448
- i("div", U, [m(S).messages.value.length === 0 ? (u(), r("p", W, " 直接输入问题即可,复杂查单或赔付问题会提示转人工。 ")) : n("", !0), (u(!0), r(e, null, f(m(S).messages.value, (t, n) => (u(), r("article", {
511
+ i("div", ne, [m(S).messages.value.length === 0 ? (u(), r("p", re, " 直接输入问题即可,复杂查单或赔付问题会提示转人工。 ")) : n("", !0), (u(!0), r(e, null, f(m(S).messages.value, (t, n) => (u(), r("article", {
449
512
  key: `${t.role}-${n}`,
450
513
  class: c(["seer-chat__message", `seer-chat__message--${t.role}`])
451
- }, [i("span", G, p(t.role === "user" ? "你" : "AI 客服"), 1), i("div", K, [(u(!0), r(e, null, f(m(L)(t.content, { complete: !(m(S).loading.value && n === m(S).messages.value.length - 1) }), (t, n) => (u(), r("p", {
514
+ }, [i("span", V, p(t.role === "user" ? "你" : "AI 客服"), 1), i("div", H, [(u(!0), r(e, null, f(m(L)(t.content, { complete: !(m(S).loading.value && n === m(S).messages.value.length - 1) }), (t, n) => (u(), r("p", {
452
515
  key: n,
453
516
  class: "seer-chat__line"
454
517
  }, [(u(!0), r(e, null, f(t, (t, n) => (u(), r(e, { key: n }, [t.kind === "link" ? (u(), r("a", {
@@ -457,82 +520,115 @@ var V = {
457
520
  href: t.href,
458
521
  target: "_blank",
459
522
  rel: "noopener noreferrer"
460
- }, p(t.label), 9, q)) : t.kind === "image" ? (u(), r("button", {
523
+ }, p(t.label), 9, U)) : t.kind === "image" ? (u(), r("button", {
461
524
  key: 1,
462
525
  type: "button",
463
526
  class: "seer-chat__image-button",
464
527
  "aria-label": `放大查看${t.alt}`,
465
- onClick: (e) => M(t.href, t.alt)
528
+ onClick: (e) => P(t.href, t.alt)
466
529
  }, [i("img", {
467
530
  class: "seer-chat__image",
468
531
  src: t.href,
469
532
  alt: t.alt,
470
533
  loading: "lazy"
471
- }, null, 8, Y)], 8, J)) : t.kind === "strong" ? (u(), r("strong", X, p(t.value), 1)) : t.kind === "price" ? (u(), r("span", ee, p(t.value), 1)) : (u(), r("span", te, p(t.value), 1))], 64))), 128))]))), 128))])], 2))), 128))]),
472
- m(S).loading.value ? (u(), r("p", ne, "正在回复...")) : n("", !0),
473
- m(S).error.value || x.value ? (u(), r("p", re, p(x.value || m(S).error.value), 1)) : n("", !0),
474
- m(S).handoffRequested.value ? (u(), r("p", ie, " 建议转人工:请留下联系方式或等待客服接入。 ")) : n("", !0),
534
+ }, null, 8, G)], 8, W)) : t.kind === "strong" ? (u(), r("strong", K, p(t.value), 1)) : t.kind === "price" ? (u(), r("span", q, p(t.value), 1)) : (u(), r("span", J, p(t.value), 1))], 64))), 128))]))), 128))])], 2))), 128))]),
535
+ m(S).loading.value ? (u(), r("p", Y, "正在回复...")) : n("", !0),
536
+ m(S).error.value || x.value ? (u(), r("p", ie, p(x.value || m(S).error.value), 1)) : n("", !0),
537
+ m(S).handoffRequested.value ? (u(), r("p", ae, " 建议转人工:请留下联系方式或等待客服接入。 ")) : n("", !0),
475
538
  i("form", {
476
539
  class: "seer-chat__form",
477
- onSubmit: v(P, ["prevent"])
540
+ onSubmit: v(I, ["prevent"])
478
541
  }, [
479
- o[6] ||= i("label", {
542
+ o[8] ||= i("label", {
480
543
  class: "seer-chat__label",
481
544
  for: "seer-chat-input"
482
545
  }, "问题", -1),
546
+ m(S).attachments.value.length > 0 ? (u(), r("ul", oe, [(u(!0), r(e, null, f(m(S).attachments.value, (e) => (u(), r("li", {
547
+ key: e.fileId,
548
+ class: "seer-chat__attachment"
549
+ }, [i("span", se, p(e.filename), 1), i("button", {
550
+ type: "button",
551
+ class: "seer-chat__attachment-remove",
552
+ "aria-label": `移除附件${e.filename}`,
553
+ onClick: (t) => m(S).removeAttachment(e.fileId)
554
+ }, " × ", 8, ce)]))), 128))])) : n("", !0),
483
555
  g(i("textarea", {
484
556
  id: "seer-chat-input",
485
557
  "onUpdate:modelValue": o[1] ||= (e) => s.value = e,
486
558
  placeholder: a.placeholder,
487
559
  disabled: m(S).loading.value || b.value,
488
560
  rows: "3",
489
- onKeydown: _(v(P, ["exact", "prevent"]), ["enter"])
490
- }, null, 40, ae), [[h, s.value]]),
491
- i("div", oe, [
492
- i("span", se, p(y.value ? `正在录音,最长 ${m(S).maxRecordingSeconds} 秒` : b.value ? "正在识别语音..." : "Enter 发送 · Shift+Enter 换行"), 1),
493
- w.value ? (u(), r("button", {
494
- key: 0,
561
+ onKeydown: _(v(I, ["exact", "prevent"]), ["enter"])
562
+ }, null, 40, le), [[h, s.value]]),
563
+ i("div", ue, [
564
+ i("span", de, p(y.value ? `正在录音,最长 ${m(S).maxRecordingSeconds} 秒` : b.value ? "正在识别语音..." : m(S).uploading.value ? "正在上传附件..." : "Enter 发送 · Shift+Enter 换行"), 1),
565
+ m(S).uploadEnabled ? (u(), r(e, { key: 0 }, [i("input", {
566
+ ref_key: "fileInput",
567
+ ref: C,
568
+ class: "seer-chat__file-input",
569
+ type: "file",
570
+ multiple: "",
571
+ accept: ve,
572
+ onChange: T
573
+ }, null, 544), i("button", {
574
+ type: "button",
575
+ class: "seer-chat__attach-button",
576
+ disabled: m(S).loading.value || m(S).uploading.value,
577
+ "aria-label": "上传附件",
578
+ title: "上传下单模板、实物图片等",
579
+ onClick: o[2] ||= (e) => C.value?.click()
580
+ }, [...o[5] ||= [i("svg", {
581
+ viewBox: "0 0 24 24",
582
+ width: "20",
583
+ height: "20",
584
+ "aria-hidden": "true"
585
+ }, [i("path", {
586
+ fill: "currentColor",
587
+ d: "M16.5 6.5v10a4.5 4.5 0 0 1-9 0V5a3 3 0 0 1 6 0v10.5a1.5 1.5 0 0 1-3 0V6.5H9v9a3 3 0 0 0 6 0V5a4.5 4.5 0 0 0-9 0v11.5a6 6 0 0 0 12 0v-10h-1.5Z"
588
+ })], -1)]], 8, fe)], 64)) : n("", !0),
589
+ E.value ? (u(), r("button", {
590
+ key: 1,
495
591
  type: "button",
496
592
  class: c(["seer-chat__mic-button", { "seer-chat__mic-button--recording": y.value }]),
497
593
  disabled: b.value || m(S).loading.value && !y.value,
498
594
  "aria-label": y.value ? "停止录音" : "开始语音输入",
499
595
  title: y.value ? "停止录音" : "语音输入",
500
- onClick: F
501
- }, [y.value ? (u(), r("span", le)) : (u(), r("svg", ue, [...o[4] ||= [i("path", {
596
+ onClick: R
597
+ }, [y.value ? (u(), r("span", me)) : (u(), r("svg", he, [...o[6] ||= [i("path", {
502
598
  fill: "currentColor",
503
599
  d: "M12 14a3 3 0 0 0 3-3V5a3 3 0 1 0-6 0v6a3 3 0 0 0 3 3Zm5-3a1 1 0 1 1 2 0 7 7 0 0 1-6 6.93V20h3a1 1 0 1 1 0 2H8a1 1 0 1 1 0-2h3v-2.07A7 7 0 0 1 5 11a1 1 0 1 1 2 0 5 5 0 0 0 10 0Z"
504
- }, null, -1)]]))], 10, ce)) : n("", !0),
600
+ }, null, -1)]]))], 10, pe)) : n("", !0),
505
601
  m(S).loading.value ? (u(), r("button", {
506
- key: 1,
602
+ key: 2,
507
603
  type: "button",
508
604
  class: "seer-chat__stop-button",
509
- onClick: o[2] ||= (e) => m(S).abort()
605
+ onClick: o[3] ||= (e) => m(S).abort()
510
606
  }, " 停止 ")) : n("", !0),
511
607
  i("button", {
512
608
  class: "seer-chat__send-button",
513
609
  type: "submit",
514
- disabled: !C.value
515
- }, [...o[5] ||= [i("span", null, "发送", -1), i("span", { "aria-hidden": "true" }, "↵", -1)]], 8, de)
610
+ disabled: !w.value
611
+ }, [...o[7] ||= [i("span", null, "发送", -1), i("span", { "aria-hidden": "true" }, "↵", -1)]], 8, ge)
516
612
  ])
517
613
  ], 32),
518
- A.value ? (u(), r("div", {
614
+ M.value ? (u(), r("div", {
519
615
  key: 3,
520
616
  class: "seer-chat__lightbox",
521
617
  role: "dialog",
522
618
  "aria-modal": "true",
523
- "aria-label": A.value.alt,
524
- onClick: N
619
+ "aria-label": M.value.alt,
620
+ onClick: F
525
621
  }, [i("button", {
526
622
  type: "button",
527
623
  class: "seer-chat__lightbox-close",
528
624
  "aria-label": "关闭",
529
- onClick: N
625
+ onClick: F
530
626
  }, " × "), i("img", {
531
627
  class: "seer-chat__lightbox-image",
532
- src: A.value.src,
533
- alt: A.value.alt
534
- }, null, 8, pe)], 8, fe)) : n("", !0)
628
+ src: M.value.src,
629
+ alt: M.value.alt
630
+ }, null, 8, _e)], 8, X)) : n("", !0)
535
631
  ]));
536
632
  }
537
- }), [["__scopeId", "data-v-ccb67bdd"]]);
633
+ }), [["__scopeId", "data-v-adaeb672"]]);
538
634
  export { Z as SeerChat, z as SeerChatPlugin, y as createSeerClient, F as linkAnchorLabel, L as toContentLines, I as toContentSegments, B as useSeerChat };
package/dist/plugin.d.ts CHANGED
@@ -4,6 +4,8 @@ export interface SeerChatPluginOptions extends SeerClientOptions {
4
4
  stream?: boolean;
5
5
  voiceEnabled?: boolean;
6
6
  maxRecordingSeconds?: number;
7
+ /** 显示附件按钮(下单模板、实物图片等);默认跟随 uploadEndpoint 是否可用。 */
8
+ uploadEnabled?: boolean;
7
9
  }
8
10
  export declare const seerChatOptionsKey: InjectionKey<SeerChatPluginOptions>;
9
11
  export declare const SeerChatPlugin: {
@@ -1,4 +1,4 @@
1
- import { type SeerChatMessage } from './client';
1
+ import { type SeerChatMessage, type SeerUploadedFile } from './client';
2
2
  import { type SeerChatPluginOptions } from './plugin';
3
3
  export interface UseSeerChatOptions extends Partial<SeerChatPluginOptions> {
4
4
  endpoint?: string;
@@ -21,6 +21,23 @@ export declare function useSeerChat(localOptions?: UseSeerChatOptions): {
21
21
  transcriptionEnabled: boolean;
22
22
  maxRecordingSeconds: number;
23
23
  transcribe: (audio: Blob, filename: string, callbacks?: import("./client").SeerTranscriptionCallbacks) => Promise<import("./client").SeerTranscriptionResult>;
24
+ uploadEnabled: boolean;
25
+ attachments: import("vue").Ref<{
26
+ fileId: string;
27
+ filename: string;
28
+ kind: "spreadsheet" | "image" | "document" | string;
29
+ contentType: string;
30
+ size: number;
31
+ }[], SeerUploadedFile[] | {
32
+ fileId: string;
33
+ filename: string;
34
+ kind: "spreadsheet" | "image" | "document" | string;
35
+ contentType: string;
36
+ size: number;
37
+ }[]>;
38
+ uploading: import("vue").Ref<boolean, boolean>;
39
+ attach: (file: File) => Promise<void>;
40
+ removeAttachment: (fileId: string) => void;
24
41
  send: (content: string) => Promise<void>;
25
42
  reset: () => void;
26
43
  abort: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seer-ai/seer-vue",
3
- "version": "0.1.0-beta.10",
3
+ "version": "0.1.0-beta.11",
4
4
  "description": "Vue chat plugin for the seer OpenAI-compatible API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",