@hlw-zbzs/ai 1.0.1 → 1.0.3
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/ai.css +1 -0
- package/dist/env.d.ts +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +951 -0
- package/dist/src/components/ai.vue.d.ts +2 -0
- package/dist/src/components/chat.vue.d.ts +2 -0
- package/dist/src/components/grid.vue.d.ts +7 -0
- package/dist/src/components/header.vue.d.ts +2 -0
- package/dist/src/components/index.d.ts +6 -0
- package/dist/src/components/params.vue.d.ts +7 -0
- package/dist/src/composables/index.d.ts +413 -0
- package/dist/src/engines/base.d.ts +49 -0
- package/dist/src/engines/index.d.ts +10 -0
- package/dist/src/store/index.d.ts +418 -0
- package/dist/src/store/preset.d.ts +15 -0
- package/dist/src/types.d.ts +94 -0
- package/package.json +24 -20
- package/index.ts +0 -10
- package/src/components/ai.vue +0 -84
- package/src/components/chat.vue +0 -474
- package/src/components/grid.vue +0 -175
- package/src/components/header.vue +0 -98
- package/src/components/index.ts +0 -15
- package/src/components/params.vue +0 -388
- package/src/core/index.ts +0 -169
- package/src/engines/base.ts +0 -142
- package/src/engines/index.ts +0 -12
- package/src/store/index.ts +0 -158
- package/src/types.d.ts +0 -1
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="ai-header">
|
|
3
|
-
<div class="header-title">
|
|
4
|
-
<n-icon :size="18" class="title-icon"><HardwareChipOutline /></n-icon>
|
|
5
|
-
<span class="title-text">大模型配置与测试</span>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<div class="header-right">
|
|
9
|
-
<n-tag
|
|
10
|
-
:type="ready ? 'success' : 'warning'"
|
|
11
|
-
size="small"
|
|
12
|
-
round
|
|
13
|
-
:bordered="false"
|
|
14
|
-
class="status-tag"
|
|
15
|
-
>
|
|
16
|
-
<template #icon>
|
|
17
|
-
<n-icon>
|
|
18
|
-
<CheckmarkCircleOutline v-if="ready" />
|
|
19
|
-
<AlertCircleOutline v-else />
|
|
20
|
-
</n-icon>
|
|
21
|
-
</template>
|
|
22
|
-
{{ ready ? `${info.name} 已就绪` : `${info.name} 未配 Key` }}
|
|
23
|
-
</n-tag>
|
|
24
|
-
|
|
25
|
-
<div class="switch-wrap">
|
|
26
|
-
<span class="switch-label">启用 AI:</span>
|
|
27
|
-
<n-switch :value="enabled" size="small" @update:value="onToggle" />
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script setup lang="ts">
|
|
34
|
-
import { HardwareChipOutline, CheckmarkCircleOutline, AlertCircleOutline } from '@vicons/ionicons5'
|
|
35
|
-
import { NIcon, NTag, NSwitch } from 'naive-ui'
|
|
36
|
-
import { toast } from '@hlw-zbzs/vue'
|
|
37
|
-
import { useAi } from '../core'
|
|
38
|
-
|
|
39
|
-
const { info, ready, enabled } = useAi()
|
|
40
|
-
|
|
41
|
-
function onToggle(val: boolean) {
|
|
42
|
-
enabled.value = val
|
|
43
|
-
toast(`AI 功能已${val ? '开启' : '停用'}`, val ? 'success' : 'info')
|
|
44
|
-
}
|
|
45
|
-
</script>
|
|
46
|
-
|
|
47
|
-
<style scoped lang="scss">
|
|
48
|
-
.ai-header {
|
|
49
|
-
height: 48px;
|
|
50
|
-
background: #ffffff;
|
|
51
|
-
border-bottom: 1px solid #e2e8f0;
|
|
52
|
-
display: flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
justify-content: space-between;
|
|
55
|
-
padding: 0 16px;
|
|
56
|
-
flex-shrink: 0;
|
|
57
|
-
|
|
58
|
-
.header-title {
|
|
59
|
-
display: flex;
|
|
60
|
-
align-items: center;
|
|
61
|
-
gap: 8px;
|
|
62
|
-
|
|
63
|
-
.title-icon {
|
|
64
|
-
color: #2563eb;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.title-text {
|
|
68
|
-
font-size: 15px;
|
|
69
|
-
font-weight: 400;
|
|
70
|
-
color: #0f172a;
|
|
71
|
-
letter-spacing: 0.5px;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.header-right {
|
|
76
|
-
display: flex;
|
|
77
|
-
align-items: center;
|
|
78
|
-
gap: 14px;
|
|
79
|
-
|
|
80
|
-
.status-tag {
|
|
81
|
-
user-select: none;
|
|
82
|
-
font-weight: 400;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.switch-wrap {
|
|
86
|
-
display: flex;
|
|
87
|
-
align-items: center;
|
|
88
|
-
gap: 6px;
|
|
89
|
-
|
|
90
|
-
.switch-label {
|
|
91
|
-
font-size: 13px;
|
|
92
|
-
font-weight: 400;
|
|
93
|
-
color: #475569;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
</style>
|
package/src/components/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import HlwAi from './ai.vue'
|
|
2
|
-
import AiHeader from './header.vue'
|
|
3
|
-
import AiGrid from './grid.vue'
|
|
4
|
-
import AiParams from './params.vue'
|
|
5
|
-
import AiChat from './chat.vue'
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
HlwAi,
|
|
9
|
-
AiHeader,
|
|
10
|
-
AiGrid,
|
|
11
|
-
AiParams,
|
|
12
|
-
AiChat
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default HlwAi
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="params-container">
|
|
3
|
-
<div class="params-fields">
|
|
4
|
-
<!-- 凭证与连接参数 -->
|
|
5
|
-
<div class="field-section">
|
|
6
|
-
<div class="section-header">
|
|
7
|
-
<span class="section-title">API 凭证与模型</span>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="form-row">
|
|
11
|
-
<div class="form-field flex-2">
|
|
12
|
-
<label class="field-label">
|
|
13
|
-
<span class="label-title">
|
|
14
|
-
<span>API Key</span>
|
|
15
|
-
<span v-if="!info.free" class="req-star">*</span>
|
|
16
|
-
<span v-else class="free-text">(本地模型无需填写)</span>
|
|
17
|
-
</span>
|
|
18
|
-
<a v-if="info.url" class="portal-link" title="在浏览器中打开开发者平台获取 Key" @click.stop="openPortal(info.url)">
|
|
19
|
-
<span>获取 {{ info.name }} 密钥</span>
|
|
20
|
-
<n-icon :size="12"><OpenOutline /></n-icon>
|
|
21
|
-
</a>
|
|
22
|
-
</label>
|
|
23
|
-
<n-input
|
|
24
|
-
:value="conf.key"
|
|
25
|
-
type="password"
|
|
26
|
-
show-password-on="click"
|
|
27
|
-
:placeholder="info.free ? '免密模型' : '请输入 API Key (sk-...)'"
|
|
28
|
-
size="medium"
|
|
29
|
-
clearable
|
|
30
|
-
@update:value="onKeyChange"
|
|
31
|
-
/>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="form-field flex-1">
|
|
35
|
-
<label class="field-label">模型 (Model)</label>
|
|
36
|
-
<n-select
|
|
37
|
-
:value="conf.model"
|
|
38
|
-
:options="modelOptions"
|
|
39
|
-
tag
|
|
40
|
-
filterable
|
|
41
|
-
size="medium"
|
|
42
|
-
placeholder="选择或输入模型"
|
|
43
|
-
@update:value="onModelChange"
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div class="form-field">
|
|
49
|
-
<label class="field-label">接口地址 (Base URL)</label>
|
|
50
|
-
<n-input
|
|
51
|
-
:value="conf.url"
|
|
52
|
-
type="text"
|
|
53
|
-
placeholder="https://api.openai.com/v1"
|
|
54
|
-
size="medium"
|
|
55
|
-
clearable
|
|
56
|
-
@update:value="onUrlChange"
|
|
57
|
-
/>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<!-- 超参数调节 -->
|
|
62
|
-
<div class="field-section">
|
|
63
|
-
<div class="section-header">
|
|
64
|
-
<span class="section-title">生成参数</span>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div class="form-row">
|
|
68
|
-
<div class="form-field flex-1">
|
|
69
|
-
<label class="field-label">随机温度 (Temperature): {{ conf.temp ?? 0.7 }}</label>
|
|
70
|
-
<div class="slider-box">
|
|
71
|
-
<n-slider
|
|
72
|
-
:value="conf.temp ?? 0.7"
|
|
73
|
-
:min="0"
|
|
74
|
-
:max="2"
|
|
75
|
-
:step="0.05"
|
|
76
|
-
style="flex: 1;"
|
|
77
|
-
@update:value="onTempChange"
|
|
78
|
-
/>
|
|
79
|
-
<n-input-number
|
|
80
|
-
:value="conf.temp ?? 0.7"
|
|
81
|
-
:min="0"
|
|
82
|
-
:max="2"
|
|
83
|
-
:step="0.05"
|
|
84
|
-
size="small"
|
|
85
|
-
style="width: 76px;"
|
|
86
|
-
@update:value="onTempChange"
|
|
87
|
-
/>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div class="form-field flex-1">
|
|
92
|
-
<label class="field-label">最大 Token 数</label>
|
|
93
|
-
<n-input-number
|
|
94
|
-
:value="conf.tokens ?? 2048"
|
|
95
|
-
:min="128"
|
|
96
|
-
:max="16384"
|
|
97
|
-
:step="256"
|
|
98
|
-
size="medium"
|
|
99
|
-
style="width: 100%;"
|
|
100
|
-
@update:value="onTokensChange"
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<div class="form-field">
|
|
106
|
-
<label class="field-label">系统人设 (System Prompt)</label>
|
|
107
|
-
<n-input
|
|
108
|
-
:value="conf.sys"
|
|
109
|
-
type="textarea"
|
|
110
|
-
placeholder="定义 AI 的角色定位与回答风格..."
|
|
111
|
-
:autosize="{ minRows: 2.5, maxRows: 4.5 }"
|
|
112
|
-
size="medium"
|
|
113
|
-
@update:value="onSysChange"
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<!-- 左侧底部操作栏 -->
|
|
120
|
-
<div class="params-footer">
|
|
121
|
-
<div class="footer-actions">
|
|
122
|
-
<hlw-button
|
|
123
|
-
size="medium"
|
|
124
|
-
type="primary"
|
|
125
|
-
secondary
|
|
126
|
-
:loading="testing"
|
|
127
|
-
:icon="PulseOutline"
|
|
128
|
-
@click="onTest"
|
|
129
|
-
>
|
|
130
|
-
测试连接
|
|
131
|
-
</hlw-button>
|
|
132
|
-
|
|
133
|
-
<hlw-button
|
|
134
|
-
size="medium"
|
|
135
|
-
secondary
|
|
136
|
-
:icon="RefreshOutline"
|
|
137
|
-
@click="onReset"
|
|
138
|
-
>
|
|
139
|
-
恢复默认
|
|
140
|
-
</hlw-button>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<n-tag
|
|
144
|
-
v-if="testResult"
|
|
145
|
-
:type="testResult.success ? 'success' : 'error'"
|
|
146
|
-
size="small"
|
|
147
|
-
round
|
|
148
|
-
:bordered="false"
|
|
149
|
-
>
|
|
150
|
-
<template #icon>
|
|
151
|
-
<n-icon>
|
|
152
|
-
<CheckmarkCircleOutline v-if="testResult.success" />
|
|
153
|
-
<CloseCircleOutline v-else />
|
|
154
|
-
</n-icon>
|
|
155
|
-
</template>
|
|
156
|
-
{{ testResult.success ? `已连通 (${testResult.latency}ms)` : '连接失败' }}
|
|
157
|
-
</n-tag>
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
</template>
|
|
161
|
-
|
|
162
|
-
<script setup lang="ts">
|
|
163
|
-
import { ref, computed } from 'vue'
|
|
164
|
-
import {
|
|
165
|
-
PulseOutline,
|
|
166
|
-
RefreshOutline,
|
|
167
|
-
CheckmarkCircleOutline,
|
|
168
|
-
CloseCircleOutline,
|
|
169
|
-
OpenOutline
|
|
170
|
-
} from '@vicons/ionicons5'
|
|
171
|
-
import { NInput, NSelect, NSlider, NInputNumber, NTag, NIcon } from 'naive-ui'
|
|
172
|
-
import { tools } from '@hlw-zbzs/core'
|
|
173
|
-
import { toast, HlwButton } from '@hlw-zbzs/vue'
|
|
174
|
-
import { useAi } from '../core'
|
|
175
|
-
import type { AiTest } from '../types'
|
|
176
|
-
|
|
177
|
-
const {
|
|
178
|
-
engine,
|
|
179
|
-
conf,
|
|
180
|
-
info,
|
|
181
|
-
save,
|
|
182
|
-
reset,
|
|
183
|
-
test
|
|
184
|
-
} = useAi()
|
|
185
|
-
|
|
186
|
-
function openPortal(url?: string) {
|
|
187
|
-
if (!url) return
|
|
188
|
-
if (tools?.openExternal) {
|
|
189
|
-
tools.openExternal(url)
|
|
190
|
-
} else {
|
|
191
|
-
window.open(url, '_blank')
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const testing = ref(false)
|
|
196
|
-
const testResult = ref<AiTest | null>(null)
|
|
197
|
-
|
|
198
|
-
const modelOptions = computed(() => {
|
|
199
|
-
const models = info.value.models || []
|
|
200
|
-
return models.map((item) => ({
|
|
201
|
-
label: item,
|
|
202
|
-
value: item
|
|
203
|
-
}))
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
function onKeyChange(val: string) {
|
|
207
|
-
save(engine.value, { key: val.trim() })
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function onUrlChange(val: string) {
|
|
211
|
-
save(engine.value, { url: val.trim() })
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function onModelChange(val: string) {
|
|
215
|
-
save(engine.value, { model: val.trim() })
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function onTempChange(val: number | null) {
|
|
219
|
-
if (val !== null && val !== undefined) {
|
|
220
|
-
save(engine.value, { temp: val })
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function onTokensChange(val: number | null) {
|
|
225
|
-
if (val) save(engine.value, { tokens: val })
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function onSysChange(val: string) {
|
|
229
|
-
save(engine.value, { sys: val })
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function onTest() {
|
|
233
|
-
testing.value = true
|
|
234
|
-
testResult.value = null
|
|
235
|
-
try {
|
|
236
|
-
const res = await test()
|
|
237
|
-
testResult.value = res
|
|
238
|
-
if (res.success) {
|
|
239
|
-
toast(`连接成功,耗时 ${res.latency}ms`, 'success')
|
|
240
|
-
} else {
|
|
241
|
-
toast(`连接失败: ${res.msg}`, 'error')
|
|
242
|
-
}
|
|
243
|
-
} catch (err: any) {
|
|
244
|
-
testResult.value = {
|
|
245
|
-
success: false,
|
|
246
|
-
msg: err?.message || '连接异常',
|
|
247
|
-
latency: 0
|
|
248
|
-
}
|
|
249
|
-
toast(`连接异常: ${err?.message || '未知错误'}`, 'error')
|
|
250
|
-
} finally {
|
|
251
|
-
testing.value = false
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function onReset() {
|
|
256
|
-
reset(engine.value)
|
|
257
|
-
testResult.value = null
|
|
258
|
-
toast(`已恢复【${info.value.name}】为默认配置`, 'info')
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
defineExpose({
|
|
262
|
-
clearTest: () => {
|
|
263
|
-
testResult.value = null
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
</script>
|
|
267
|
-
|
|
268
|
-
<style scoped lang="scss">
|
|
269
|
-
.params-container {
|
|
270
|
-
display: flex;
|
|
271
|
-
flex-direction: column;
|
|
272
|
-
gap: 16px;
|
|
273
|
-
padding: 16px 16px 0 16px;
|
|
274
|
-
|
|
275
|
-
.params-fields {
|
|
276
|
-
display: flex;
|
|
277
|
-
flex-direction: column;
|
|
278
|
-
gap: 16px;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.field-section {
|
|
282
|
-
display: flex;
|
|
283
|
-
flex-direction: column;
|
|
284
|
-
gap: 10px;
|
|
285
|
-
|
|
286
|
-
.section-header {
|
|
287
|
-
display: flex;
|
|
288
|
-
align-items: center;
|
|
289
|
-
justify-content: space-between;
|
|
290
|
-
border-bottom: 1px solid #f1f5f9;
|
|
291
|
-
padding-bottom: 6px;
|
|
292
|
-
|
|
293
|
-
.section-title {
|
|
294
|
-
font-size: 14px;
|
|
295
|
-
font-weight: 400;
|
|
296
|
-
color: #1e293b;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.form-row {
|
|
301
|
-
display: flex;
|
|
302
|
-
gap: 12px;
|
|
303
|
-
|
|
304
|
-
.flex-1 {
|
|
305
|
-
flex: 1;
|
|
306
|
-
min-width: 0;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.flex-2 {
|
|
310
|
-
flex: 2;
|
|
311
|
-
min-width: 0;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.form-field {
|
|
316
|
-
display: flex;
|
|
317
|
-
flex-direction: column;
|
|
318
|
-
gap: 6px;
|
|
319
|
-
|
|
320
|
-
.field-label {
|
|
321
|
-
font-size: 13px;
|
|
322
|
-
font-weight: 400;
|
|
323
|
-
color: #475569;
|
|
324
|
-
display: flex;
|
|
325
|
-
align-items: center;
|
|
326
|
-
justify-content: space-between;
|
|
327
|
-
|
|
328
|
-
.label-title {
|
|
329
|
-
display: flex;
|
|
330
|
-
align-items: center;
|
|
331
|
-
gap: 4px;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.req-star {
|
|
335
|
-
color: #ef4444;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.free-text {
|
|
339
|
-
color: #059669;
|
|
340
|
-
font-size: 12px;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.portal-link {
|
|
344
|
-
display: inline-flex;
|
|
345
|
-
align-items: center;
|
|
346
|
-
gap: 3px;
|
|
347
|
-
font-size: 12px;
|
|
348
|
-
font-weight: 400;
|
|
349
|
-
color: #2563eb;
|
|
350
|
-
cursor: pointer;
|
|
351
|
-
text-decoration: none;
|
|
352
|
-
transition: all 0.15s ease;
|
|
353
|
-
|
|
354
|
-
&:hover {
|
|
355
|
-
color: #1d4ed8;
|
|
356
|
-
text-decoration: underline;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.slider-box {
|
|
362
|
-
display: flex;
|
|
363
|
-
align-items: center;
|
|
364
|
-
gap: 10px;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.params-footer {
|
|
370
|
-
height: 54px;
|
|
371
|
-
margin-top: auto;
|
|
372
|
-
border-top: 1px solid #e2e8f0;
|
|
373
|
-
display: flex;
|
|
374
|
-
align-items: center;
|
|
375
|
-
justify-content: space-between;
|
|
376
|
-
padding: 0 16px;
|
|
377
|
-
margin-left: -16px;
|
|
378
|
-
margin-right: -16px;
|
|
379
|
-
background: #ffffff;
|
|
380
|
-
|
|
381
|
-
.footer-actions {
|
|
382
|
-
display: flex;
|
|
383
|
-
align-items: center;
|
|
384
|
-
gap: 8px;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
</style>
|
package/src/core/index.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file AI 核心响应式 Hook (单单词极简命名,零冗余代码)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { computed, ref } from 'vue'
|
|
6
|
-
import { storeToRefs } from 'pinia'
|
|
7
|
-
import { useAiStore } from '../store'
|
|
8
|
-
import { Engine } from '../engines/base'
|
|
9
|
-
import type {
|
|
10
|
-
AiConfig,
|
|
11
|
-
AiEngine,
|
|
12
|
-
AiHandler,
|
|
13
|
-
AiInfo,
|
|
14
|
-
AiMsg,
|
|
15
|
-
AiOption,
|
|
16
|
-
AiResult,
|
|
17
|
-
AiTest
|
|
18
|
-
} from '../types'
|
|
19
|
-
|
|
20
|
-
const engines = new Map<AiEngine, Engine>()
|
|
21
|
-
|
|
22
|
-
function getEngine(type: AiEngine, conf: AiConfig): Engine {
|
|
23
|
-
let instance = engines.get(type)
|
|
24
|
-
if (!instance) {
|
|
25
|
-
instance = new Engine(type, conf)
|
|
26
|
-
engines.set(type, instance)
|
|
27
|
-
} else {
|
|
28
|
-
instance.save(conf)
|
|
29
|
-
}
|
|
30
|
-
return instance
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function useAi() {
|
|
34
|
-
const store = useAiStore()
|
|
35
|
-
const { engine, enabled } = storeToRefs(store)
|
|
36
|
-
|
|
37
|
-
const loading = ref(false)
|
|
38
|
-
const text = ref('')
|
|
39
|
-
const think = ref('')
|
|
40
|
-
let controller: AbortController | null = null
|
|
41
|
-
|
|
42
|
-
const current = computed<Engine>(() => getEngine(store.engine, store.conf))
|
|
43
|
-
const info = computed<AiInfo>(() => store.info)
|
|
44
|
-
const conf = computed<AiConfig>(() => store.conf)
|
|
45
|
-
const ready = computed<boolean>(() => store.ready)
|
|
46
|
-
const list = computed<AiInfo[]>(() => store.list)
|
|
47
|
-
|
|
48
|
-
function select(type: AiEngine) {
|
|
49
|
-
store.set(type)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function abort() {
|
|
53
|
-
if (controller) {
|
|
54
|
-
controller.abort()
|
|
55
|
-
controller = null
|
|
56
|
-
}
|
|
57
|
-
loading.value = false
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function chat(
|
|
61
|
-
input: string | AiMsg[],
|
|
62
|
-
option?: Partial<AiOption>,
|
|
63
|
-
type?: AiEngine
|
|
64
|
-
): Promise<string> {
|
|
65
|
-
if (!enabled.value) throw new Error('AI 功能已停用')
|
|
66
|
-
loading.value = true
|
|
67
|
-
text.value = ''
|
|
68
|
-
think.value = ''
|
|
69
|
-
controller = new AbortController()
|
|
70
|
-
|
|
71
|
-
const target = type ? getEngine(type, store.configs[type] || store.conf) : current.value
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const list: AiMsg[] =
|
|
75
|
-
typeof input === 'string' ? [{ role: 'user', text: input }] : input
|
|
76
|
-
|
|
77
|
-
const res: AiResult = await target.chat({
|
|
78
|
-
list,
|
|
79
|
-
signal: controller.signal,
|
|
80
|
-
...option
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
text.value = res.text
|
|
84
|
-
think.value = res.think || ''
|
|
85
|
-
return res.text
|
|
86
|
-
} finally {
|
|
87
|
-
loading.value = false
|
|
88
|
-
controller = null
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function stream(
|
|
93
|
-
input: string | AiMsg[],
|
|
94
|
-
handler: AiHandler = {},
|
|
95
|
-
type?: AiEngine
|
|
96
|
-
): Promise<string> {
|
|
97
|
-
if (!enabled.value) throw new Error('AI 功能已停用')
|
|
98
|
-
loading.value = true
|
|
99
|
-
text.value = ''
|
|
100
|
-
think.value = ''
|
|
101
|
-
controller = new AbortController()
|
|
102
|
-
|
|
103
|
-
const target = type ? getEngine(type, store.configs[type] || store.conf) : current.value
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const list: AiMsg[] =
|
|
107
|
-
typeof input === 'string' ? [{ role: 'user', text: input }] : input
|
|
108
|
-
|
|
109
|
-
const res: AiResult = await target.stream(
|
|
110
|
-
{
|
|
111
|
-
list,
|
|
112
|
-
signal: controller.signal
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
onToken: (delta) => {
|
|
116
|
-
text.value += delta
|
|
117
|
-
handler.onToken?.(delta)
|
|
118
|
-
},
|
|
119
|
-
onThink: (delta) => {
|
|
120
|
-
think.value += delta
|
|
121
|
-
handler.onThink?.(delta)
|
|
122
|
-
},
|
|
123
|
-
onDone: (fullText, fullThink) => {
|
|
124
|
-
text.value = fullText
|
|
125
|
-
think.value = fullThink || ''
|
|
126
|
-
handler.onDone?.(fullText, fullThink)
|
|
127
|
-
},
|
|
128
|
-
onError: (err) => {
|
|
129
|
-
handler.onError?.(err)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
return res.text
|
|
135
|
-
} finally {
|
|
136
|
-
loading.value = false
|
|
137
|
-
controller = null
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function test(type?: AiEngine, tempConf?: Partial<AiConfig>): Promise<AiTest> {
|
|
142
|
-
const targetType = type || store.engine
|
|
143
|
-
const targetConf = store.configs[targetType] || store.conf
|
|
144
|
-
const target = getEngine(targetType, targetConf)
|
|
145
|
-
return target.test(tempConf)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
store,
|
|
150
|
-
engine,
|
|
151
|
-
enabled,
|
|
152
|
-
conf,
|
|
153
|
-
info,
|
|
154
|
-
list,
|
|
155
|
-
ready,
|
|
156
|
-
loading: computed(() => loading.value),
|
|
157
|
-
text: computed(() => text.value),
|
|
158
|
-
think: computed(() => think.value),
|
|
159
|
-
select,
|
|
160
|
-
chat,
|
|
161
|
-
stream,
|
|
162
|
-
test,
|
|
163
|
-
abort,
|
|
164
|
-
save: store.save.bind(store),
|
|
165
|
-
reset: store.reset.bind(store)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export default useAi
|