|
@@ -1,1377 +0,0 @@
|
|
|
-import { fetchEventSource } from "@microsoft/fetch-event-source";
|
|
|
|
|
-import qs from "qs";
|
|
|
|
|
-import mitt, { Emitter, EventType } from "mitt";
|
|
|
|
|
-import { removeMarkdownLabels, resolveAllMediaResource } from "./utils";
|
|
|
|
|
-import { RecordRowTypes, StreamXMLFilter, getStreamPlainText } from "@/utils/stream-xml-filter";
|
|
|
|
|
-import { jsonrepair } from "jsonrepair";
|
|
|
|
|
-import { isKnowledgeApi } from "@/utils/runtime-config";
|
|
|
|
|
-import { getScopeCloseLineBoundaryEnd } from "@/utils/scope-stream-boundary";
|
|
|
|
|
-
|
|
|
|
|
-const isTypewriterDebugEnabled = () =>
|
|
|
|
|
- import.meta.env.MODE !== "production" &&
|
|
|
|
|
- typeof localStorage !== "undefined" &&
|
|
|
|
|
- localStorage.getItem("enable_typewriter_debug") === "true";
|
|
|
|
|
-
|
|
|
|
|
-export enum MessageState {
|
|
|
|
|
- Update,
|
|
|
|
|
- Sending,
|
|
|
|
|
- Success,
|
|
|
|
|
- Finish,
|
|
|
|
|
- Fail,
|
|
|
|
|
- Stop,
|
|
|
|
|
- End,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-declare enum TTSAudioType {
|
|
|
|
|
- Wav = "wav",
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-export type TTSParams = {
|
|
|
|
|
- text: string;
|
|
|
|
|
- tts_args: string;
|
|
|
|
|
- audio_type: TTSAudioType;
|
|
|
|
|
- storage_type: "cloud";
|
|
|
|
|
- anim_agent: string;
|
|
|
|
|
- silence_type?: string;
|
|
|
|
|
- silence_value?: string;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-type StreamMessageArgs = {
|
|
|
|
|
- SSEURL: string;
|
|
|
|
|
- session: string;
|
|
|
|
|
- token: string;
|
|
|
|
|
- card_id: number;
|
|
|
|
|
- isStrict?: boolean;
|
|
|
|
|
- sign?: () => string;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-type textItem = {
|
|
|
|
|
- text: string;
|
|
|
|
|
- isFinished: boolean;
|
|
|
|
|
- itemCount: number;
|
|
|
|
|
- from?: string;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-type OptionsType = {
|
|
|
|
|
- is_strict: boolean;
|
|
|
|
|
- disable_censor?: boolean;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-type TTSItem = {
|
|
|
|
|
- text: string;
|
|
|
|
|
- audio: string;
|
|
|
|
|
- expression_anim: string;
|
|
|
|
|
- teeth_anim: string;
|
|
|
|
|
- isFormChat: boolean;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-type FollowUpSuggestion = {
|
|
|
|
|
- text: string;
|
|
|
|
|
- intent?: string;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-export class StreamMessageCoordinator {
|
|
|
|
|
- SSEURL: string;
|
|
|
|
|
- SSEFormURL: string;
|
|
|
|
|
- TTSURL: string;
|
|
|
|
|
- session: string;
|
|
|
|
|
- token: string;
|
|
|
|
|
- card_id: number;
|
|
|
|
|
- department_id: string;
|
|
|
|
|
- ctrl: AbortController;
|
|
|
|
|
- storedTexts: textItem[];
|
|
|
|
|
- storedTTS: TTSItem[];
|
|
|
|
|
- question: string;
|
|
|
|
|
- totalResponse: string;
|
|
|
|
|
- record_id: string;
|
|
|
|
|
- loading: boolean;
|
|
|
|
|
- generating: boolean;
|
|
|
|
|
- isGettingTTS: boolean;
|
|
|
|
|
- isSpeaking: boolean;
|
|
|
|
|
- AIResponseQueueTimer: number;
|
|
|
|
|
- tts: TTSParams;
|
|
|
|
|
- TTSTimer: number;
|
|
|
|
|
- isMuted: boolean;
|
|
|
|
|
- formChatting: boolean;
|
|
|
|
|
- formTaskId: string;
|
|
|
|
|
- isStrict: boolean;
|
|
|
|
|
- isFinishEventEmitted: boolean;
|
|
|
|
|
- knowledge_ids: string[];
|
|
|
|
|
- sign?: () => string;
|
|
|
|
|
- protected _eventbus: Emitter<Record<EventType, unknown>>;
|
|
|
|
|
- isThinkMode: boolean;
|
|
|
|
|
- scopeBufferArray: string[];
|
|
|
|
|
- silenceBuffer: string;
|
|
|
|
|
- silenceMode: boolean;
|
|
|
|
|
- followupFilter: StreamXMLFilter;
|
|
|
|
|
- constructor(args: StreamMessageArgs) {
|
|
|
|
|
- this._eventbus = mitt();
|
|
|
|
|
- // this.link_id = args.link_id || 'a2fd3b17-c9fe-4b4f-87af-af902a2f1440'
|
|
|
|
|
- this.SSEURL = args.SSEURL || "http://192.168.14.45:4006/knowledge/chat";
|
|
|
|
|
- this.SSEFormURL = args.SSEFormURL || "http://192.168.14.45:4006/knowledge/form_chat";
|
|
|
|
|
- this.TTSURL = args.TTSURL || "//llm-api.test.metamaker.cn/knowledge/tts";
|
|
|
|
|
- this.session = args.session || "example";
|
|
|
|
|
- this.card_id = args.card_id;
|
|
|
|
|
- this.department_id = args.department_id;
|
|
|
|
|
- this.token = args.token;
|
|
|
|
|
- this.tts = {
|
|
|
|
|
- ...args.tts,
|
|
|
|
|
- anim_agent: "mms",
|
|
|
|
|
- silence_type: "Tailing",
|
|
|
|
|
- silence_value: "100ms",
|
|
|
|
|
- };
|
|
|
|
|
- this.isStrict = args.isStrict || false;
|
|
|
|
|
- this.ctrl = new AbortController();
|
|
|
|
|
- this.TTSTimer = 0;
|
|
|
|
|
- this.storedTexts = [];
|
|
|
|
|
- this.knowledge_ids = [];
|
|
|
|
|
- this.storedTTS = [];
|
|
|
|
|
- this.totalResponse = "";
|
|
|
|
|
- this.isMuted = false;
|
|
|
|
|
- this.formChatting = false;
|
|
|
|
|
- this.formTaskId = "";
|
|
|
|
|
- this.sign = args.sign;
|
|
|
|
|
- // this.storedTexts = new Proxy([], {
|
|
|
|
|
- // set: (target, prop, value) => {
|
|
|
|
|
- // target[prop] = value;
|
|
|
|
|
-
|
|
|
|
|
- // target['length'] = target.filter(each => each !== null).length;
|
|
|
|
|
- // this.checkAvailableTexts();
|
|
|
|
|
- // this.checkAvailableTTS();
|
|
|
|
|
-
|
|
|
|
|
- // console.log('texts', this.storedTexts, 'tts', this.storedTTS)
|
|
|
|
|
- // return true;
|
|
|
|
|
- // }
|
|
|
|
|
- // })
|
|
|
|
|
-
|
|
|
|
|
- // this.storedTTS = new Proxy([], {
|
|
|
|
|
- // set: (target, prop, value) => {
|
|
|
|
|
- // target[prop] = value;
|
|
|
|
|
- // target['length'] = target.filter(each => each !== null).length;
|
|
|
|
|
- // this.checkAvailableTTS();
|
|
|
|
|
-
|
|
|
|
|
- // console.log('texts', this.storedTexts, 'tts', this.storedTTS)
|
|
|
|
|
- // return true;
|
|
|
|
|
- // }
|
|
|
|
|
- // })
|
|
|
|
|
-
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- this.isSpeaking = false;
|
|
|
|
|
- this.scopeBufferArray = [];
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- this.silenceMode = false;
|
|
|
|
|
- this.followupFilter = new StreamXMLFilter({
|
|
|
|
|
- customProcessor: new Map([
|
|
|
|
|
- [
|
|
|
|
|
- RecordRowTypes.FollowUp,
|
|
|
|
|
- (payload: { schema: string }) => {
|
|
|
|
|
- const suggestions = this.parseFollowupSuggestions(payload?.schema);
|
|
|
|
|
- if (suggestions.length) {
|
|
|
|
|
- this._eventbus.emit("followupSuggestions", suggestions);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- [
|
|
|
|
|
- RecordRowTypes.PolicyListUpdated,
|
|
|
|
|
- async (payload: { schema: string }) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const jsonObj = JSON.parse(jsonrepair(payload.schema));
|
|
|
|
|
- let link = jsonObj.link;
|
|
|
|
|
- const dataPayload = jsonObj.payload;
|
|
|
|
|
- if (!link && dataPayload && dataPayload.link) {
|
|
|
|
|
- link = dataPayload.link;
|
|
|
|
|
- }
|
|
|
|
|
- if (link) {
|
|
|
|
|
- let cleanLink = link.replace(/`/g, '').trim();
|
|
|
|
|
- try {
|
|
|
|
|
- const urlObj = new URL(cleanLink, window.location.origin);
|
|
|
|
|
- if (dataPayload) {
|
|
|
|
|
- Object.keys(dataPayload).forEach(key => {
|
|
|
|
|
- if (key !== 'link' && dataPayload[key] !== undefined && dataPayload[key] !== null) {
|
|
|
|
|
- urlObj.searchParams.set(key, String(dataPayload[key]));
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- cleanLink = urlObj.toString();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn('Invalid URL:', cleanLink, e);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const res = await fetch(cleanLink);
|
|
|
|
|
- if (res.ok) {
|
|
|
|
|
- const data = await res.json();
|
|
|
|
|
- if (data && Array.isArray(data.data)) {
|
|
|
|
|
- globalThis.policiesPublic = data.data;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('Fetch policy-list-updated error in stream coordinator:', e);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- [
|
|
|
|
|
- RecordRowTypes.EnterpriseNotification,
|
|
|
|
|
- (payload: { schema: string }) => {
|
|
|
|
|
- // 解析关注企业的统一社会信用代码,保存到 globalThis 并广播事件
|
|
|
|
|
- const creditCode = String(payload?.schema || "").trim();
|
|
|
|
|
- if (!creditCode) return;
|
|
|
|
|
- (globalThis as any).focusedEnterpriseCreditCode = creditCode;
|
|
|
|
|
- try {
|
|
|
|
|
- document.dispatchEvent(
|
|
|
|
|
- new CustomEvent("qingpu:enterprise-notification", {
|
|
|
|
|
- detail: { creditCode },
|
|
|
|
|
- })
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn("Dispatch enterprise-notification failed", e);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- ]),
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- setURL(url) {
|
|
|
|
|
- this.SSEURL = url;
|
|
|
|
|
- }
|
|
|
|
|
- addEventListener(eventName: string, callback) {
|
|
|
|
|
- this._eventbus.on(eventName, callback);
|
|
|
|
|
- return this;
|
|
|
|
|
- }
|
|
|
|
|
- removeEventListener(eventName: string, callback?) {
|
|
|
|
|
- this._eventbus.off(eventName, callback);
|
|
|
|
|
- return this;
|
|
|
|
|
- }
|
|
|
|
|
- allClear() {
|
|
|
|
|
- this._eventbus.all.clear();
|
|
|
|
|
- }
|
|
|
|
|
- async generateAnswer(text: string, isFormChat: boolean = undefined, taskId: string = undefined) {
|
|
|
|
|
- this.loading = true;
|
|
|
|
|
- this.generating = true;
|
|
|
|
|
- this.isGettingTTS = false;
|
|
|
|
|
- this._eventbus.emit("loadingState", { type: "loading", state: true });
|
|
|
|
|
- this.record_id = "";
|
|
|
|
|
- this.question = text;
|
|
|
|
|
- this.totalResponse = "";
|
|
|
|
|
- this.storedTexts = [];
|
|
|
|
|
- this.storedTTS = [];
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- this.knowledge_ids = [];
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- this.silenceMode = false;
|
|
|
|
|
- this.followupFilter.reset();
|
|
|
|
|
-
|
|
|
|
|
- if (isFormChat !== undefined) {
|
|
|
|
|
- this.formChatting = isFormChat;
|
|
|
|
|
-
|
|
|
|
|
- if (taskId !== undefined) {
|
|
|
|
|
- this.formTaskId = taskId;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // let data = {
|
|
|
|
|
- // link_id: this.link_id,
|
|
|
|
|
- // text: text,
|
|
|
|
|
- // session: this.session
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- let data = {
|
|
|
|
|
- card_id: this.card_id,
|
|
|
|
|
- department_id: this.department_id,
|
|
|
|
|
- text: text,
|
|
|
|
|
- session_id: this.session,
|
|
|
|
|
- format: "object",
|
|
|
|
|
- model: globalThis.llmModel,
|
|
|
|
|
- transmission: {
|
|
|
|
|
- session_id: this.session,
|
|
|
|
|
- model: globalThis.llmModel,
|
|
|
|
|
- },
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (this.formChatting) {
|
|
|
|
|
- data.task_id = this.formTaskId;
|
|
|
|
|
- data.text = text;
|
|
|
|
|
- } else {
|
|
|
|
|
- this.formTaskId = "";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (globalThis.transmission) {
|
|
|
|
|
- data.transmission.credit_code = globalThis.transmission.credit_code;
|
|
|
|
|
- if (globalThis.transmission.files !== undefined) {
|
|
|
|
|
- data.transmission.files = globalThis.transmission.files;
|
|
|
|
|
- }
|
|
|
|
|
- if (globalThis.transmission.file_pos !== undefined) {
|
|
|
|
|
- data.transmission.file_pos = globalThis.transmission.file_pos;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if ((globalThis as any).source) {
|
|
|
|
|
- data.source = (globalThis as any).source;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 与 source 同一级:补充企业级上下文与历史控制参数
|
|
|
|
|
- data.credit_code = globalThis.transmission?.credit_code;
|
|
|
|
|
- data.knowledge_ids = this.knowledge_ids;
|
|
|
|
|
- data.enable_history = true;
|
|
|
|
|
-
|
|
|
|
|
- if (globalThis.enable_thinking !== undefined) {
|
|
|
|
|
- data.transmission.enable_thinking = globalThis.enable_thinking;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.checkAvailableResponse();
|
|
|
|
|
-
|
|
|
|
|
- let headers: any = {
|
|
|
|
|
- "Content-Type": "application/json",
|
|
|
|
|
- };
|
|
|
|
|
- let token = this.token || globalThis.token;
|
|
|
|
|
- if (token && token !== 'undefined') {
|
|
|
|
|
- headers.Authorization = token;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let begin = Date.now();
|
|
|
|
|
- let trace = window.__bl && window.__bl.getUberTraceId();
|
|
|
|
|
- let traceId = trace && trace.traceId;
|
|
|
|
|
- let isFirstByteReached = false;
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl.setConfig({
|
|
|
|
|
- disableHook: true,
|
|
|
|
|
- });
|
|
|
|
|
- console.log(trace);
|
|
|
|
|
- headers["Uber-Trace-Id"] = trace["uber-trace-id"];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let eventSourceUrl = this.formChatting ? this.SSEFormURL : this.SSEURL;
|
|
|
|
|
-
|
|
|
|
|
- fetchEventSource(eventSourceUrl, {
|
|
|
|
|
- method: "POST",
|
|
|
|
|
- headers,
|
|
|
|
|
- body: JSON.stringify(data),
|
|
|
|
|
- signal: this.ctrl.signal,
|
|
|
|
|
- openWhenHidden: true,
|
|
|
|
|
- onopen: async (response) => {
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.setConfig({
|
|
|
|
|
- disableHook: false,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let serverPreprocessInfo = await this.handleServerPreprocessInfo(response);
|
|
|
|
|
- if (serverPreprocessInfo) {
|
|
|
|
|
- throw serverPreprocessInfo;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.isThinkMode = globalThis.enable_thinking;
|
|
|
|
|
- this.isFinishEventEmitted = false;
|
|
|
|
|
- this._eventbus.emit("open", response);
|
|
|
|
|
- },
|
|
|
|
|
- onmessage: (msg) => {
|
|
|
|
|
- if (!isFirstByteReached && traceId) {
|
|
|
|
|
- let time = Date.now() - begin;
|
|
|
|
|
- // 上报接口调用成功
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.api(this.SSEURL.slice(2), true, time, 200, msg.data, begin, traceId);
|
|
|
|
|
- }
|
|
|
|
|
- isFirstByteReached = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (msg.event === "fail") {
|
|
|
|
|
- // this._eventbus.emit('info', '知识库未正确设置,请联系管理员')
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.loading = false;
|
|
|
|
|
- // this.AIResponse.text = '系统繁忙,请稍后再试'
|
|
|
|
|
- // this.AIResponse.state = MessageState.Finish
|
|
|
|
|
- // this._eventbus.emit('subtitle', this.AIResponse)
|
|
|
|
|
-
|
|
|
|
|
- let message = "系统繁忙,请稍后再试";
|
|
|
|
|
- try {
|
|
|
|
|
- let data = JSON.parse(msg.data);
|
|
|
|
|
- if (data) {
|
|
|
|
|
- message = data.message;
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.log(e);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("info", message);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- let data = JSON.parse(msg.data);
|
|
|
|
|
- if (data) {
|
|
|
|
|
- if (!this.record_id) {
|
|
|
|
|
- this.record_id = data.record_id;
|
|
|
|
|
- if (this.record_id) {
|
|
|
|
|
- this._eventbus.emit("recordId", this.record_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.knowledge_ids) {
|
|
|
|
|
- this.knowledge_ids = data.knowledge_ids;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let message = data.message;
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this.followupFilter.processChunk(message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message === "<think>") {
|
|
|
|
|
- this.isThinkMode = true;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message == "</think>") {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- message &&
|
|
|
|
|
- !data.reasoning_content &&
|
|
|
|
|
- this.isThinkMode &&
|
|
|
|
|
- !isKnowledgeApi()
|
|
|
|
|
- ) {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.reasoning_content || this.isThinkMode) {
|
|
|
|
|
- this._eventbus.emit("reasoning_content", data.reasoning_content || message);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- let { taskId, relate, reference } = resolveAllMediaResource(message);
|
|
|
|
|
- if ((relate && relate.length) || (reference && reference.length)) {
|
|
|
|
|
- const ques_relate = relate
|
|
|
|
|
- .map((each) => ({
|
|
|
|
|
- question: each["key_question"] || each["问题"] || each["事项名称"] || each,
|
|
|
|
|
- }))
|
|
|
|
|
- .filter((each) => each.question);
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("relate", ques_relate);
|
|
|
|
|
-
|
|
|
|
|
- const ques_reference = reference
|
|
|
|
|
- .map((each) => ({
|
|
|
|
|
- question: each["key_question"] || each["问题"] || each["事项名称"] || each,
|
|
|
|
|
- }))
|
|
|
|
|
- .filter((each) => each.question);
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("reference", ques_reference);
|
|
|
|
|
- return;
|
|
|
|
|
- } else if (taskId) {
|
|
|
|
|
- this.turnFormRequest({ taskId, question: this.question });
|
|
|
|
|
- return;
|
|
|
|
|
- } else if (message.includes("<!-- POLICY_TABLE")) {
|
|
|
|
|
- this.putInText({ message, from: data.from });
|
|
|
|
|
- this.textEnqueue();
|
|
|
|
|
- } else {
|
|
|
|
|
- message.split("").forEach((char) => {
|
|
|
|
|
- this.putInText({ message: char, from: data.from });
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.status) {
|
|
|
|
|
- if (data.status === "done" || data.status === "interrupt") {
|
|
|
|
|
- this.turnChatRequest();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this._eventbus.emit("message", message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.disease_id) {
|
|
|
|
|
- this._eventbus.emit("diagnosed", data.disease_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- this._eventbus.emit("info", "知识库未正确设置,请联系管理员");
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- console.log(e);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- onerror: (err) => {
|
|
|
|
|
- if (traceId) {
|
|
|
|
|
- let time = Date.now() - begin;
|
|
|
|
|
- // 上报接口调用失败
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.api(
|
|
|
|
|
- this.SSEURL.slice(2),
|
|
|
|
|
- false,
|
|
|
|
|
- time,
|
|
|
|
|
- "ERROR",
|
|
|
|
|
- JSON.stringify(err),
|
|
|
|
|
- begin,
|
|
|
|
|
- traceId
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- console.warn(err);
|
|
|
|
|
- this._eventbus.emit("info", "与服务器的连接异常,请稍后再试");
|
|
|
|
|
- this.stopGenerate();
|
|
|
|
|
- this.turnChatRequest();
|
|
|
|
|
- this._eventbus.emit("error", err);
|
|
|
|
|
- throw new Error(err);
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- onclose: () => {
|
|
|
|
|
- console.warn("onclose");
|
|
|
|
|
- this.generating = false;
|
|
|
|
|
-
|
|
|
|
|
- this.storeTotalResponse();
|
|
|
|
|
- this._eventbus.emit("close");
|
|
|
|
|
-
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- async generateFormSubmit(text: string) {
|
|
|
|
|
- this.loading = true;
|
|
|
|
|
- this.generating = true;
|
|
|
|
|
- this.isGettingTTS = false;
|
|
|
|
|
- this._eventbus.emit("loadingState", { type: "loading", state: true });
|
|
|
|
|
- this.record_id = "";
|
|
|
|
|
- this.question = text;
|
|
|
|
|
- this.totalResponse = "";
|
|
|
|
|
- this.storedTexts = [];
|
|
|
|
|
- this.storedTTS = [];
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- this.knowledge_ids = [];
|
|
|
|
|
- this.followupFilter.reset();
|
|
|
|
|
-
|
|
|
|
|
- const currentModel = "form_submit";
|
|
|
|
|
- const messages = [
|
|
|
|
|
- {
|
|
|
|
|
- role: "user",
|
|
|
|
|
- content: text,
|
|
|
|
|
- },
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- // let data = {
|
|
|
|
|
- // stream: true,
|
|
|
|
|
- // model: currentModel,
|
|
|
|
|
- // messages,
|
|
|
|
|
- // session_id: this.session,
|
|
|
|
|
- // };
|
|
|
|
|
-
|
|
|
|
|
- // if (globalThis.transmission) {
|
|
|
|
|
- // data.credit_code = globalThis.transmission.credit_code;
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- let data = {
|
|
|
|
|
- card_id: this.card_id,
|
|
|
|
|
- department_id: this.department_id,
|
|
|
|
|
- text: text,
|
|
|
|
|
- session_id: this.session,
|
|
|
|
|
- format: "object",
|
|
|
|
|
- model: currentModel,
|
|
|
|
|
- transmission: {
|
|
|
|
|
- session_id: this.session,
|
|
|
|
|
- model: currentModel,
|
|
|
|
|
- },
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.checkAvailableResponse();
|
|
|
|
|
-
|
|
|
|
|
- let headers: any = {
|
|
|
|
|
- "Content-Type": "application/json",
|
|
|
|
|
- };
|
|
|
|
|
- let token = this.token || globalThis.token;
|
|
|
|
|
- if (token && token !== 'undefined') {
|
|
|
|
|
- headers.Authorization = token;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let begin = Date.now();
|
|
|
|
|
- let trace = window.__bl && window.__bl.getUberTraceId();
|
|
|
|
|
- let traceId = trace && trace.traceId;
|
|
|
|
|
- let isFirstByteReached = false;
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl.setConfig({
|
|
|
|
|
- disableHook: true,
|
|
|
|
|
- });
|
|
|
|
|
- console.log(trace);
|
|
|
|
|
- headers["Uber-Trace-Id"] = trace["uber-trace-id"];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let eventSourceUrl = `${import.meta.env.VITE_MODEL_SERVER}/v1/chat/completions`;
|
|
|
|
|
- // let eventSourceUrl = this.SSEURL;
|
|
|
|
|
-
|
|
|
|
|
- fetchEventSource(eventSourceUrl, {
|
|
|
|
|
- method: "POST",
|
|
|
|
|
- headers,
|
|
|
|
|
- body: JSON.stringify(data),
|
|
|
|
|
- signal: this.ctrl.signal,
|
|
|
|
|
- openWhenHidden: true,
|
|
|
|
|
- onopen: async (response) => {
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.setConfig({
|
|
|
|
|
- disableHook: false,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let serverPreprocessInfo = await this.handleServerPreprocessInfo(response);
|
|
|
|
|
- if (serverPreprocessInfo) {
|
|
|
|
|
- throw serverPreprocessInfo;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.isThinkMode = globalThis.enable_thinking;
|
|
|
|
|
- this.isFinishEventEmitted = false;
|
|
|
|
|
- this._eventbus.emit("open", response);
|
|
|
|
|
- },
|
|
|
|
|
- onmessage: (msg) => {
|
|
|
|
|
- if (!isFirstByteReached && traceId) {
|
|
|
|
|
- let time = Date.now() - begin;
|
|
|
|
|
- // 上报接口调用成功
|
|
|
|
|
- if (trace) {
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.api(this.SSEURL.slice(2), true, time, 200, msg.data, begin, traceId);
|
|
|
|
|
- }
|
|
|
|
|
- isFirstByteReached = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (msg.event === "fail") {
|
|
|
|
|
- // this._eventbus.emit('info', '知识库未正确设置,请联系管理员')
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.loading = false;
|
|
|
|
|
- // this.AIResponse.text = '系统繁忙,请稍后再试'
|
|
|
|
|
- // this.AIResponse.state = MessageState.Finish
|
|
|
|
|
- // this._eventbus.emit('subtitle', this.AIResponse)
|
|
|
|
|
-
|
|
|
|
|
- let message = "系统繁忙,请稍后再试";
|
|
|
|
|
- try {
|
|
|
|
|
- let data = JSON.parse(msg.data);
|
|
|
|
|
- if (data) {
|
|
|
|
|
- // message = data.choice[0].delta.content;
|
|
|
|
|
- message = data.message;
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.log(e);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("info", message);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- let data = JSON.parse(msg.data);
|
|
|
|
|
- if (data) {
|
|
|
|
|
- if (!this.record_id) {
|
|
|
|
|
- this.record_id = data.record_id;
|
|
|
|
|
- if (this.record_id) {
|
|
|
|
|
- this._eventbus.emit("recordId", this.record_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.knowledge_ids) {
|
|
|
|
|
- this.knowledge_ids = data.knowledge_ids;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // const message = data.choices[0].delta.content;
|
|
|
|
|
- const message = data.message;
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this.followupFilter.processChunk(message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message === "<think>") {
|
|
|
|
|
- this.isThinkMode = true;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message == "</think>") {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- message &&
|
|
|
|
|
- !data.reasoning_content &&
|
|
|
|
|
- this.isThinkMode &&
|
|
|
|
|
- !isKnowledgeApi()
|
|
|
|
|
- ) {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.reasoning_content || this.isThinkMode) {
|
|
|
|
|
- this._eventbus.emit("reasoning_content", data.reasoning_content || message);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- let { taskId, relate, reference } = resolveAllMediaResource(message);
|
|
|
|
|
- if ((relate && relate.length) || (reference && reference.length)) {
|
|
|
|
|
- const ques_relate = relate
|
|
|
|
|
- .map((each) => ({
|
|
|
|
|
- question: each["key_question"] || each["问题"] || each["事项名称"] || each,
|
|
|
|
|
- }))
|
|
|
|
|
- .filter((each) => each.question);
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("relate", ques_relate);
|
|
|
|
|
-
|
|
|
|
|
- const ques_reference = reference
|
|
|
|
|
- .map((each) => ({
|
|
|
|
|
- question: each["key_question"] || each["问题"] || each["事项名称"] || each,
|
|
|
|
|
- }))
|
|
|
|
|
- .filter((each) => each.question);
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("reference", ques_reference);
|
|
|
|
|
- return;
|
|
|
|
|
- } else if (taskId) {
|
|
|
|
|
- this.turnFormRequest({ taskId, question: this.question });
|
|
|
|
|
- return;
|
|
|
|
|
- } else if (message.includes("<!-- POLICY_TABLE")) {
|
|
|
|
|
- this.putInText({ message, from: data.from });
|
|
|
|
|
- this.textEnqueue();
|
|
|
|
|
- } else {
|
|
|
|
|
- message.split("").forEach((char) => {
|
|
|
|
|
- this.putInText({ message: char, from: data.from });
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.status) {
|
|
|
|
|
- if (data.status === "done" || data.status === "interrupt") {
|
|
|
|
|
- this.turnChatRequest();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (message) {
|
|
|
|
|
- this._eventbus.emit("message", message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (data.disease_id) {
|
|
|
|
|
- this._eventbus.emit("diagnosed", data.disease_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- this._eventbus.emit("info", "知识库未正确设置,请联系管理员");
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- console.log(e);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- onerror: (err) => {
|
|
|
|
|
- if (traceId) {
|
|
|
|
|
- let time = Date.now() - begin;
|
|
|
|
|
- // 上报接口调用失败
|
|
|
|
|
- window.__bl &&
|
|
|
|
|
- __bl.api(
|
|
|
|
|
- this.SSEURL.slice(2),
|
|
|
|
|
- false,
|
|
|
|
|
- time,
|
|
|
|
|
- "ERROR",
|
|
|
|
|
- JSON.stringify(err),
|
|
|
|
|
- begin,
|
|
|
|
|
- traceId
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- console.warn(err);
|
|
|
|
|
- this._eventbus.emit("info", "与服务器的连接异常,请稍后再试");
|
|
|
|
|
- this.stopGenerate();
|
|
|
|
|
- this.turnChatRequest();
|
|
|
|
|
- this._eventbus.emit("error", err);
|
|
|
|
|
- throw new Error(err);
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- onclose: () => {
|
|
|
|
|
- console.warn("onclose");
|
|
|
|
|
- this.generating = false;
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("close");
|
|
|
|
|
-
|
|
|
|
|
- if (this.storedTexts[this.storedTexts.length - 1]) {
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- stopGenerate(payload: { answer?: string } = {}) {
|
|
|
|
|
- let { answer } = payload || {};
|
|
|
|
|
-
|
|
|
|
|
- this.loading = false;
|
|
|
|
|
- this.generating = false;
|
|
|
|
|
- this.isSpeaking = false;
|
|
|
|
|
- this.storedTexts = [];
|
|
|
|
|
- this.storedTTS = [];
|
|
|
|
|
- this.isThinkMode = false;
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- this.silenceMode = false;
|
|
|
|
|
- this.followupFilter.reset();
|
|
|
|
|
-
|
|
|
|
|
- if (this.totalResponse || answer) {
|
|
|
|
|
- this.storeTotalResponse({ answer });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("loadingState", { type: "loading", state: false });
|
|
|
|
|
- if (this.AIResponseQueueTimer) {
|
|
|
|
|
- clearInterval(this.AIResponseQueueTimer);
|
|
|
|
|
- this.AIResponseQueueTimer = 0;
|
|
|
|
|
- }
|
|
|
|
|
- this.abortConnection();
|
|
|
|
|
- }
|
|
|
|
|
- abortConnection() {
|
|
|
|
|
- this.ctrl.abort();
|
|
|
|
|
- this.ctrl = new AbortController();
|
|
|
|
|
- }
|
|
|
|
|
- private getCurrentTextItem(from: string) {
|
|
|
|
|
- let lastIdx = this.storedTexts.length - 1;
|
|
|
|
|
- let last = this.storedTexts[lastIdx];
|
|
|
|
|
-
|
|
|
|
|
- if (this.storedTexts.length === 0 || (last && last.isFinished)) {
|
|
|
|
|
- this.storedTexts.push({
|
|
|
|
|
- text: "",
|
|
|
|
|
- isFinished: false,
|
|
|
|
|
- itemCount: 0,
|
|
|
|
|
- from,
|
|
|
|
|
- });
|
|
|
|
|
- lastIdx = this.storedTexts.length - 1;
|
|
|
|
|
- last = this.storedTexts[lastIdx];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return { lastIdx, last };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private appendTextToCurrentItem(message: string, from: string, autoEnqueue = true) {
|
|
|
|
|
- const { lastIdx, last } = this.getCurrentTextItem(from);
|
|
|
|
|
-
|
|
|
|
|
- // 追加文本优化:targetObject.message 为空时跳过
|
|
|
|
|
- if (!message) return;
|
|
|
|
|
-
|
|
|
|
|
- const updatedText = (last?.text || "") + message;
|
|
|
|
|
- Reflect.set(this.storedTexts, lastIdx, {
|
|
|
|
|
- ...last,
|
|
|
|
|
- text: updatedText,
|
|
|
|
|
- itemCount: updatedText.length,
|
|
|
|
|
- isFinished: false,
|
|
|
|
|
- from,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 优化分段触发:批量符号 includes 和换行
|
|
|
|
|
- const gapChars = [",", "。", ",", "!", "?", "\n"];
|
|
|
|
|
- const isGap =
|
|
|
|
|
- gapChars.some((char) => message.includes(char)) ||
|
|
|
|
|
- message.includes("\n\n");
|
|
|
|
|
- if (autoEnqueue && isGap) {
|
|
|
|
|
- this.textEnqueue();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- putInText(targetObject: { message: string; from: string }) {
|
|
|
|
|
- // 追加文本优化:targetObject.message 为空时跳过
|
|
|
|
|
- if (!targetObject.message) return;
|
|
|
|
|
-
|
|
|
|
|
- const startedAt = performance.now();
|
|
|
|
|
- let splitCount = 0;
|
|
|
|
|
-
|
|
|
|
|
- // HTML 注释(如 <!-- POLICY_TABLE)应当跳过 silence 检查,
|
|
|
|
|
- // 因为 checkSilenceMode 逐字符处理会破坏 <!- 前缀导致整个注释块被丢弃
|
|
|
|
|
- let message = targetObject.message;
|
|
|
|
|
- if (message.includes("<!--")) {
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- this.silenceMode = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- message = this.checkSilenceMode(message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- while (message) {
|
|
|
|
|
- const last = this.storedTexts[this.storedTexts.length - 1];
|
|
|
|
|
- const lastText = last && !last.isFinished ? last.text || "" : "";
|
|
|
|
|
- const boundaryEnd = getScopeCloseLineBoundaryEnd(lastText + message);
|
|
|
|
|
-
|
|
|
|
|
- if (boundaryEnd > lastText.length) {
|
|
|
|
|
- const boundaryMessageLength = boundaryEnd - lastText.length;
|
|
|
|
|
- this.appendTextToCurrentItem(message.slice(0, boundaryMessageLength), targetObject.from, false);
|
|
|
|
|
- this.textEnqueue(true);
|
|
|
|
|
- message = message.slice(boundaryMessageLength);
|
|
|
|
|
- splitCount += 1;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.appendTextToCurrentItem(message, targetObject.from);
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const cost = performance.now() - startedAt;
|
|
|
|
|
- if (isTypewriterDebugEnabled() && (cost > 8 || splitCount > 1)) {
|
|
|
|
|
- console.debug("[StreamMessageCoordinator] putInText", {
|
|
|
|
|
- cost: Number(cost.toFixed(2)),
|
|
|
|
|
- splitCount,
|
|
|
|
|
- messageLength: targetObject.message.length,
|
|
|
|
|
- from: targetObject.from,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- textEnqueue(force = false) {
|
|
|
|
|
- if (this.storedTexts.length === 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const startedAt = performance.now();
|
|
|
|
|
- if (!force && this.storedTexts[this.storedTexts.length - 1].itemCount <= 12) {
|
|
|
|
|
- if (isTypewriterDebugEnabled()) {
|
|
|
|
|
- console.debug("[StreamMessageCoordinator] textEnqueue", {
|
|
|
|
|
- force,
|
|
|
|
|
- action: "keep-pending",
|
|
|
|
|
- itemCount: this.storedTexts[this.storedTexts.length - 1].itemCount,
|
|
|
|
|
- storeLength: this.storedTexts.length,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- itemCount:
|
|
|
|
|
- this.storedTexts[this.storedTexts.length - 1].itemCount +
|
|
|
|
|
- this.storedTexts[this.storedTexts.length - 1].text.length,
|
|
|
|
|
- });
|
|
|
|
|
- // 当前的 textItem 还没有完成,不需要再次 enqueue,直接返回
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
|
|
|
|
|
- ...this.storedTexts[this.storedTexts.length - 1],
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (isTypewriterDebugEnabled()) {
|
|
|
|
|
- const cost = performance.now() - startedAt;
|
|
|
|
|
- console.debug("[StreamMessageCoordinator] textEnqueue", {
|
|
|
|
|
- force,
|
|
|
|
|
- action: "finish-item",
|
|
|
|
|
- itemCount: this.storedTexts[this.storedTexts.length - 1].itemCount,
|
|
|
|
|
- storeLength: this.storedTexts.length,
|
|
|
|
|
- cost: Number(cost.toFixed(2)),
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- async fetchTTS(text: string, options: OptionsType = { is_strict: this.isStrict }) {
|
|
|
|
|
- let headers: any = {
|
|
|
|
|
- "Content-Type": "application/x-www-form-urlencoded",
|
|
|
|
|
- };
|
|
|
|
|
- let token = this.token || globalThis.token;
|
|
|
|
|
- if (token && token !== 'undefined') {
|
|
|
|
|
- headers.Authorization = token;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (this.sign) {
|
|
|
|
|
- headers["X-Heijing-Verf"] = this.sign();
|
|
|
|
|
- }
|
|
|
|
|
- text = removeMarkdownLabels(text);
|
|
|
|
|
- const response = await fetch(this.TTSURL, {
|
|
|
|
|
- method: "post",
|
|
|
|
|
- headers,
|
|
|
|
|
- // body: qs.stringify({ text: textItem.text, tts_args: JSON.stringify({ ...this.human.tts, voice_name: 'boss_neuralv2' }), audio_type: 'wav', storage_type: 'cloud' }),
|
|
|
|
|
- body: qs.stringify({
|
|
|
|
|
- text,
|
|
|
|
|
- tts_args: JSON.stringify(this.tts),
|
|
|
|
|
- audio_type: "wav",
|
|
|
|
|
- storage_type: "cloud",
|
|
|
|
|
- card_id: this.card_id,
|
|
|
|
|
- ...options,
|
|
|
|
|
- }),
|
|
|
|
|
- }).catch((err) => {
|
|
|
|
|
- console.warn(err);
|
|
|
|
|
- this._eventbus.emit("error", err);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const result = await response.json();
|
|
|
|
|
- if (result.err_code != 0) {
|
|
|
|
|
- this.handleSensitiveTTS(result, text);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- async checkAvailableTexts() {
|
|
|
|
|
- let _record_id = this.record_id;
|
|
|
|
|
- if (!this.isGettingTTS && this.storedTexts.length && this.storedTexts[0].isFinished) {
|
|
|
|
|
- this.isGettingTTS = true;
|
|
|
|
|
- let textItem = this.storedTexts.length ? this.storedTexts[0] : null;
|
|
|
|
|
- if (textItem && textItem.text) {
|
|
|
|
|
- // 为了让 tts 的时候不念出 控制 token,需要把控制 token 去掉
|
|
|
|
|
- const streamText = getStreamPlainText(textItem.text);
|
|
|
|
|
- let { taskId } = resolveAllMediaResource(textItem.text);
|
|
|
|
|
- let { content } = resolveAllMediaResource(streamText);
|
|
|
|
|
- if (this.checkIfCanBeConvertedToTTS(content)) {
|
|
|
|
|
- if (this.isMuted) {
|
|
|
|
|
- let err_code = 0;
|
|
|
|
|
- // if (Number(textItem.from) > 1) {
|
|
|
|
|
- // let headers = {
|
|
|
|
|
- // Authorization: this.token,
|
|
|
|
|
- // "Content-Type": "application/json",
|
|
|
|
|
- // };
|
|
|
|
|
- // if (this.sign) {
|
|
|
|
|
- // headers["X-Heijing-Verf"] = this.sign();
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- // const response = await fetch(this.CensorURL, {
|
|
|
|
|
- // method: "post",
|
|
|
|
|
- // headers,
|
|
|
|
|
- // body: JSON.stringify({ text: content, is_strict: this.isStrict }),
|
|
|
|
|
- // }).catch((err) => {
|
|
|
|
|
- // console.warn(err);
|
|
|
|
|
- // this._eventbus.emit("error", err);
|
|
|
|
|
- // });
|
|
|
|
|
- // const result = await response.json();
|
|
|
|
|
- // err_code = result.err_code;
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- if (_record_id !== this.record_id) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (err_code === 0) {
|
|
|
|
|
- this.storedTTS.push({
|
|
|
|
|
- text: textItem.text,
|
|
|
|
|
- audio: "",
|
|
|
|
|
- expression_anim: "",
|
|
|
|
|
- teeth_anim: "",
|
|
|
|
|
- isFormChat: this.formChatting,
|
|
|
|
|
- });
|
|
|
|
|
- this.storedTexts.shift();
|
|
|
|
|
- } else {
|
|
|
|
|
- this.handleSensitiveTTS({ err_code }, textItem.text);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- let options: OptionsType = {
|
|
|
|
|
- is_strict: this.isStrict,
|
|
|
|
|
- };
|
|
|
|
|
- if (Number(textItem.from) <= 1) {
|
|
|
|
|
- // 假如大模型判断的风险等级小于 1,则需要禁用敏感词过滤
|
|
|
|
|
- options.disable_censor = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (content == "" && textItem.text != "") {
|
|
|
|
|
- if (taskId) {
|
|
|
|
|
- this.storedTTS.push({
|
|
|
|
|
- text: "",
|
|
|
|
|
- audio: "",
|
|
|
|
|
- expression_anim: "",
|
|
|
|
|
- teeth_anim: "",
|
|
|
|
|
- isFormChat: true,
|
|
|
|
|
- });
|
|
|
|
|
- this.turnFormRequest({ taskId });
|
|
|
|
|
- } else {
|
|
|
|
|
- this.storedTTS.push({
|
|
|
|
|
- text: textItem.text,
|
|
|
|
|
- audio: "",
|
|
|
|
|
- expression_anim: "",
|
|
|
|
|
- teeth_anim: "",
|
|
|
|
|
- isFormChat: false,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- this.storedTexts.shift();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- // const result = await this.fetchTTS(content, options);
|
|
|
|
|
- // if (result.err_code !== 0 || _record_id !== this.record_id) {
|
|
|
|
|
- // return;
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- // this.handleSensitiveTTS({ err_code: 0 }, textItem.text);
|
|
|
|
|
-
|
|
|
|
|
- // let { audio, expression_anim, teeth_anim } = result.ret;
|
|
|
|
|
- this.storedTTS.push({
|
|
|
|
|
- text: textItem.text,
|
|
|
|
|
- audio: "",
|
|
|
|
|
- expression_anim: "",
|
|
|
|
|
- teeth_anim: "",
|
|
|
|
|
- isFormChat: this.formChatting,
|
|
|
|
|
- });
|
|
|
|
|
- this.storedTexts.shift();
|
|
|
|
|
- // Promise.all([
|
|
|
|
|
- // Resources.audioFetcher.fetch(audio),
|
|
|
|
|
- // Resources.ttsTeethAnimateFetcher.fetch(teeth_anim),
|
|
|
|
|
- // Resources.ttsEmoAnimateFetcher.fetch(expression_anim),
|
|
|
|
|
- // ]);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- this.storedTexts.shift();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.totalResponse += textItem.text;
|
|
|
|
|
- this.isGettingTTS = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- this.storedTexts.shift();
|
|
|
|
|
- this.isGettingTTS = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- checkIfCanBeConvertedToTTS(content) {
|
|
|
|
|
- let canBeConverted = false;
|
|
|
|
|
- const regex = /[\u4e00-\u9fa5A-Za-z0-9]/;
|
|
|
|
|
-
|
|
|
|
|
- if (regex.test(content)) {
|
|
|
|
|
- canBeConverted = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return canBeConverted;
|
|
|
|
|
- }
|
|
|
|
|
- handleSensitiveTTS(result, text) {
|
|
|
|
|
- let errorText = "抱歉,我不能回答该问题。你可以问其他问题。";
|
|
|
|
|
- if (text === errorText) {
|
|
|
|
|
- return;
|
|
|
|
|
- } else {
|
|
|
|
|
- text = result.err_msg || errorText;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("info", text);
|
|
|
|
|
-
|
|
|
|
|
- this.storeTotalResponse({ answer: text });
|
|
|
|
|
-
|
|
|
|
|
- console.warn("text error", text);
|
|
|
|
|
- this.storedTexts = [
|
|
|
|
|
- {
|
|
|
|
|
- text: errorText,
|
|
|
|
|
- isFinished: true,
|
|
|
|
|
- itemCount: errorText.length,
|
|
|
|
|
- },
|
|
|
|
|
- ];
|
|
|
|
|
- this.storedTTS = [];
|
|
|
|
|
- this.isGettingTTS = false;
|
|
|
|
|
- this.abortConnection();
|
|
|
|
|
- }
|
|
|
|
|
- async checkAvailableTTS() {
|
|
|
|
|
- if (this.storedTTS.length) {
|
|
|
|
|
- // if (!this.isSpeaking) {
|
|
|
|
|
- this.isSpeaking = true;
|
|
|
|
|
- let ttsItem = this.storedTTS.shift();
|
|
|
|
|
- if (ttsItem) {
|
|
|
|
|
- this._eventbus.emit("tts", ttsItem);
|
|
|
|
|
- }
|
|
|
|
|
- // }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- cancelSpeakingState() {
|
|
|
|
|
- this.isSpeaking = false;
|
|
|
|
|
- }
|
|
|
|
|
- changeMuteStatus(status: boolean) {
|
|
|
|
|
- this.isMuted = status;
|
|
|
|
|
- }
|
|
|
|
|
- checkAvailableResponse() {
|
|
|
|
|
- if (this.AIResponseQueueTimer) {
|
|
|
|
|
- clearInterval(this.AIResponseQueueTimer);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let isAllTextFinished = false;
|
|
|
|
|
- let isAllTTSFinished = false;
|
|
|
|
|
- this.AIResponseQueueTimer = setInterval(async () => {
|
|
|
|
|
- if (this.storedTexts.length) {
|
|
|
|
|
- this.checkAvailableTexts();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (this.storedTTS.length) {
|
|
|
|
|
- this.checkAvailableTTS();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- isAllTextFinished = this.generating == false && this.storedTexts.length == 0;
|
|
|
|
|
- isAllTTSFinished = isAllTextFinished && this.storedTTS.length == 0; //&& this.storedResponse.length == 0
|
|
|
|
|
-
|
|
|
|
|
- // if (isAllTextFinished && this.isFinishEventEmitted === false) {
|
|
|
|
|
- // this._eventbus.emit('totalResponse', { question: this.question, answer: this.totalResponse, record_id: this.record_id })
|
|
|
|
|
- // this.totalResponse = ''
|
|
|
|
|
- // this.isFinishEventEmitted = true
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- if (!this.isSpeaking && isAllTTSFinished) {
|
|
|
|
|
- console.log("checking all finished", this.loading);
|
|
|
|
|
- this.loading = false;
|
|
|
|
|
- this._eventbus.emit("loadingState", { type: "loading", state: false });
|
|
|
|
|
- if (this.AIResponseQueueTimer) {
|
|
|
|
|
- clearInterval(this.AIResponseQueueTimer);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }, 20);
|
|
|
|
|
- }
|
|
|
|
|
- checkSilenceMode(char: string): string {
|
|
|
|
|
- if (this.silenceMode) {
|
|
|
|
|
- this.silenceBuffer += char;
|
|
|
|
|
- if (this.silenceBuffer === "</silence>") {
|
|
|
|
|
- this.silenceMode = false;
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- } else if (!"</silence>".startsWith(this.silenceBuffer)) {
|
|
|
|
|
- // Mismatch in silence mode: swallow buffer but check if last char starts new tag
|
|
|
|
|
- this.silenceBuffer = char === "<" ? "<" : "";
|
|
|
|
|
- }
|
|
|
|
|
- return "";
|
|
|
|
|
- } else {
|
|
|
|
|
- if (this.silenceBuffer.length > 0) {
|
|
|
|
|
- this.silenceBuffer += char;
|
|
|
|
|
- if (this.silenceBuffer === "<silence>") {
|
|
|
|
|
- this.silenceMode = true;
|
|
|
|
|
- this.silenceBuffer = "";
|
|
|
|
|
- return "";
|
|
|
|
|
- } else if (!"<silence>".startsWith(this.silenceBuffer)) {
|
|
|
|
|
- // Mismatch in normal mode: release buffer
|
|
|
|
|
- let output = this.silenceBuffer.slice(0, -1);
|
|
|
|
|
- const lastChar = this.silenceBuffer.slice(-1);
|
|
|
|
|
- this.silenceBuffer = lastChar === "<" ? "<" : "";
|
|
|
|
|
- if (this.silenceBuffer === "") {
|
|
|
|
|
- output += lastChar;
|
|
|
|
|
- }
|
|
|
|
|
- return output;
|
|
|
|
|
- }
|
|
|
|
|
- return "";
|
|
|
|
|
- } else {
|
|
|
|
|
- if (char === "<") {
|
|
|
|
|
- this.silenceBuffer = "<";
|
|
|
|
|
- return "";
|
|
|
|
|
- } else {
|
|
|
|
|
- return char;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- checkScopeRules(text: string): {
|
|
|
|
|
- isScopeStart: boolean;
|
|
|
|
|
- isScopeEnd: boolean;
|
|
|
|
|
- isAllCheckFailed: boolean;
|
|
|
|
|
- } {
|
|
|
|
|
- // 需要判断 <silence></silence>
|
|
|
|
|
- let isScopeStart = false;
|
|
|
|
|
- let isScopeEnd = false;
|
|
|
|
|
- let isScopeStartCheckFailed = false;
|
|
|
|
|
- let isScopeEndCheckFailed = false;
|
|
|
|
|
-
|
|
|
|
|
- const startTag = "<silence>";
|
|
|
|
|
- const endTag = "</silence>";
|
|
|
|
|
-
|
|
|
|
|
- const lastIndex = this.scopeBufferArray.length - 1;
|
|
|
|
|
- const lastBuffer = this.scopeBufferArray[lastIndex] || "";
|
|
|
|
|
- if (text.includes(startTag) || lastBuffer.includes(startTag)) {
|
|
|
|
|
- isScopeStart = true;
|
|
|
|
|
- }
|
|
|
|
|
- if (text.includes(endTag) || lastBuffer.includes(endTag)) {
|
|
|
|
|
- isScopeEnd = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!isScopeStart && lastBuffer.length >= startTag.length) {
|
|
|
|
|
- isScopeStartCheckFailed = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- isScopeStart,
|
|
|
|
|
- isScopeEnd,
|
|
|
|
|
- isAllCheckFailed: isScopeStartCheckFailed || isScopeEndCheckFailed,
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- async handleServerPreprocessInfo(response: Response) {
|
|
|
|
|
- let errorTips = "";
|
|
|
|
|
- if (response.status === 401) {
|
|
|
|
|
- this._eventbus.emit("reLogin");
|
|
|
|
|
- errorTips = "need to reLogin";
|
|
|
|
|
- } else if (response.headers.get("Content-Type").startsWith("application/json")) {
|
|
|
|
|
- let stream = response.body;
|
|
|
|
|
- const reader = stream.getReader();
|
|
|
|
|
- let result = await reader.read();
|
|
|
|
|
-
|
|
|
|
|
- let value = result.value;
|
|
|
|
|
- let targetString = new TextDecoder().decode(value);
|
|
|
|
|
- let errInfo = JSON.parse(targetString);
|
|
|
|
|
-
|
|
|
|
|
- errorTips = errInfo.err_msg;
|
|
|
|
|
- this._eventbus.emit("info", errorTips);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return errorTips;
|
|
|
|
|
- }
|
|
|
|
|
- storeTotalResponse(payload = {}) {
|
|
|
|
|
- if ((this.totalResponse || payload.answer) && !this.isFinishEventEmitted) {
|
|
|
|
|
- if (payload.answer) {
|
|
|
|
|
- this.totalResponse = payload.answer;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._eventbus.emit("totalResponse", {
|
|
|
|
|
- question: this.question,
|
|
|
|
|
- answer: this.totalResponse,
|
|
|
|
|
- record_id: this.record_id,
|
|
|
|
|
- session: this.session,
|
|
|
|
|
- task_id: this.formTaskId,
|
|
|
|
|
- knowledge_ids: this.knowledge_ids,
|
|
|
|
|
- ...payload,
|
|
|
|
|
- });
|
|
|
|
|
- this.totalResponse = "";
|
|
|
|
|
- this.knowledge_ids = [];
|
|
|
|
|
- this.isFinishEventEmitted = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- isTotalResponseSaved() {
|
|
|
|
|
- return this.isFinishEventEmitted;
|
|
|
|
|
- }
|
|
|
|
|
- async turnFormRequest(payload) {
|
|
|
|
|
- this.stopGenerate();
|
|
|
|
|
- await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
|
- const { taskId, question } = payload;
|
|
|
|
|
- this.formChatting = true;
|
|
|
|
|
- this.formTaskId = taskId;
|
|
|
|
|
- this.generateAnswer("");
|
|
|
|
|
- this.question = question;
|
|
|
|
|
- }
|
|
|
|
|
- async turnChatRequest(continueChat = false) {
|
|
|
|
|
- console.log("turn chat request");
|
|
|
|
|
- this.formChatting = false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- parseFollowupSuggestions(schema: string): FollowUpSuggestion[] {
|
|
|
|
|
- if (!schema) {
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const parsed = JSON.parse(schema);
|
|
|
|
|
- const data = Array.isArray(parsed?.data) ? parsed.data : [];
|
|
|
|
|
- return data
|
|
|
|
|
- .map((item: any) => ({
|
|
|
|
|
- text: String(item?.text || "").trim(),
|
|
|
|
|
- intent: item?.intent ? String(item.intent).trim() : undefined,
|
|
|
|
|
- }))
|
|
|
|
|
- .filter((item: FollowUpSuggestion) => item.text);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.warn("Failed to parse followup suggestions", error);
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|