@blueking/ai-ui-sdk 0.0.1-beta.1 → 0.0.1-beta.2
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/dist/common/chart-helper.d.ts +32 -0
- package/dist/common/util.d.ts +12 -0
- package/dist/hooks/use-reference-doc.d.ts +14 -0
- package/dist/hooks/use-think.d.ts +21 -0
- package/dist/main.d.ts +59 -0
- package/dist/main.js +195 -0
- package/dist/types/enum.d.ts +54 -0
- package/dist/types/type.d.ts +26 -0
- package/package.json +10 -3
- package/bk.config.js +0 -21
- package/src/common/chart-helper.ts +0 -156
- package/src/common/util.ts +0 -41
- package/src/hooks/use-reference-doc.ts +0 -205
- package/src/hooks/use-think.ts +0 -156
- package/src/main.ts +0 -227
- package/src/types/enum.ts +0 -58
- package/src/types/type.ts +0 -33
@@ -0,0 +1,32 @@
|
|
1
|
+
import type { Document } from '../types/type';
|
2
|
+
type HandleStart = (sessionCode: string) => any;
|
3
|
+
type HandleText = (sessionCode: string, message: string, cover?: boolean) => void;
|
4
|
+
type HandleReferenceDoc = (sessionCode: string, documents: Document[], cover?: boolean) => void;
|
5
|
+
type HandleThink = (sessionCode: string, content: string, cover?: boolean, elapsed_time?: number) => void;
|
6
|
+
type HandleEnd = (sessionCode: string, message?: string) => void;
|
7
|
+
type HandleError = (sessionCode: string, message: string, code: string) => any;
|
8
|
+
export declare class ChatHelper {
|
9
|
+
handleStart: HandleStart;
|
10
|
+
handleText: HandleText;
|
11
|
+
handleReferenceDoc?: HandleReferenceDoc;
|
12
|
+
handleThink?: HandleThink;
|
13
|
+
handleEnd: HandleEnd;
|
14
|
+
handleError: HandleError;
|
15
|
+
controllerMap: Record<string, AbortController>;
|
16
|
+
constructor({ handleStart, handleText, handleReferenceDoc, handleThink, handleEnd, handleError, }: {
|
17
|
+
handleStart: HandleStart;
|
18
|
+
handleText: HandleText;
|
19
|
+
handleReferenceDoc?: HandleReferenceDoc;
|
20
|
+
handleThink?: HandleThink;
|
21
|
+
handleEnd: HandleEnd;
|
22
|
+
handleError: HandleError;
|
23
|
+
});
|
24
|
+
stream({ sessionCode, url, headers, param, }: {
|
25
|
+
sessionCode: string;
|
26
|
+
url: string;
|
27
|
+
headers?: Record<string, string>;
|
28
|
+
param?: Record<string, any>;
|
29
|
+
}): Promise<void>;
|
30
|
+
stop(sessionCode: string): void;
|
31
|
+
}
|
32
|
+
export {};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* 判断是否是 JSON 字符串
|
3
|
+
* @param str 字符串
|
4
|
+
* @returns 是否是 JSON 字符串
|
5
|
+
*/
|
6
|
+
export declare const isJSON: (str: string) => boolean;
|
7
|
+
/**
|
8
|
+
* 响应时间格式化
|
9
|
+
* @param val 待格式化时间,xxms
|
10
|
+
* @returns 格式化后的时间,xx小时xx分钟xx秒
|
11
|
+
*/
|
12
|
+
export declare function durationFormatter(val: number): string;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import type { Document } from '../types/type';
|
2
|
+
/**
|
3
|
+
* 获取引用资料的 HTML 内容
|
4
|
+
* @param documents
|
5
|
+
* @returns
|
6
|
+
*/
|
7
|
+
export declare const getHtmlContentFromDocuments: (documents: Document[]) => string;
|
8
|
+
/**
|
9
|
+
* 移除引用资料的 HTML 内容
|
10
|
+
* @param content
|
11
|
+
* @returns
|
12
|
+
*/
|
13
|
+
export declare const removeReferenceDoc: (content: string) => string;
|
14
|
+
export declare const useReferenceDoc: () => void;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/**
|
2
|
+
* 获取思考的 HTML 内容
|
3
|
+
* @param chatContent 聊天内容
|
4
|
+
* @param content 思考内容
|
5
|
+
* @param elapsedTime 思考时间
|
6
|
+
* @returns 完整的 chatContent
|
7
|
+
*/
|
8
|
+
export declare const getHtmlContentFromThink: (chatContent: string, content: string, cover?: boolean, elapsedTime?: number) => string;
|
9
|
+
/**
|
10
|
+
* 移除思考的 HTML 内容
|
11
|
+
* @param content
|
12
|
+
* @returns
|
13
|
+
*/
|
14
|
+
export declare const removeThink: (content: string) => string;
|
15
|
+
/**
|
16
|
+
* 判断是否是思考中
|
17
|
+
* @param content
|
18
|
+
* @returns
|
19
|
+
*/
|
20
|
+
export declare const isThinking: (content: string) => boolean | null;
|
21
|
+
export declare const useThink: () => void;
|
package/dist/main.d.ts
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
import type { ISessionContent } from './types/type.ts';
|
2
|
+
import { SessionContentRole, SessionContentStatus } from './types/enum';
|
3
|
+
type SessionContentsMap = {
|
4
|
+
[key: string]: ISessionContent[];
|
5
|
+
};
|
6
|
+
type SessionLoadingMap = {
|
7
|
+
[key: string]: boolean;
|
8
|
+
};
|
9
|
+
export type { ISessionContent, };
|
10
|
+
export { SessionContentRole, SessionContentStatus, };
|
11
|
+
export interface AICallbacks {
|
12
|
+
handleStart?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
13
|
+
handleText?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
14
|
+
handleReferenceDoc?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
15
|
+
handleThink?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
16
|
+
handleEnd?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
17
|
+
handleError?: (sessionCode: string, code: string | undefined, sessionContent: ISessionContent) => void;
|
18
|
+
}
|
19
|
+
export declare const useAI: ({ handleStart, handleText, handleReferenceDoc, handleThink, handleEnd, handleError, }: AICallbacks) => {
|
20
|
+
sessionContents: import("vue").Ref<{
|
21
|
+
id?: number | undefined;
|
22
|
+
createdAt?: string | undefined;
|
23
|
+
createdBy?: string | undefined;
|
24
|
+
updatedAt?: string | undefined;
|
25
|
+
updatedBy?: string | undefined;
|
26
|
+
spaceId?: string | undefined;
|
27
|
+
liked?: boolean | undefined;
|
28
|
+
role: SessionContentRole;
|
29
|
+
content: string;
|
30
|
+
rate?: number | undefined;
|
31
|
+
status?: SessionContentStatus | undefined;
|
32
|
+
sessionCode?: string | undefined;
|
33
|
+
}[], ISessionContent[] | {
|
34
|
+
id?: number | undefined;
|
35
|
+
createdAt?: string | undefined;
|
36
|
+
createdBy?: string | undefined;
|
37
|
+
updatedAt?: string | undefined;
|
38
|
+
updatedBy?: string | undefined;
|
39
|
+
spaceId?: string | undefined;
|
40
|
+
liked?: boolean | undefined;
|
41
|
+
role: SessionContentRole;
|
42
|
+
content: string;
|
43
|
+
rate?: number | undefined;
|
44
|
+
status?: SessionContentStatus | undefined;
|
45
|
+
sessionCode?: string | undefined;
|
46
|
+
}[]>;
|
47
|
+
sessionContentsMap: SessionContentsMap;
|
48
|
+
sessionLoadingMap: import("vue").Ref<SessionLoadingMap, SessionLoadingMap>;
|
49
|
+
chat: ({ sessionCode, param, url, headers, }: {
|
50
|
+
sessionCode: string;
|
51
|
+
param?: Record<string, any>;
|
52
|
+
url: string;
|
53
|
+
headers?: Record<string, string>;
|
54
|
+
}) => void;
|
55
|
+
getSessionContentBySessionCode: (sessionCode: string) => ISessionContent;
|
56
|
+
getSessionContentsBySessionCode: (sessionCode: string) => ISessionContent[];
|
57
|
+
changeSessionCode: (sessionCode: string) => void;
|
58
|
+
setSessionContents: (data: ISessionContent[]) => void;
|
59
|
+
};
|
package/dist/main.js
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue")):"function"==typeof define&&define.amd?define(["vue"],t):"object"==typeof exports?exports["ai-ui-sdk"]=t(require("vue")):e["ai-ui-sdk"]=t(e.vue)}("undefined"!=typeof self?self:this,e=>(()=>{"use strict";var t,n,o,i,r={380:t=>{t.exports=e}},a={};function l(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}};return r[e](n,n.exports,l),n.exports}l.d=(e,t)=>{for(var n in t)l.o(t,n)&&!l.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},l.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),l.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.p="/";var d={};l.r(d),l.d(d,{SessionContentRole:()=>o,SessionContentStatus:()=>n,default:()=>w,useAI:()=>y});var s={};l.r(s),l.d(s,{In:()=>o,c_:()=>n,OD:()=>y});var c=l(380);!function(e){e.System="system",e.Assistant="assistant",e.User="user",e.Guide="guide",e.Pause="pause",e.UserImage="user-image",e.Hidden="hidden",e.HiddenUser="hidden-user",e.HiddenAssistant="hidden-assistant",e.HiddenSystem="hidden-system",e.HiddenGuide="hidden-guide",e.TemplateUser="template-user",e.TemplateAssistant="template-assistant",e.TemplateSystem="template-system",e.TemplateGuide="template-guide",e.TemplateHidden="template-hidden"}(t||(t={})),function(e){e.Fail="fail",e.Loading="loading",e.Success="success"}(n||(n={})),function(e){e.Ai="ai",e.User="user",e.Time="time",e.System="system",e.Role="role",e.Hidden="hidden",e.Guide="guide",e.Pause="pause",e.TokenExpired="token-expired",e.UserImage="user-image",e.HiddenUser="hidden-user",e.HiddenAi="hidden-ai",e.HiddenRole="hidden-role",e.HiddenGuide="hidden-guide",e.TemplateUser="template-user",e.TemplateAi="template-ai",e.TemplateRole="template-role",e.TemplateGuide="template-guide",e.TemplateHidden="template-hidden",e.ImageNotSupported="image-not-supported"}(o||(o={})),function(e){e.TokenExpired="80003",e.ImageNotSupported="82003",e.UnAuthorizedPreviewFile="80401",e.TagRepeat="1513405",e[e.Aborted=20]="Aborted",e[e.UnLogin=401]="UnLogin",e.IamNoPermission="IAM_NO_PERMISSION"}(i||(i={}));let u=e=>{let t=`<section class="knowledge-head click-close">
|
2
|
+
<svg
|
3
|
+
class="bkaidev-wenzhang"
|
4
|
+
>
|
5
|
+
<use href="#bkaidev-wenzhang"></use>
|
6
|
+
</svg>
|
7
|
+
找到 ${e.length} 篇资料参考
|
8
|
+
<i class="bkaidev-icon bkaidev-angle-up"></i>
|
9
|
+
</section>
|
10
|
+
<ul class="knowledge-body">`;return e.forEach(e=>{let{file_path:n,preview_path:o}=e.metadata,i=n.split("/").pop();t+=`<li
|
11
|
+
class="knowledge-item"
|
12
|
+
title="${i} (${o})"
|
13
|
+
>
|
14
|
+
<i class="bkaidev-icon bkaidev-zhishiku"></i>
|
15
|
+
<a href="${o}" target="_blank" class="knowledge-link g-flex-truncate">
|
16
|
+
${i}
|
17
|
+
</a>
|
18
|
+
<span class="knowledge-path g-flex-truncate">${n}</span>
|
19
|
+
</li>`}),t+="</ul>"},p=()=>{let e=null,t=()=>{let t=`.knowledge-tips {
|
20
|
+
position: relative;
|
21
|
+
padding: 6px 8px;
|
22
|
+
margin-bottom: 14px;
|
23
|
+
line-height: 22px;
|
24
|
+
background: #f5f7fa;
|
25
|
+
border-radius: 4px;
|
26
|
+
|
27
|
+
&.closed {
|
28
|
+
.bkaidev-angle-up {
|
29
|
+
transform: rotate(180deg);
|
30
|
+
}
|
31
|
+
|
32
|
+
.knowledge-summary {
|
33
|
+
margin-bottom: 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
.knowledge-link {
|
37
|
+
display: none;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
.bkaidev-angle-up {
|
42
|
+
position: absolute;
|
43
|
+
top: 4px;
|
44
|
+
right: 0px;
|
45
|
+
font-size: 22px;
|
46
|
+
color: #979ba5;
|
47
|
+
cursor: pointer;
|
48
|
+
}
|
49
|
+
|
50
|
+
.bkaidev-help-document {
|
51
|
+
margin-right: 4px;
|
52
|
+
font-size: 19px;
|
53
|
+
color: #3a84ff;
|
54
|
+
}
|
55
|
+
|
56
|
+
.knowledge-summary {
|
57
|
+
display: block;
|
58
|
+
margin-bottom: 4px;
|
59
|
+
color: #313238;
|
60
|
+
}
|
61
|
+
|
62
|
+
.knowledge-link {
|
63
|
+
display: block;
|
64
|
+
color: #3a84ff;
|
65
|
+
text-decoration: none;
|
66
|
+
cursor: pointer;
|
67
|
+
|
68
|
+
.bkaidev-cc-jump-link {
|
69
|
+
font-size: 14px;
|
70
|
+
}
|
71
|
+
|
72
|
+
&:last-child {
|
73
|
+
margin-bottom: 2px;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}`,n=`.knowledge-head {
|
77
|
+
display: flex;
|
78
|
+
align-items: center;
|
79
|
+
padding: 0 8px 0 6px;
|
80
|
+
margin-bottom: 8px;
|
81
|
+
line-height: 28px;
|
82
|
+
background: #F0F1F5;
|
83
|
+
border-radius: 4px;
|
84
|
+
font-size: 12px;
|
85
|
+
color: #313238;
|
86
|
+
cursor: pointer;
|
87
|
+
width: fit-content;
|
88
|
+
|
89
|
+
.bkaidev-wenzhang {
|
90
|
+
width: 14px;
|
91
|
+
height: 14px;
|
92
|
+
margin-right: 6px;
|
93
|
+
}
|
94
|
+
|
95
|
+
.bkaidev-angle-up {
|
96
|
+
font-size: 22px;
|
97
|
+
color: #4D4F56;
|
98
|
+
margin-left: 4px;
|
99
|
+
}
|
100
|
+
|
101
|
+
&.closed {
|
102
|
+
margin-bottom: 16px;
|
103
|
+
|
104
|
+
.bkaidev-angle-up {
|
105
|
+
transform: rotate(180deg);
|
106
|
+
}
|
107
|
+
|
108
|
+
&~ .knowledge-body {
|
109
|
+
display: none;
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
&:hover {
|
114
|
+
background: #EAEBF0;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
.knowledge-body {
|
118
|
+
background: #F5F7FA;
|
119
|
+
padding: 16px !important;
|
120
|
+
.knowledge-item {
|
121
|
+
display: flex;
|
122
|
+
align-items: center;
|
123
|
+
line-height: 28px;
|
124
|
+
a {
|
125
|
+
margin-right: 20px;
|
126
|
+
}
|
127
|
+
.bkaidev-zhishiku {
|
128
|
+
color: #D66F6B;
|
129
|
+
font-size: 14px;
|
130
|
+
margin-right: 6px;
|
131
|
+
}
|
132
|
+
.knowledge-path {
|
133
|
+
margin-left: auto;
|
134
|
+
color: #979BA5;
|
135
|
+
text-align: right;
|
136
|
+
}
|
137
|
+
&:hover {
|
138
|
+
background: #EAEBF0;
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}`;(e=document.createElement("style")).textContent=t+n,document.head.appendChild(e)},n=()=>{e&&(e.remove(),e=null)};(0,c.onBeforeMount)(()=>{t()}),(0,c.onBeforeUnmount)(()=>{n()})},h=e=>{try{return JSON.parse(e),!0}catch(e){return!1}},g=(e,t,n,o)=>{let i="内容正在生成中..."===e||n?"":e;if(i.includes('<section class="think-head click-close">')){let e=i.match(/<section class="think-body">([\s\S]*?)<\/section>/);if(e){let n=e[1],o=n.replace(/\n$/g,`${t}
|
142
|
+
`);i=i.replace(n,o)}}else i+=`<section class="think-head click-close">
|
143
|
+
<i class="bkaidev-icon bkaidev-sikao"></i>思考中...<i class="bkaidev-icon bkaidev-angle-up"></i>
|
144
|
+
</section>
|
145
|
+
<section class="think-body">
|
146
|
+
${t}
|
147
|
+
</section>`;return o&&(i=i.replace("思考中...",`已完成思考 (耗时:${function(e){let t=Math.floor(e/36e5),n=Math.floor(e%36e5/6e4),o=Math.floor(e%6e4/1e3),i=e%1e3,r=[];return t>0&&r.push(`${t}h`),n>0&&r.push(`${n}min`),o>0&&r.push(`${o}s`),i>0&&r.push(`${i.toFixed(2)}ms`),r.join(" ")}(o)})`)),i},f=e=>{let t="正在思考..."===e,n=e.match(/<section class="think-body">([\s\S]*?)<\/section>$/),o=n&&""===n[1].trim();return t||o},m=()=>{let e=null,t=()=>{let t=`.think-head {
|
148
|
+
display: flex;
|
149
|
+
align-items: center;
|
150
|
+
padding: 0 8px 0 6px;
|
151
|
+
margin-bottom: 8px;
|
152
|
+
line-height: 28px;
|
153
|
+
background: #F0F1F5;
|
154
|
+
border-radius: 4px;
|
155
|
+
font-size: 12px;
|
156
|
+
color: #313238;
|
157
|
+
cursor: pointer;
|
158
|
+
width: fit-content;
|
159
|
+
.bkaidev-sikao {
|
160
|
+
font-size: 14px;
|
161
|
+
color: #979ba5;
|
162
|
+
margin-right: 4px;
|
163
|
+
}
|
164
|
+
|
165
|
+
.bkaidev-angle-up {
|
166
|
+
font-size: 22px;
|
167
|
+
color: #4D4F56;
|
168
|
+
margin-left: 4px;
|
169
|
+
}
|
170
|
+
|
171
|
+
&.closed {
|
172
|
+
margin-bottom: 16px;
|
173
|
+
|
174
|
+
.bkaidev-angle-up {
|
175
|
+
transform: rotate(180deg);
|
176
|
+
}
|
177
|
+
|
178
|
+
&~ .think-body {
|
179
|
+
display: none;
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
&:hover {
|
184
|
+
background: #EAEBF0;
|
185
|
+
}
|
186
|
+
}
|
187
|
+
.think-body {
|
188
|
+
background: #F5F7FA;
|
189
|
+
color: #979BA5;
|
190
|
+
padding: 16px;
|
191
|
+
margin-bottom: 16px;
|
192
|
+
&~ .knowledge-head {
|
193
|
+
margin-bottom: 8px;
|
194
|
+
}
|
195
|
+
}`;(e=document.createElement("style")).textContent=t,document.head.appendChild(e)},n=()=>{e&&(e.remove(),e=null)};(0,c.onBeforeMount)(()=>{t()}),(0,c.onBeforeUnmount)(()=>{n()})};function b(e,t,n,o,i,r,a){try{var l=e[r](a),d=l.value}catch(e){n(e);return}l.done?t(d):Promise.resolve(d).then(o,i)}function k(e){return function(){var t=this,n=arguments;return new Promise(function(o,i){var r=e.apply(t,n);function a(e){b(r,o,i,a,l,"next",e)}function l(e){b(r,o,i,a,l,"throw",e)}a(void 0)})}}function v(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class x{stream({sessionCode:e,url:t,headers:n,param:o}){var i=this;return k(function*(){var r,a;yield null===(r=i.handleStart)||void 0===r?void 0:r.call(i,e);let l=new AbortController;i.controllerMap[e]=l,fetch(t,{method:"post",signal:l.signal,headers:function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},o=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(o=o.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),o.forEach(function(t){v(e,t,n[t])})}return e}({"Content-Type":"application/json"},n),mode:"cors",credentials:"include",body:JSON.stringify(o)}).then((a=k(function*(t){let n=t.body.pipeThrough(new window.TextDecoderStream).getReader(),o="";for(;;)try{let{value:r,done:a}=yield n.read();if(!t.ok){i.handleError(e,r||t.statusText,t.status);break}if(a){i.handleEnd(e);break}(o+r.toString()).split("\n").forEach(n=>{let r=n.replace("data:","").trim();if(h(r)){var a,l;let{event:n,content:d,cover:s,documents:c,result:u,code:p,elapsed_time:h,message:g}=JSON.parse(r);if(!1===u||200!==t.status){i.handleError(e,g||"模型调用失败",p);return}switch(n){case"text":i.handleText(e,d,s);break;case"reference_doc":null===(a=i.handleReferenceDoc)||void 0===a||a.call(i,e,c,s);break;case"think":null===(l=i.handleThink)||void 0===l||l.call(i,e,d,s,h);break;case"done":i.handleEnd(e,s?d:"");break;case"error":i.handleError(e,g||"模型调用失败",p)}o=""}else r&&(o=r)})}catch(t){(null==t?void 0:t.code)!==20&&i.handleError(e,`模型调用失败:${t.message}`,t.code);break}}),function(e){return a.apply(this,arguments)}))})()}stop(e){var t,n;return null===(n=this.controllerMap[e])||void 0===n||null===(t=n.abort)||void 0===t||t.call(n),this.handleEnd(e)}constructor({handleStart:e,handleText:t,handleReferenceDoc:n,handleThink:o,handleEnd:i,handleError:r}){v(this,"handleStart",void 0),v(this,"handleText",void 0),v(this,"handleReferenceDoc",void 0),v(this,"handleThink",void 0),v(this,"handleEnd",void 0),v(this,"handleError",void 0),v(this,"controllerMap",void 0),this.handleStart=e,this.handleText=t,this.handleReferenceDoc=n,this.handleThink=o,this.handleEnd=i,this.handleError=r,this.controllerMap={}}}let y=({handleStart:e,handleText:t,handleReferenceDoc:r,handleThink:a,handleEnd:l,handleError:d})=>{p(),m();let s="",h=(0,c.ref)({}),b=(0,c.ref)([]),k={},v=new x({handleStart:function(t){h.value[t]=!0;let i={sessionCode:t,role:o.Ai,status:n.Loading,content:"内容正在生成中..."};return!function(e,t){w(e).push(t)}(t,i),null==e?void 0:e(t,i)},handleText:function(e,o,i){let r=y(e);if("内容正在生成中..."===r.content)r.content=o;else{if(r.status!==n.Loading)return;r.content=i?o:r.content+o}return null==t?void 0:t(e,r)},handleReferenceDoc:function(e,t,n){let o=y(e),i=u(t);return o.content=n?i:o.content+i,null==r?void 0:r(e,o)},handleThink:function(e,t,n,o){let i=y(e);return i.content=g(i.content,t,n,o),null==a?void 0:a(e,i)},handleEnd:function(e,t){let o=y(e);if(o.status===n.Loading)return h.value[e]=!1,t&&(o.content=t),o.status=n.Success,null==l?void 0:l(e,o);("内容正在生成中..."===o.content||f(o.content))&&S(e,"聊天内容已中断")},handleError:S});function y(e){var t;return s===e?b.value.at(-1):null===(t=k[e])||void 0===t?void 0:t.at(-1)}function w(e){return s===e?b.value:k[e]}function S(e,t,r){let a=y(e);return a.status=n.Fail,a.content=t,h.value[e]=!1,r===i.TokenExpired&&(a.content="抱歉,您的剩余 Token 不足,无法返回回答内容,请先清空当前会话(上下文仍会作为历史记录保留))",a.role=o.TokenExpired),r===i.ImageNotSupported&&(a.content="抱歉,当前模型不支持图片内容解析",a.role=o.ImageNotSupported),null==d?void 0:d(e,r,a)}return{sessionContents:b,sessionContentsMap:k,sessionLoadingMap:h,chat:function({sessionCode:e,param:t,url:n,headers:o}){v.stream({sessionCode:e,url:n,param:t,headers:o})},getSessionContentBySessionCode:y,getSessionContentsBySessionCode:w,changeSessionCode:function(e){s=e,k[e]||(k[e]=[]),b.value=k[e]},setSessionContents:function(e){s&&(k[s]=e,b.value=e)}}},w=s.default;if("undefined"!=typeof window){let{currentScript:e}=window.document,t=e&&e.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);t&&(l.p=t[1])}return d})());
|
@@ -0,0 +1,54 @@
|
|
1
|
+
export declare enum SessionPromptRole {
|
2
|
+
System = "system",
|
3
|
+
Assistant = "assistant",
|
4
|
+
User = "user",
|
5
|
+
Guide = "guide",
|
6
|
+
Pause = "pause",
|
7
|
+
UserImage = "user-image",
|
8
|
+
Hidden = "hidden",
|
9
|
+
HiddenUser = "hidden-user",
|
10
|
+
HiddenAssistant = "hidden-assistant",
|
11
|
+
HiddenSystem = "hidden-system",
|
12
|
+
HiddenGuide = "hidden-guide",
|
13
|
+
TemplateUser = "template-user",
|
14
|
+
TemplateAssistant = "template-assistant",
|
15
|
+
TemplateSystem = "template-system",
|
16
|
+
TemplateGuide = "template-guide",
|
17
|
+
TemplateHidden = "template-hidden"
|
18
|
+
}
|
19
|
+
export declare enum SessionContentStatus {
|
20
|
+
Fail = "fail",
|
21
|
+
Loading = "loading",
|
22
|
+
Success = "success"
|
23
|
+
}
|
24
|
+
export declare enum SessionContentRole {
|
25
|
+
Ai = "ai",
|
26
|
+
User = "user",
|
27
|
+
Time = "time",
|
28
|
+
System = "system",
|
29
|
+
Role = "role",
|
30
|
+
Hidden = "hidden",
|
31
|
+
Guide = "guide",
|
32
|
+
Pause = "pause",
|
33
|
+
TokenExpired = "token-expired",
|
34
|
+
UserImage = "user-image",
|
35
|
+
HiddenUser = "hidden-user",
|
36
|
+
HiddenAi = "hidden-ai",
|
37
|
+
HiddenRole = "hidden-role",
|
38
|
+
HiddenGuide = "hidden-guide",
|
39
|
+
TemplateUser = "template-user",
|
40
|
+
TemplateAi = "template-ai",
|
41
|
+
TemplateRole = "template-role",
|
42
|
+
TemplateGuide = "template-guide",
|
43
|
+
TemplateHidden = "template-hidden",
|
44
|
+
ImageNotSupported = "image-not-supported"
|
45
|
+
}
|
46
|
+
export declare enum HttpErrorCode {
|
47
|
+
TokenExpired = "80003",
|
48
|
+
ImageNotSupported = "82003",
|
49
|
+
UnAuthorizedPreviewFile = "80401",
|
50
|
+
TagRepeat = "1513405",
|
51
|
+
Aborted = 20,
|
52
|
+
UnLogin = 401,
|
53
|
+
IamNoPermission = "IAM_NO_PERMISSION"
|
54
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { SessionContentRole, SessionPromptRole, SessionContentStatus } from './enum.ts';
|
2
|
+
export type Document = {
|
3
|
+
metadata: {
|
4
|
+
file_path: string;
|
5
|
+
path: string;
|
6
|
+
preview_path: string;
|
7
|
+
};
|
8
|
+
};
|
9
|
+
export interface ISessionContent {
|
10
|
+
id?: number;
|
11
|
+
createdAt?: string;
|
12
|
+
createdBy?: string;
|
13
|
+
updatedAt?: string;
|
14
|
+
updatedBy?: string;
|
15
|
+
spaceId?: string;
|
16
|
+
liked?: boolean;
|
17
|
+
role: SessionContentRole;
|
18
|
+
content: string;
|
19
|
+
rate?: number;
|
20
|
+
status?: SessionContentStatus;
|
21
|
+
sessionCode?: string;
|
22
|
+
}
|
23
|
+
export interface ISessionPrompt {
|
24
|
+
content: string;
|
25
|
+
role: SessionPromptRole;
|
26
|
+
}
|
package/package.json
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
{
|
2
2
|
"name": "@blueking/ai-ui-sdk",
|
3
|
-
"version": "0.0.1-beta.
|
3
|
+
"version": "0.0.1-beta.2",
|
4
4
|
"description": "蓝鲸AI UI SDK",
|
5
|
-
"main": "main.js",
|
5
|
+
"main": "dist/main.js",
|
6
|
+
"types": "dist/main.d.ts",
|
6
7
|
"scripts": {
|
7
|
-
"build": "bk-cli-service-webpack build"
|
8
|
+
"build": "bk-cli-service-webpack build && tsc --emitDeclarationOnly"
|
8
9
|
},
|
10
|
+
"files": [
|
11
|
+
"dist"
|
12
|
+
],
|
9
13
|
"keywords": [],
|
10
14
|
"author": "",
|
11
15
|
"license": "ISC",
|
12
16
|
"dependencies": {
|
13
17
|
"@blueking/cli-service-webpack": "0.0.7-beta.1",
|
14
18
|
"vue": "^3.5.13"
|
19
|
+
},
|
20
|
+
"devDependencies": {
|
21
|
+
"typescript": "^5.5.4"
|
15
22
|
}
|
16
23
|
}
|
package/bk.config.js
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
|
2
|
-
module.exports = {
|
3
|
-
host: process.env.BK_APP_HOST,
|
4
|
-
port: process.env.BK_APP_PORT,
|
5
|
-
publicPath: process.env.BK_STATIC_URL,
|
6
|
-
cache: false,
|
7
|
-
open: false,
|
8
|
-
replaceStatic: false,
|
9
|
-
target: 'lib',
|
10
|
-
splitChunk: false,
|
11
|
-
resource: {
|
12
|
-
main: {
|
13
|
-
entry: './src/main.ts',
|
14
|
-
},
|
15
|
-
},
|
16
|
-
configureWebpack: {
|
17
|
-
externals: {
|
18
|
-
'vue': 'vue',
|
19
|
-
}
|
20
|
-
}
|
21
|
-
};
|
@@ -1,156 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
isJSON,
|
3
|
-
} from './util.ts';
|
4
|
-
|
5
|
-
import type {
|
6
|
-
Document,
|
7
|
-
} from '../types/type.ts';
|
8
|
-
|
9
|
-
type HandleStart = (sessionCode: string) => any;
|
10
|
-
type HandleText = (sessionCode: string, message: string, cover?: boolean) => void;
|
11
|
-
type HandleReferenceDoc = (sessionCode: string, documents: Document[], cover?: boolean) => void;
|
12
|
-
type HandleThink = (sessionCode: string, content: string, cover?: boolean, elapsed_time?: number) => void;
|
13
|
-
type HandleEnd = (sessionCode: string, message?: string) => void;
|
14
|
-
type HandleError = (sessionCode: string, message: string, code: string) => any;
|
15
|
-
|
16
|
-
export class ChatHelper {
|
17
|
-
handleStart: HandleStart;
|
18
|
-
handleText: HandleText;
|
19
|
-
handleReferenceDoc?: HandleReferenceDoc;
|
20
|
-
handleThink?: HandleThink;
|
21
|
-
handleEnd: HandleEnd;
|
22
|
-
handleError: HandleError;
|
23
|
-
controllerMap: Record<string, AbortController>;
|
24
|
-
|
25
|
-
constructor({
|
26
|
-
handleStart,
|
27
|
-
handleText,
|
28
|
-
handleReferenceDoc,
|
29
|
-
handleThink,
|
30
|
-
handleEnd,
|
31
|
-
handleError,
|
32
|
-
}: {
|
33
|
-
handleStart: HandleStart;
|
34
|
-
handleText: HandleText;
|
35
|
-
handleReferenceDoc?: HandleReferenceDoc;
|
36
|
-
handleThink?: HandleThink;
|
37
|
-
handleEnd: HandleEnd;
|
38
|
-
handleError: HandleError;
|
39
|
-
}) {
|
40
|
-
this.handleStart = handleStart;
|
41
|
-
this.handleText = handleText;
|
42
|
-
this.handleReferenceDoc = handleReferenceDoc;
|
43
|
-
this.handleThink = handleThink;
|
44
|
-
this.handleEnd = handleEnd;
|
45
|
-
this.handleError = handleError;
|
46
|
-
this.controllerMap = {};
|
47
|
-
}
|
48
|
-
|
49
|
-
async stream({
|
50
|
-
sessionCode,
|
51
|
-
url,
|
52
|
-
headers,
|
53
|
-
param,
|
54
|
-
}: {
|
55
|
-
sessionCode: string;
|
56
|
-
url: string;
|
57
|
-
headers?: Record<string, string>;
|
58
|
-
param?: Record<string, any>;
|
59
|
-
}) {
|
60
|
-
// 开始
|
61
|
-
await this.handleStart?.(sessionCode);
|
62
|
-
// 记录 controller
|
63
|
-
const controller = new AbortController();
|
64
|
-
this.controllerMap[sessionCode] = controller;
|
65
|
-
// 发送请求
|
66
|
-
fetch(url, {
|
67
|
-
method: 'post',
|
68
|
-
signal: controller.signal,
|
69
|
-
headers: {
|
70
|
-
'Content-Type': 'application/json',
|
71
|
-
...headers,
|
72
|
-
},
|
73
|
-
mode: 'cors',
|
74
|
-
credentials: 'include',
|
75
|
-
body: JSON.stringify(param),
|
76
|
-
})
|
77
|
-
.then(async (response: any) => {
|
78
|
-
const reader = response.body
|
79
|
-
.pipeThrough(new window.TextDecoderStream())
|
80
|
-
.getReader();
|
81
|
-
|
82
|
-
// 临时存储数据
|
83
|
-
let temp = '';
|
84
|
-
|
85
|
-
while (true) {
|
86
|
-
try {
|
87
|
-
const { value, done } = await reader.read();
|
88
|
-
|
89
|
-
// 接口异常处理
|
90
|
-
if (!response.ok) {
|
91
|
-
this.handleError(sessionCode, value || response.statusText, response.status);
|
92
|
-
break;
|
93
|
-
}
|
94
|
-
// 接口完成
|
95
|
-
if (done) {
|
96
|
-
this.handleEnd(sessionCode);
|
97
|
-
break;
|
98
|
-
}
|
99
|
-
|
100
|
-
const values = (temp + value.toString()).split('\n');
|
101
|
-
values.forEach((value) => {
|
102
|
-
const item = value.replace('data:', '').trim();
|
103
|
-
if (isJSON(item)) {
|
104
|
-
const {
|
105
|
-
event,
|
106
|
-
content,
|
107
|
-
cover,
|
108
|
-
documents,
|
109
|
-
result,
|
110
|
-
code,
|
111
|
-
elapsed_time,
|
112
|
-
message,
|
113
|
-
} = JSON.parse(item);
|
114
|
-
// 业务错误处理
|
115
|
-
if (result === false || response.status !== 200) {
|
116
|
-
this.handleError(sessionCode, message || '模型调用失败', code);
|
117
|
-
return;
|
118
|
-
}
|
119
|
-
|
120
|
-
switch (event) {
|
121
|
-
case 'text':
|
122
|
-
this.handleText(sessionCode, content, cover);
|
123
|
-
break;
|
124
|
-
case 'reference_doc':
|
125
|
-
this.handleReferenceDoc?.(sessionCode, documents, cover);
|
126
|
-
break;
|
127
|
-
case 'think':
|
128
|
-
this.handleThink?.(sessionCode, content, cover, elapsed_time);
|
129
|
-
break;
|
130
|
-
case 'done':
|
131
|
-
this.handleEnd(sessionCode, cover ? content : '');
|
132
|
-
break;
|
133
|
-
case 'error':
|
134
|
-
this.handleError(sessionCode, message || '模型调用失败', code);
|
135
|
-
break;
|
136
|
-
}
|
137
|
-
temp = '';
|
138
|
-
} else if (item) {
|
139
|
-
temp = item;
|
140
|
-
}
|
141
|
-
});
|
142
|
-
} catch (error: any) {
|
143
|
-
if (error?.code !== 20) {
|
144
|
-
this.handleError(sessionCode, `模型调用失败:${error.message}`, error.code);
|
145
|
-
}
|
146
|
-
break;
|
147
|
-
}
|
148
|
-
}
|
149
|
-
});
|
150
|
-
}
|
151
|
-
|
152
|
-
stop(sessionCode: string) {
|
153
|
-
this.controllerMap[sessionCode]?.abort?.();
|
154
|
-
return this.handleEnd(sessionCode);
|
155
|
-
}
|
156
|
-
}
|
package/src/common/util.ts
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* 判断是否是 JSON 字符串
|
3
|
-
* @param str 字符串
|
4
|
-
* @returns 是否是 JSON 字符串
|
5
|
-
*/
|
6
|
-
export const isJSON = (str: string) => {
|
7
|
-
try {
|
8
|
-
JSON.parse(str);
|
9
|
-
return true;
|
10
|
-
} catch (e) {
|
11
|
-
return false;
|
12
|
-
}
|
13
|
-
};
|
14
|
-
|
15
|
-
/**
|
16
|
-
* 响应时间格式化
|
17
|
-
* @param val 待格式化时间,xxms
|
18
|
-
* @returns 格式化后的时间,xx小时xx分钟xx秒
|
19
|
-
*/
|
20
|
-
export function durationFormatter(val: number) {
|
21
|
-
const hours = Math.floor(val / 3600000);
|
22
|
-
const minutes = Math.floor((val % 3600000) / 60000);
|
23
|
-
const seconds = Math.floor((val % 60000) / 1000);
|
24
|
-
const milliseconds = val % 1000;
|
25
|
-
|
26
|
-
const parts: string[] = [];
|
27
|
-
if (hours > 0) {
|
28
|
-
parts.push(`${hours}h`);
|
29
|
-
}
|
30
|
-
if (minutes > 0) {
|
31
|
-
parts.push(`${minutes}min`);
|
32
|
-
}
|
33
|
-
if (seconds > 0) {
|
34
|
-
parts.push(`${seconds}s`);
|
35
|
-
}
|
36
|
-
if (milliseconds > 0) {
|
37
|
-
parts.push(`${milliseconds.toFixed(2)}ms`);
|
38
|
-
}
|
39
|
-
|
40
|
-
return parts.join(' ');
|
41
|
-
}
|
@@ -1,205 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
onBeforeMount,
|
3
|
-
onBeforeUnmount,
|
4
|
-
} from 'vue';
|
5
|
-
|
6
|
-
import type {
|
7
|
-
Document,
|
8
|
-
} from '../types/type.ts';
|
9
|
-
|
10
|
-
/**
|
11
|
-
* 获取引用资料的 HTML 内容
|
12
|
-
* @param documents
|
13
|
-
* @returns
|
14
|
-
*/
|
15
|
-
export const getHtmlContentFromDocuments = (documents: Document[]) => {
|
16
|
-
let htmlContent = `<section class="knowledge-head click-close">
|
17
|
-
<svg
|
18
|
-
class="bkaidev-wenzhang"
|
19
|
-
>
|
20
|
-
<use href="#bkaidev-wenzhang"></use>
|
21
|
-
</svg>
|
22
|
-
找到 ${documents.length} 篇资料参考
|
23
|
-
<i class="bkaidev-icon bkaidev-angle-up"></i>
|
24
|
-
</section>
|
25
|
-
<ul class="knowledge-body">`;
|
26
|
-
documents.forEach((document) => {
|
27
|
-
const { file_path: filePath, preview_path: previewPath } = document.metadata;
|
28
|
-
const title = filePath.split('/').pop();
|
29
|
-
htmlContent += `<li
|
30
|
-
class="knowledge-item"
|
31
|
-
title="${title} (${previewPath})"
|
32
|
-
>
|
33
|
-
<i class="bkaidev-icon bkaidev-zhishiku"></i>
|
34
|
-
<a href="${previewPath}" target="_blank" class="knowledge-link g-flex-truncate">
|
35
|
-
${title}
|
36
|
-
</a>
|
37
|
-
<span class="knowledge-path g-flex-truncate">${filePath}</span>
|
38
|
-
</li>`;
|
39
|
-
});
|
40
|
-
htmlContent += '</ul>';
|
41
|
-
return htmlContent;
|
42
|
-
};
|
43
|
-
|
44
|
-
/**
|
45
|
-
* 移除引用资料的 HTML 内容
|
46
|
-
* @param content
|
47
|
-
* @returns
|
48
|
-
*/
|
49
|
-
export const removeReferenceDoc = (content: string) => {
|
50
|
-
return content
|
51
|
-
.replace(/<section class="knowledge-head click-close">[\s\S]*?<\/section>/, '')
|
52
|
-
.replace(/<ul class="knowledge-body">[\s\S]*?<\/ul>/, '')
|
53
|
-
.replace(/<section class="knowledge-tips">[\s\S]*?<\/section>/, '');
|
54
|
-
};
|
55
|
-
|
56
|
-
export const useReferenceDoc = () => {
|
57
|
-
let styleEle: HTMLStyleElement | null = null;
|
58
|
-
|
59
|
-
const addReferenceDocStyle = () => {
|
60
|
-
// 旧版知识样式
|
61
|
-
const oldStyle = `.knowledge-tips {
|
62
|
-
position: relative;
|
63
|
-
padding: 6px 8px;
|
64
|
-
margin-bottom: 14px;
|
65
|
-
line-height: 22px;
|
66
|
-
background: #f5f7fa;
|
67
|
-
border-radius: 4px;
|
68
|
-
|
69
|
-
&.closed {
|
70
|
-
.bkaidev-angle-up {
|
71
|
-
transform: rotate(180deg);
|
72
|
-
}
|
73
|
-
|
74
|
-
.knowledge-summary {
|
75
|
-
margin-bottom: 0;
|
76
|
-
}
|
77
|
-
|
78
|
-
.knowledge-link {
|
79
|
-
display: none;
|
80
|
-
}
|
81
|
-
}
|
82
|
-
|
83
|
-
.bkaidev-angle-up {
|
84
|
-
position: absolute;
|
85
|
-
top: 4px;
|
86
|
-
right: 0px;
|
87
|
-
font-size: 22px;
|
88
|
-
color: #979ba5;
|
89
|
-
cursor: pointer;
|
90
|
-
}
|
91
|
-
|
92
|
-
.bkaidev-help-document {
|
93
|
-
margin-right: 4px;
|
94
|
-
font-size: 19px;
|
95
|
-
color: #3a84ff;
|
96
|
-
}
|
97
|
-
|
98
|
-
.knowledge-summary {
|
99
|
-
display: block;
|
100
|
-
margin-bottom: 4px;
|
101
|
-
color: #313238;
|
102
|
-
}
|
103
|
-
|
104
|
-
.knowledge-link {
|
105
|
-
display: block;
|
106
|
-
color: #3a84ff;
|
107
|
-
text-decoration: none;
|
108
|
-
cursor: pointer;
|
109
|
-
|
110
|
-
.bkaidev-cc-jump-link {
|
111
|
-
font-size: 14px;
|
112
|
-
}
|
113
|
-
|
114
|
-
&:last-child {
|
115
|
-
margin-bottom: 2px;
|
116
|
-
}
|
117
|
-
}
|
118
|
-
}`;
|
119
|
-
// 新版知识样式
|
120
|
-
const newStyle = `.knowledge-head {
|
121
|
-
display: flex;
|
122
|
-
align-items: center;
|
123
|
-
padding: 0 8px 0 6px;
|
124
|
-
margin-bottom: 8px;
|
125
|
-
line-height: 28px;
|
126
|
-
background: #F0F1F5;
|
127
|
-
border-radius: 4px;
|
128
|
-
font-size: 12px;
|
129
|
-
color: #313238;
|
130
|
-
cursor: pointer;
|
131
|
-
width: fit-content;
|
132
|
-
|
133
|
-
.bkaidev-wenzhang {
|
134
|
-
width: 14px;
|
135
|
-
height: 14px;
|
136
|
-
margin-right: 6px;
|
137
|
-
}
|
138
|
-
|
139
|
-
.bkaidev-angle-up {
|
140
|
-
font-size: 22px;
|
141
|
-
color: #4D4F56;
|
142
|
-
margin-left: 4px;
|
143
|
-
}
|
144
|
-
|
145
|
-
&.closed {
|
146
|
-
margin-bottom: 16px;
|
147
|
-
|
148
|
-
.bkaidev-angle-up {
|
149
|
-
transform: rotate(180deg);
|
150
|
-
}
|
151
|
-
|
152
|
-
&~ .knowledge-body {
|
153
|
-
display: none;
|
154
|
-
}
|
155
|
-
}
|
156
|
-
|
157
|
-
&:hover {
|
158
|
-
background: #EAEBF0;
|
159
|
-
}
|
160
|
-
}
|
161
|
-
.knowledge-body {
|
162
|
-
background: #F5F7FA;
|
163
|
-
padding: 16px !important;
|
164
|
-
.knowledge-item {
|
165
|
-
display: flex;
|
166
|
-
align-items: center;
|
167
|
-
line-height: 28px;
|
168
|
-
a {
|
169
|
-
margin-right: 20px;
|
170
|
-
}
|
171
|
-
.bkaidev-zhishiku {
|
172
|
-
color: #D66F6B;
|
173
|
-
font-size: 14px;
|
174
|
-
margin-right: 6px;
|
175
|
-
}
|
176
|
-
.knowledge-path {
|
177
|
-
margin-left: auto;
|
178
|
-
color: #979BA5;
|
179
|
-
text-align: right;
|
180
|
-
}
|
181
|
-
&:hover {
|
182
|
-
background: #EAEBF0;
|
183
|
-
}
|
184
|
-
}
|
185
|
-
}`;
|
186
|
-
styleEle = document.createElement('style');
|
187
|
-
styleEle.textContent = oldStyle + newStyle;
|
188
|
-
document.head.appendChild(styleEle);
|
189
|
-
};
|
190
|
-
|
191
|
-
const removeReferenceDocStyle = () => {
|
192
|
-
if (styleEle) {
|
193
|
-
styleEle.remove();
|
194
|
-
styleEle = null;
|
195
|
-
}
|
196
|
-
};
|
197
|
-
|
198
|
-
onBeforeMount(() => {
|
199
|
-
addReferenceDocStyle();
|
200
|
-
});
|
201
|
-
|
202
|
-
onBeforeUnmount(() => {
|
203
|
-
removeReferenceDocStyle();
|
204
|
-
});
|
205
|
-
};
|
package/src/hooks/use-think.ts
DELETED
@@ -1,156 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
onBeforeMount,
|
3
|
-
onBeforeUnmount,
|
4
|
-
} from 'vue';
|
5
|
-
|
6
|
-
import {
|
7
|
-
durationFormatter,
|
8
|
-
} from '../common/util.ts';
|
9
|
-
|
10
|
-
/**
|
11
|
-
* 获取思考的 HTML 内容
|
12
|
-
* @param chatContent 聊天内容
|
13
|
-
* @param content 思考内容
|
14
|
-
* @param elapsedTime 思考时间
|
15
|
-
* @returns 完整的 chatContent
|
16
|
-
*/
|
17
|
-
export const getHtmlContentFromThink = (chatContent: string, content: string, cover?: boolean, elapsedTime?: number) => {
|
18
|
-
let htmlContent = (chatContent === '内容正在生成中...' || cover) ? '' : chatContent;
|
19
|
-
// 思考开始
|
20
|
-
if (!htmlContent.includes('<section class="think-head click-close">')) {
|
21
|
-
htmlContent += `<section class="think-head click-close">
|
22
|
-
<i class="bkaidev-icon bkaidev-sikao"></i>思考中...<i class="bkaidev-icon bkaidev-angle-up"></i>
|
23
|
-
</section>
|
24
|
-
<section class="think-body">
|
25
|
-
${content}
|
26
|
-
</section>`;
|
27
|
-
} else {
|
28
|
-
// 补充思考内容
|
29
|
-
const thinkBodyMatch = htmlContent.match(/<section class="think-body">([\s\S]*?)<\/section>/);
|
30
|
-
if (thinkBodyMatch) {
|
31
|
-
// 原有思考内容
|
32
|
-
const thinkContent = thinkBodyMatch[1];
|
33
|
-
// 在最后的换行符前面添加新的思考内容
|
34
|
-
const newThinkContent = thinkContent.replace(/\n$/g, `${content}\n`);
|
35
|
-
htmlContent = htmlContent.replace(
|
36
|
-
thinkContent,
|
37
|
-
newThinkContent,
|
38
|
-
);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
// 思考结束
|
42
|
-
if (elapsedTime) {
|
43
|
-
htmlContent = htmlContent.replace('思考中...', `已完成思考 (耗时:${durationFormatter(elapsedTime)})`);
|
44
|
-
}
|
45
|
-
return htmlContent;
|
46
|
-
};
|
47
|
-
|
48
|
-
/**
|
49
|
-
* 移除思考的 HTML 内容
|
50
|
-
* @param content
|
51
|
-
* @returns
|
52
|
-
*/
|
53
|
-
export const removeThink = (content: string) => {
|
54
|
-
// 移除思考头部
|
55
|
-
const afterRemoveThinkHead = content.replace(/<section class="think-head click-close">[\s\S]*<\/section>/, '');
|
56
|
-
// 移除思考内容
|
57
|
-
let afterRemoveThinkBody = afterRemoveThinkHead.replace(/<section class="think-body">[\s\S]*<\/section>/, '');
|
58
|
-
// 如果内容只有思考,那就给ai传递思考内容
|
59
|
-
if (afterRemoveThinkBody.trim() === '') {
|
60
|
-
const thinkBodyMatch = content.match(/<section class="think-body">([\s\S]*?)<\/section>/);
|
61
|
-
if (thinkBodyMatch) {
|
62
|
-
[, afterRemoveThinkBody] = thinkBodyMatch;
|
63
|
-
}
|
64
|
-
}
|
65
|
-
return afterRemoveThinkBody;
|
66
|
-
};
|
67
|
-
|
68
|
-
/**
|
69
|
-
* 判断是否是思考中
|
70
|
-
* @param content
|
71
|
-
* @returns
|
72
|
-
*/
|
73
|
-
export const isThinking = (content: string) => {
|
74
|
-
// 是否开始思考
|
75
|
-
const isStartThinking = content === '正在思考...';
|
76
|
-
// 判断是否空思考
|
77
|
-
const thinkBodyMatch = content.match(/<section class="think-body">([\s\S]*?)<\/section>$/);
|
78
|
-
const isEmptyThinking = thinkBodyMatch && thinkBodyMatch[1].trim() === '';
|
79
|
-
|
80
|
-
return isStartThinking || isEmptyThinking;
|
81
|
-
};
|
82
|
-
|
83
|
-
// 思考
|
84
|
-
export const useThink = () => {
|
85
|
-
let styleEle: HTMLStyleElement | null = null;
|
86
|
-
|
87
|
-
const addThinkStyle = () => {
|
88
|
-
const style = `.think-head {
|
89
|
-
display: flex;
|
90
|
-
align-items: center;
|
91
|
-
padding: 0 8px 0 6px;
|
92
|
-
margin-bottom: 8px;
|
93
|
-
line-height: 28px;
|
94
|
-
background: #F0F1F5;
|
95
|
-
border-radius: 4px;
|
96
|
-
font-size: 12px;
|
97
|
-
color: #313238;
|
98
|
-
cursor: pointer;
|
99
|
-
width: fit-content;
|
100
|
-
.bkaidev-sikao {
|
101
|
-
font-size: 14px;
|
102
|
-
color: #979ba5;
|
103
|
-
margin-right: 4px;
|
104
|
-
}
|
105
|
-
|
106
|
-
.bkaidev-angle-up {
|
107
|
-
font-size: 22px;
|
108
|
-
color: #4D4F56;
|
109
|
-
margin-left: 4px;
|
110
|
-
}
|
111
|
-
|
112
|
-
&.closed {
|
113
|
-
margin-bottom: 16px;
|
114
|
-
|
115
|
-
.bkaidev-angle-up {
|
116
|
-
transform: rotate(180deg);
|
117
|
-
}
|
118
|
-
|
119
|
-
&~ .think-body {
|
120
|
-
display: none;
|
121
|
-
}
|
122
|
-
}
|
123
|
-
|
124
|
-
&:hover {
|
125
|
-
background: #EAEBF0;
|
126
|
-
}
|
127
|
-
}
|
128
|
-
.think-body {
|
129
|
-
background: #F5F7FA;
|
130
|
-
color: #979BA5;
|
131
|
-
padding: 16px;
|
132
|
-
margin-bottom: 16px;
|
133
|
-
&~ .knowledge-head {
|
134
|
-
margin-bottom: 8px;
|
135
|
-
}
|
136
|
-
}`;
|
137
|
-
styleEle = document.createElement('style');
|
138
|
-
styleEle.textContent = style;
|
139
|
-
document.head.appendChild(styleEle);
|
140
|
-
};
|
141
|
-
|
142
|
-
const removeThinkStyle = () => {
|
143
|
-
if (styleEle) {
|
144
|
-
styleEle.remove();
|
145
|
-
styleEle = null;
|
146
|
-
}
|
147
|
-
};
|
148
|
-
|
149
|
-
onBeforeMount(() => {
|
150
|
-
addThinkStyle();
|
151
|
-
});
|
152
|
-
|
153
|
-
onBeforeUnmount(() => {
|
154
|
-
removeThinkStyle();
|
155
|
-
});
|
156
|
-
};
|
package/src/main.ts
DELETED
@@ -1,227 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
ref,
|
3
|
-
} from 'vue';
|
4
|
-
import type {
|
5
|
-
Document,
|
6
|
-
ISessionContent,
|
7
|
-
} from './types/type.ts';
|
8
|
-
import {
|
9
|
-
HttpErrorCode,
|
10
|
-
SessionContentRole,
|
11
|
-
SessionContentStatus,
|
12
|
-
} from './types/enum.ts';
|
13
|
-
import {
|
14
|
-
useReferenceDoc,
|
15
|
-
getHtmlContentFromDocuments
|
16
|
-
} from './hooks/use-reference-doc.ts';
|
17
|
-
import {
|
18
|
-
useThink,
|
19
|
-
getHtmlContentFromThink,
|
20
|
-
isThinking
|
21
|
-
} from './hooks/use-think.ts';
|
22
|
-
import {
|
23
|
-
ChatHelper,
|
24
|
-
} from './common/chart-helper.ts';
|
25
|
-
|
26
|
-
type SessionContentsMap = {
|
27
|
-
[key: string]: ISessionContent[]
|
28
|
-
};
|
29
|
-
|
30
|
-
type SessionLoadingMap = {
|
31
|
-
[key: string]: boolean
|
32
|
-
};
|
33
|
-
|
34
|
-
interface AICallbacks {
|
35
|
-
handleStart?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
36
|
-
handleText?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
37
|
-
handleReferenceDoc?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
38
|
-
handleThink?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
39
|
-
handleEnd?: (sessionCode: string, sessionContent: ISessionContent) => void;
|
40
|
-
handleError?: (sessionCode: string, code: string | undefined, sessionContent: ISessionContent) => void;
|
41
|
-
}
|
42
|
-
|
43
|
-
export const useAI = ({
|
44
|
-
handleStart,
|
45
|
-
handleText,
|
46
|
-
handleReferenceDoc,
|
47
|
-
handleThink,
|
48
|
-
handleEnd,
|
49
|
-
handleError,
|
50
|
-
}: AICallbacks) => {
|
51
|
-
// 引用资料
|
52
|
-
useReferenceDoc();
|
53
|
-
// 思考
|
54
|
-
useThink();
|
55
|
-
// 聊天上下文
|
56
|
-
let currentSessionCode = '';
|
57
|
-
const sessionLoadingMap = ref<SessionLoadingMap>({});
|
58
|
-
const sessionContents = ref<ISessionContent[]>([]);
|
59
|
-
const sessionContentsMap: SessionContentsMap = {};
|
60
|
-
|
61
|
-
const chatHelper = new ChatHelper({
|
62
|
-
handleStart: handleStartChat,
|
63
|
-
handleText: handleTextChat,
|
64
|
-
handleReferenceDoc: handleReferenceDocChat,
|
65
|
-
handleThink: handleThinkChat,
|
66
|
-
handleEnd: handleEndChat,
|
67
|
-
handleError: handleErrorChat,
|
68
|
-
});
|
69
|
-
|
70
|
-
// 设置 sessionCode
|
71
|
-
function changeSessionCode(sessionCode: string) {
|
72
|
-
currentSessionCode = sessionCode;
|
73
|
-
if (!sessionContentsMap[sessionCode]) {
|
74
|
-
sessionContentsMap[sessionCode] = [];
|
75
|
-
}
|
76
|
-
sessionContents.value = sessionContentsMap[sessionCode];
|
77
|
-
}
|
78
|
-
|
79
|
-
// 设置 sessionContents
|
80
|
-
function setSessionContents(data: ISessionContent[]) {
|
81
|
-
if (!currentSessionCode) return;
|
82
|
-
sessionContentsMap[currentSessionCode] = data;
|
83
|
-
sessionContents.value = data;
|
84
|
-
}
|
85
|
-
|
86
|
-
// 获取执行中的 SessionContent
|
87
|
-
function getSessionContentBySessionCode(sessionCode: string) {
|
88
|
-
if (currentSessionCode === sessionCode) {
|
89
|
-
return sessionContents.value.at(-1) as ISessionContent;
|
90
|
-
}
|
91
|
-
return sessionContentsMap[sessionCode]?.at(-1) as ISessionContent;
|
92
|
-
}
|
93
|
-
|
94
|
-
// 获取 SessionContents
|
95
|
-
function getSessionContentsBySessionCode(sessionCode: string) {
|
96
|
-
if (currentSessionCode === sessionCode) {
|
97
|
-
return sessionContents.value;
|
98
|
-
}
|
99
|
-
return sessionContentsMap[sessionCode];
|
100
|
-
}
|
101
|
-
|
102
|
-
// 新增 sessionContent
|
103
|
-
function plusSessionContent(sessionCode: string, sessionContent: ISessionContent) {
|
104
|
-
const sessionContents = getSessionContentsBySessionCode(sessionCode);
|
105
|
-
sessionContents.push(sessionContent);
|
106
|
-
}
|
107
|
-
|
108
|
-
// 聊天开始
|
109
|
-
function handleStartChat(sessionCode: string) {
|
110
|
-
sessionLoadingMap.value[sessionCode] = true;
|
111
|
-
const sessionContent: ISessionContent = {
|
112
|
-
sessionCode,
|
113
|
-
role: SessionContentRole.Ai,
|
114
|
-
status: SessionContentStatus.Loading,
|
115
|
-
content: '内容正在生成中...',
|
116
|
-
};
|
117
|
-
// 画布新增
|
118
|
-
plusSessionContent(sessionCode, sessionContent);
|
119
|
-
// 调用cb
|
120
|
-
return handleStart?.(sessionCode, sessionContent);
|
121
|
-
}
|
122
|
-
|
123
|
-
// 引用资料
|
124
|
-
function handleReferenceDocChat(sessionCode: string, documents: Document[], cover?: boolean) {
|
125
|
-
const sessionContent = getSessionContentBySessionCode(sessionCode);
|
126
|
-
const content = getHtmlContentFromDocuments(documents);
|
127
|
-
sessionContent.content = cover ? content : sessionContent.content + content;
|
128
|
-
return handleReferenceDoc?.(sessionCode, sessionContent);
|
129
|
-
}
|
130
|
-
|
131
|
-
// 思考
|
132
|
-
function handleThinkChat(sessionCode: string, content: string, cover?: boolean, elapsedTime?: number) {
|
133
|
-
const sessionContent = getSessionContentBySessionCode(sessionCode);
|
134
|
-
// 获取思考的html
|
135
|
-
sessionContent.content = getHtmlContentFromThink(sessionContent.content, content, cover, elapsedTime);
|
136
|
-
// 调用cb
|
137
|
-
return handleThink?.(sessionCode, sessionContent);
|
138
|
-
}
|
139
|
-
|
140
|
-
// 文本
|
141
|
-
function handleTextChat(sessionCode: string, message: string, cover?: boolean) {
|
142
|
-
const sessionContent = getSessionContentBySessionCode(sessionCode);
|
143
|
-
if (sessionContent.content === '内容正在生成中...') {
|
144
|
-
sessionContent.content = message;
|
145
|
-
} else if (sessionContent.status === SessionContentStatus.Loading) {
|
146
|
-
sessionContent.content = cover
|
147
|
-
? message
|
148
|
-
: sessionContent.content + message;
|
149
|
-
} else {
|
150
|
-
return;
|
151
|
-
}
|
152
|
-
// 调用cb
|
153
|
-
return handleText?.(sessionCode, sessionContent);
|
154
|
-
}
|
155
|
-
|
156
|
-
// 聊天结束
|
157
|
-
function handleEndChat(sessionCode: string, message?: string) {
|
158
|
-
const sessionContent = getSessionContentBySessionCode(sessionCode);
|
159
|
-
// 防止 error 的情况下异步触发 end 更新了数据
|
160
|
-
if (sessionContent.status === SessionContentStatus.Loading) {
|
161
|
-
sessionLoadingMap.value[sessionCode] = false;
|
162
|
-
// end 的时候,如果 message 有值,则更新 content
|
163
|
-
if (message) {
|
164
|
-
sessionContent.content = message;
|
165
|
-
}
|
166
|
-
// 更新状态
|
167
|
-
sessionContent.status = SessionContentStatus.Success;
|
168
|
-
// 调用cb
|
169
|
-
return handleEnd?.(sessionCode, sessionContent);
|
170
|
-
}
|
171
|
-
// 内容正在生成中或者正在思考中
|
172
|
-
if (sessionContent.content === '内容正在生成中...' || isThinking(sessionContent.content)) {
|
173
|
-
handleErrorChat(sessionCode, '聊天内容已中断');
|
174
|
-
}
|
175
|
-
}
|
176
|
-
|
177
|
-
// 聊天异常
|
178
|
-
function handleErrorChat(sessionCode: string, message: string, code?: string) {
|
179
|
-
const sessionContent = getSessionContentBySessionCode(sessionCode);
|
180
|
-
sessionContent.status = SessionContentStatus.Fail;
|
181
|
-
sessionContent.content = message;
|
182
|
-
sessionLoadingMap.value[sessionCode] = false;
|
183
|
-
// token 超了,提示 token 不足
|
184
|
-
if (code === HttpErrorCode.TokenExpired) {
|
185
|
-
sessionContent.content = '抱歉,您的剩余 Token 不足,无法返回回答内容,请先清空当前会话(上下文仍会作为历史记录保留))';
|
186
|
-
sessionContent.role = SessionContentRole.TokenExpired;
|
187
|
-
}
|
188
|
-
if (code === HttpErrorCode.ImageNotSupported) {
|
189
|
-
sessionContent.content = '抱歉,当前模型不支持图片内容解析';
|
190
|
-
sessionContent.role = SessionContentRole.ImageNotSupported;
|
191
|
-
}
|
192
|
-
// 调用cb
|
193
|
-
return handleError?.(sessionCode, code, sessionContent);
|
194
|
-
}
|
195
|
-
|
196
|
-
// 聊天
|
197
|
-
function chat({
|
198
|
-
sessionCode,
|
199
|
-
param,
|
200
|
-
url,
|
201
|
-
headers,
|
202
|
-
}: {
|
203
|
-
sessionCode: string;
|
204
|
-
param?: Record<string, any>;
|
205
|
-
url: string;
|
206
|
-
headers?: Record<string, string>;
|
207
|
-
}) {
|
208
|
-
// 发送请求
|
209
|
-
chatHelper.stream({
|
210
|
-
sessionCode,
|
211
|
-
url,
|
212
|
-
param,
|
213
|
-
headers,
|
214
|
-
});
|
215
|
-
}
|
216
|
-
|
217
|
-
return {
|
218
|
-
sessionContents,
|
219
|
-
sessionContentsMap,
|
220
|
-
sessionLoadingMap,
|
221
|
-
chat,
|
222
|
-
getSessionContentBySessionCode,
|
223
|
-
getSessionContentsBySessionCode,
|
224
|
-
changeSessionCode,
|
225
|
-
setSessionContents,
|
226
|
-
};
|
227
|
-
};
|
package/src/types/enum.ts
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
// 传给 ai 的类型
|
2
|
-
export enum SessionPromptRole {
|
3
|
-
System = 'system',
|
4
|
-
Assistant = 'assistant',
|
5
|
-
User = 'user',
|
6
|
-
Guide = 'guide',
|
7
|
-
Pause = 'pause',
|
8
|
-
UserImage = 'user-image',
|
9
|
-
Hidden = 'hidden',
|
10
|
-
HiddenUser = 'hidden-user',
|
11
|
-
HiddenAssistant = 'hidden-assistant',
|
12
|
-
HiddenSystem = 'hidden-system',
|
13
|
-
HiddenGuide = 'hidden-guide',
|
14
|
-
TemplateUser = 'template-user',
|
15
|
-
TemplateAssistant = 'template-assistant',
|
16
|
-
TemplateSystem = 'template-system',
|
17
|
-
TemplateGuide = 'template-guide',
|
18
|
-
TemplateHidden = 'template-hidden',
|
19
|
-
}
|
20
|
-
|
21
|
-
export enum SessionContentStatus {
|
22
|
-
Fail = 'fail',
|
23
|
-
Loading = 'loading',
|
24
|
-
Success = 'success',
|
25
|
-
}
|
26
|
-
|
27
|
-
export enum SessionContentRole {
|
28
|
-
Ai = 'ai',
|
29
|
-
User = 'user',
|
30
|
-
Time = 'time',
|
31
|
-
System = 'system',
|
32
|
-
Role = 'role',
|
33
|
-
Hidden = 'hidden',
|
34
|
-
Guide = 'guide',
|
35
|
-
Pause = 'pause',
|
36
|
-
TokenExpired = 'token-expired',
|
37
|
-
UserImage = 'user-image',
|
38
|
-
HiddenUser = 'hidden-user',
|
39
|
-
HiddenAi = 'hidden-ai',
|
40
|
-
HiddenRole = 'hidden-role',
|
41
|
-
HiddenGuide = 'hidden-guide',
|
42
|
-
TemplateUser = 'template-user',
|
43
|
-
TemplateAi = 'template-ai',
|
44
|
-
TemplateRole = 'template-role',
|
45
|
-
TemplateGuide = 'template-guide',
|
46
|
-
TemplateHidden = 'template-hidden',
|
47
|
-
ImageNotSupported = 'image-not-supported'
|
48
|
-
}
|
49
|
-
|
50
|
-
export enum HttpErrorCode {
|
51
|
-
TokenExpired = '80003',
|
52
|
-
ImageNotSupported = '82003',
|
53
|
-
UnAuthorizedPreviewFile = '80401',
|
54
|
-
TagRepeat = '1513405',
|
55
|
-
Aborted = 20,
|
56
|
-
UnLogin = 401,
|
57
|
-
IamNoPermission = 'IAM_NO_PERMISSION'
|
58
|
-
}
|
package/src/types/type.ts
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
SessionContentRole,
|
3
|
-
SessionPromptRole,
|
4
|
-
SessionContentStatus,
|
5
|
-
} from './enum.ts';
|
6
|
-
|
7
|
-
export type Document = {
|
8
|
-
metadata: {
|
9
|
-
file_path: string;
|
10
|
-
path: string;
|
11
|
-
preview_path: string;
|
12
|
-
};
|
13
|
-
};
|
14
|
-
|
15
|
-
export interface ISessionContent {
|
16
|
-
id?: number;
|
17
|
-
createdAt?: string;
|
18
|
-
createdBy?: string;
|
19
|
-
updatedAt?: string;
|
20
|
-
updatedBy?: string;
|
21
|
-
spaceId?: string;
|
22
|
-
liked?: boolean;
|
23
|
-
role: SessionContentRole;
|
24
|
-
content: string;
|
25
|
-
rate?: number;
|
26
|
-
status?: SessionContentStatus;
|
27
|
-
sessionCode?: string;
|
28
|
-
}
|
29
|
-
|
30
|
-
export interface ISessionPrompt {
|
31
|
-
content: string;
|
32
|
-
role: SessionPromptRole;
|
33
|
-
}
|