@veltra/ai 2.1.10 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/use-chat.d.ts +15 -0
- package/dist/chat/use-chat.js +6 -0
- package/dist/chat/use-chat.js.map +1 -1
- package/dist/components/ai-chat/ai-chat.js +3 -1
- package/dist/components/ai-chat/ai-chat.js.map +1 -1
- package/dist/components/ai-chat/chat-input.js +160 -77
- package/dist/components/ai-chat/chat-input.js.map +1 -1
- package/dist/components/ai-chat/format-duration.js +13 -0
- package/dist/components/ai-chat/format-duration.js.map +1 -0
- package/dist/components/ai-chat/message-item.js +32 -23
- package/dist/components/ai-chat/message-item.js.map +1 -1
- package/dist/components/ai-chat/message-list.js +40 -5
- package/dist/components/ai-chat/message-list.js.map +1 -1
- package/dist/components/ai-chat/model-picker.js +31 -95
- package/dist/components/ai-chat/model-picker.js.map +1 -1
- package/dist/components/ai-chat/reasoning-picker.js +68 -0
- package/dist/components/ai-chat/reasoning-picker.js.map +1 -0
- package/dist/components/ai-chat/style2.css +123 -68
- package/dist/providers/types.d.ts +2 -0
- package/dist/types/ai-chat.d.ts +2 -1
- package/package.json +1 -1
- package/src/chat/use-chat.ts +6 -0
- package/src/components/ai-chat/__test__/ai-chat.test.ts +79 -28
- package/src/components/ai-chat/ai-chat.vue +2 -0
- package/src/components/ai-chat/chat-input.vue +91 -16
- package/src/components/ai-chat/format-duration.ts +8 -0
- package/src/components/ai-chat/message-item.vue +6 -0
- package/src/components/ai-chat/message-list.vue +48 -1
- package/src/components/ai-chat/model-picker.vue +11 -110
- package/src/components/ai-chat/reasoning-picker.vue +70 -0
- package/src/components/ai-chat/style.scss +137 -72
- package/src/providers/types.ts +2 -0
- package/src/types/ai-chat.ts +2 -1
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
<UDropdown
|
|
3
3
|
ref="dropdownRef"
|
|
4
4
|
trigger="click"
|
|
5
|
+
direction="top"
|
|
6
|
+
alignment="end"
|
|
5
7
|
width="auto"
|
|
6
|
-
min-width="
|
|
8
|
+
min-width="240px"
|
|
7
9
|
:content-class="cls.e('model-panel')"
|
|
8
|
-
@update:visible="handleVisibleChange"
|
|
9
10
|
>
|
|
10
11
|
<template #trigger>
|
|
11
12
|
<button type="button" :class="cls.e('model-trigger')">
|
|
12
13
|
<span :class="cls.e('model-trigger-label')">
|
|
13
14
|
{{ currentModel?.label ?? model ?? '模型' }}
|
|
14
15
|
</span>
|
|
15
|
-
<span v-if="currentReasoningLabel" :class="cls.e('model-trigger-reasoning')">
|
|
16
|
-
{{ currentReasoningLabel }}
|
|
17
|
-
</span>
|
|
18
16
|
<UIcon :class="cls.e('model-trigger-arrow')">
|
|
19
17
|
<ArrowDown />
|
|
20
18
|
</UIcon>
|
|
@@ -22,7 +20,7 @@
|
|
|
22
20
|
</template>
|
|
23
21
|
|
|
24
22
|
<template #content>
|
|
25
|
-
<
|
|
23
|
+
<UScroll :class="cls.e('model-panel-list')">
|
|
26
24
|
<div
|
|
27
25
|
v-for="item in models"
|
|
28
26
|
:key="item.id"
|
|
@@ -31,72 +29,25 @@
|
|
|
31
29
|
<div :class="cls.e('model-option-main')" @click="handleSelectModel(item)">
|
|
32
30
|
<div :class="cls.e('model-option-head')">
|
|
33
31
|
<span :class="cls.e('model-option-name')">{{ item.label ?? item.id }}</span>
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
type="button"
|
|
38
|
-
:class="[
|
|
39
|
-
cls.e('model-option-reasoning'),
|
|
40
|
-
bem.is('expanded', expandedReasoningId === item.id)
|
|
41
|
-
]"
|
|
42
|
-
@click.stop="toggleReasoning(item)"
|
|
43
|
-
>
|
|
44
|
-
<span>{{ getReasoningLabel(item) }}</span>
|
|
45
|
-
<UIcon :class="cls.e('model-option-reasoning-arrow')">
|
|
46
|
-
<ArrowDown />
|
|
47
|
-
</UIcon>
|
|
48
|
-
</button>
|
|
49
|
-
<!-- 勾选槽位固定占位:未选中也保留位置,保证推理等级按钮跨行对齐 -->
|
|
50
|
-
<UIcon :class="[cls.e('model-option-check'), bem.is('hidden', item.id !== model)]">
|
|
51
|
-
<Check />
|
|
52
|
-
</UIcon>
|
|
53
|
-
</span>
|
|
32
|
+
<UIcon v-if="item.id === model" :class="cls.e('model-option-check')">
|
|
33
|
+
<Check />
|
|
34
|
+
</UIcon>
|
|
54
35
|
</div>
|
|
55
36
|
<div v-if="item.description" :class="cls.e('model-option-desc')">
|
|
56
37
|
{{ item.description }}
|
|
57
38
|
</div>
|
|
58
39
|
</div>
|
|
59
|
-
|
|
60
|
-
<div
|
|
61
|
-
v-if="item.reasoningLevels?.length"
|
|
62
|
-
:class="[
|
|
63
|
-
cls.e('reasoning-levels'),
|
|
64
|
-
bem.is('expanded', expandedReasoningId === item.id)
|
|
65
|
-
]"
|
|
66
|
-
>
|
|
67
|
-
<div :class="cls.e('reasoning-levels-inner')">
|
|
68
|
-
<div
|
|
69
|
-
v-for="level in item.reasoningLevels"
|
|
70
|
-
:key="level.value"
|
|
71
|
-
:class="[
|
|
72
|
-
cls.e('reasoning-option'),
|
|
73
|
-
bem.is('active', isReasoningActive(item, level.value))
|
|
74
|
-
]"
|
|
75
|
-
@click="handleSelectReasoning(item, level.value)"
|
|
76
|
-
>
|
|
77
|
-
<span>{{ level.label }}</span>
|
|
78
|
-
<UIcon
|
|
79
|
-
:class="[
|
|
80
|
-
cls.e('model-option-check'),
|
|
81
|
-
bem.is('hidden', !isReasoningActive(item, level.value))
|
|
82
|
-
]"
|
|
83
|
-
>
|
|
84
|
-
<Check />
|
|
85
|
-
</UIcon>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
40
|
</div>
|
|
90
|
-
</
|
|
41
|
+
</UScroll>
|
|
91
42
|
</template>
|
|
92
43
|
</UDropdown>
|
|
93
44
|
</template>
|
|
94
45
|
|
|
95
46
|
<script lang="ts" setup>
|
|
96
|
-
import { UDropdown, UIcon } from '@veltra/desktop'
|
|
47
|
+
import { UDropdown, UIcon, UScroll } from '@veltra/desktop'
|
|
97
48
|
import { ArrowDown, Check } from '@veltra/icons/normal'
|
|
98
49
|
import { bem } from '@veltra/utils'
|
|
99
|
-
import { computed, inject,
|
|
50
|
+
import { computed, inject, useTemplateRef } from 'vue'
|
|
100
51
|
|
|
101
52
|
import type { ChatModelOption } from '../../providers'
|
|
102
53
|
import { AiChatDIKey } from './di'
|
|
@@ -109,69 +60,19 @@ const props = defineProps<{
|
|
|
109
60
|
}>()
|
|
110
61
|
|
|
111
62
|
const model = defineModel<string | undefined>('model')
|
|
112
|
-
const reasoningLevel = defineModel<string | undefined>('reasoningLevel')
|
|
113
63
|
|
|
114
64
|
const di = inject(AiChatDIKey)
|
|
115
65
|
const cls = di?.cls ?? bem('ai-chat')
|
|
116
66
|
|
|
117
67
|
const dropdownRef = useTemplateRef('dropdownRef')
|
|
118
68
|
|
|
119
|
-
/** 当前内联展开思考强度的模型 id(手风琴,同时只展开一个) */
|
|
120
|
-
const expandedReasoningId = shallowRef<string>()
|
|
121
|
-
|
|
122
69
|
const currentModel = computed(() => {
|
|
123
70
|
return props.models.find((m) => m.id === model.value)
|
|
124
71
|
})
|
|
125
72
|
|
|
126
|
-
|
|
127
|
-
return currentModel.value?.reasoningLevels?.find((level) => level.value === reasoningLevel.value)
|
|
128
|
-
?.label
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
/** 取模型默认推理等级:显式配置优先,缺省落到首个可选等级 */
|
|
132
|
-
const resolveDefaultReasoningLevel = (item: ChatModelOption) => {
|
|
133
|
-
const levels = item.reasoningLevels ?? []
|
|
134
|
-
if (!levels.length) return undefined
|
|
135
|
-
return (
|
|
136
|
-
levels.find((level) => level.value === item.defaultReasoningLevel)?.value ?? levels[0]?.value
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** 胶囊文案:已选中模型展示当前推理等级,其余展示该模型的默认等级 */
|
|
141
|
-
const getReasoningLabel = (item: ChatModelOption) => {
|
|
142
|
-
if (item.id === model.value && currentReasoningLabel.value) {
|
|
143
|
-
return currentReasoningLabel.value
|
|
144
|
-
}
|
|
145
|
-
return item.reasoningLevels?.find((level) => level.value === resolveDefaultReasoningLevel(item))
|
|
146
|
-
?.label
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const isReasoningActive = (item: ChatModelOption, value: string) => {
|
|
150
|
-
return item.id === model.value && reasoningLevel.value === value
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** 关闭时收起内联思考强度,下次打开仍从模型列表开始 */
|
|
154
|
-
const handleVisibleChange = (visible: boolean) => {
|
|
155
|
-
if (!visible) expandedReasoningId.value = undefined
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** 点击模型主体:直接选中模型,并同步到该模型默认推理等级 */
|
|
73
|
+
/** 选中模型即关闭;推理等级由 useChat 的模型联动校正 */
|
|
159
74
|
const handleSelectModel = (item: ChatModelOption) => {
|
|
160
75
|
model.value = item.id
|
|
161
|
-
reasoningLevel.value = resolveDefaultReasoningLevel(item)
|
|
162
|
-
dropdownRef.value?.close()
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** 点击推理等级胶囊:内联展开/收起该模型的思考强度 */
|
|
166
|
-
const toggleReasoning = (item: ChatModelOption) => {
|
|
167
|
-
expandedReasoningId.value = expandedReasoningId.value === item.id ? undefined : item.id
|
|
168
|
-
nextTick(() => dropdownRef.value?.updateDropdown())
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** 选中思考强度:同时切到该等级所属模型,避免模型与推理等级错配 */
|
|
172
|
-
const handleSelectReasoning = (item: ChatModelOption, value: string) => {
|
|
173
|
-
model.value = item.id
|
|
174
|
-
reasoningLevel.value = value
|
|
175
76
|
dropdownRef.value?.close()
|
|
176
77
|
}
|
|
177
78
|
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UDropdown
|
|
3
|
+
v-if="levels.length > 1"
|
|
4
|
+
ref="dropdownRef"
|
|
5
|
+
trigger="click"
|
|
6
|
+
direction="top"
|
|
7
|
+
alignment="end"
|
|
8
|
+
width="auto"
|
|
9
|
+
min-width="140px"
|
|
10
|
+
:content-class="cls.e('reasoning-panel')"
|
|
11
|
+
>
|
|
12
|
+
<template #trigger>
|
|
13
|
+
<button type="button" :class="cls.e('reasoning-trigger')" title="推理等级">
|
|
14
|
+
<span :class="cls.e('reasoning-trigger-label')">{{ currentLabel }}</span>
|
|
15
|
+
<UIcon :class="cls.e('reasoning-trigger-arrow')">
|
|
16
|
+
<ArrowDown />
|
|
17
|
+
</UIcon>
|
|
18
|
+
</button>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<template #content>
|
|
22
|
+
<div :class="cls.e('reasoning-list')">
|
|
23
|
+
<div
|
|
24
|
+
v-for="level in levels"
|
|
25
|
+
:key="level.value"
|
|
26
|
+
:class="[cls.e('reasoning-option'), bem.is('active', level.value === reasoningLevel)]"
|
|
27
|
+
@click="handleSelect(level.value)"
|
|
28
|
+
>
|
|
29
|
+
<span>{{ level.label }}</span>
|
|
30
|
+
<UIcon v-if="level.value === reasoningLevel" :class="cls.e('reasoning-check')">
|
|
31
|
+
<Check />
|
|
32
|
+
</UIcon>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
</UDropdown>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script lang="ts" setup>
|
|
40
|
+
import { UDropdown, UIcon } from '@veltra/desktop'
|
|
41
|
+
import { ArrowDown, Check } from '@veltra/icons/normal'
|
|
42
|
+
import { bem } from '@veltra/utils'
|
|
43
|
+
import { computed, inject, useTemplateRef } from 'vue'
|
|
44
|
+
|
|
45
|
+
import type { ChatReasoningLevel } from '../../providers'
|
|
46
|
+
import { AiChatDIKey } from './di'
|
|
47
|
+
|
|
48
|
+
defineOptions({ name: 'UAiChatReasoningPicker' })
|
|
49
|
+
|
|
50
|
+
const { levels } = defineProps<{
|
|
51
|
+
/** 当前模型的推理等级列表;不足两档时没有切换意义,不渲染 */
|
|
52
|
+
levels: ChatReasoningLevel[]
|
|
53
|
+
}>()
|
|
54
|
+
|
|
55
|
+
const reasoningLevel = defineModel<string | undefined>('reasoningLevel')
|
|
56
|
+
|
|
57
|
+
const di = inject(AiChatDIKey)
|
|
58
|
+
const cls = di?.cls ?? bem('ai-chat')
|
|
59
|
+
|
|
60
|
+
const dropdownRef = useTemplateRef('dropdownRef')
|
|
61
|
+
|
|
62
|
+
const currentLabel = computed(() => {
|
|
63
|
+
return levels.find((level) => level.value === reasoningLevel.value)?.label ?? '推理'
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const handleSelect = (value: string) => {
|
|
67
|
+
reasoningLevel.value = value
|
|
68
|
+
dropdownRef.value?.close()
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
@@ -178,12 +178,14 @@ $root-name: ai-chat;
|
|
|
178
178
|
color: fn.use-var(text-color, assist);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// 负边距吃掉主列横向 padding、顶到主列边缘:滚动条出现在容器右侧而非贴着会话列表;
|
|
182
|
+
// 800px 限宽下沉到 UScroll 内容层(__list-content)
|
|
181
183
|
@include m.e(list-wrap) {
|
|
182
184
|
position: relative;
|
|
183
185
|
flex: 1;
|
|
184
186
|
min-height: 0;
|
|
185
|
-
width: 100
|
|
186
|
-
|
|
187
|
+
width: calc(100% + #{fn.use-var(gap, default)} * 2);
|
|
188
|
+
margin: 0 calc(#{fn.use-var(gap, default)} * -1);
|
|
187
189
|
display: flex;
|
|
188
190
|
flex-direction: column;
|
|
189
191
|
// 纵向留出呼吸位:首条消息不贴容器顶,滚动时内容不顶边;横向由各消息自身 padding 控制
|
|
@@ -191,6 +193,16 @@ $root-name: ai-chat;
|
|
|
191
193
|
box-sizing: border-box;
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
// UScroll 内容层:左右 gap 补偿 list-wrap 负边距,宽屏内层 800px、窄屏与主列内容盒同宽,
|
|
197
|
+
// 消息区视觉几何与限宽在 list-wrap 时一致
|
|
198
|
+
@include m.e(list-content) {
|
|
199
|
+
width: 100%;
|
|
200
|
+
max-width: calc(800px + #{fn.use-var(gap, default)} * 2);
|
|
201
|
+
margin: 0 auto;
|
|
202
|
+
padding: 0 fn.use-var(gap, default);
|
|
203
|
+
box-sizing: border-box;
|
|
204
|
+
}
|
|
205
|
+
|
|
194
206
|
@include m.e(list) {
|
|
195
207
|
flex: 1;
|
|
196
208
|
min-height: 0;
|
|
@@ -200,6 +212,8 @@ $root-name: ai-chat;
|
|
|
200
212
|
@include m.e(welcome) {
|
|
201
213
|
flex-shrink: 0;
|
|
202
214
|
width: 100%;
|
|
215
|
+
max-width: 800px;
|
|
216
|
+
margin: 0 auto;
|
|
203
217
|
height: 48px;
|
|
204
218
|
@include m.flex(flex, flex-start, center);
|
|
205
219
|
color: fn.use-var(text-color, assist);
|
|
@@ -303,11 +317,11 @@ $root-name: ai-chat;
|
|
|
303
317
|
transform: translateY(-6px);
|
|
304
318
|
}
|
|
305
319
|
|
|
306
|
-
//
|
|
320
|
+
// 生成中的统一工作状态指示(列表末尾);横向定位由 __list-content 的 padding 提供
|
|
307
321
|
@include m.e(working) {
|
|
308
322
|
@include m.flex(flex, flex-start, center);
|
|
309
323
|
gap: fn.use-var(gap, small);
|
|
310
|
-
padding: 0
|
|
324
|
+
padding: 0 0 fn.use-var(gap, small);
|
|
311
325
|
width: fit-content;
|
|
312
326
|
|
|
313
327
|
// 工作开始:立即从 0 放大(无延时),与输入框上方缩小形成对向跳
|
|
@@ -334,6 +348,13 @@ $root-name: ai-chat;
|
|
|
334
348
|
font-size: fn.use-var(font-size-main, small);
|
|
335
349
|
}
|
|
336
350
|
|
|
351
|
+
// 工作计时 / 收尾停留期间的总用时
|
|
352
|
+
@include m.e(working-time) {
|
|
353
|
+
font-size: fn.use-var(font-size-main, small);
|
|
354
|
+
color: fn.use-var(text-color, assist);
|
|
355
|
+
font-variant-numeric: tabular-nums;
|
|
356
|
+
}
|
|
357
|
+
|
|
337
358
|
@media (prefers-reduced-motion: reduce) {
|
|
338
359
|
.u-#{$root-name}__welcome.is-enter .u-ai-orb,
|
|
339
360
|
.u-#{$root-name}__working .u-ai-orb {
|
|
@@ -480,6 +501,14 @@ $root-name: ai-chat;
|
|
|
480
501
|
gap: fn.use-var(gap, small);
|
|
481
502
|
}
|
|
482
503
|
|
|
504
|
+
// 答案操作区前的本轮总用时
|
|
505
|
+
@include m.e(message-duration) {
|
|
506
|
+
align-self: center;
|
|
507
|
+
color: fn.use-var(text-color, assist);
|
|
508
|
+
font-size: fn.use-var(font-size-main, small);
|
|
509
|
+
font-variant-numeric: tabular-nums;
|
|
510
|
+
}
|
|
511
|
+
|
|
483
512
|
// 思考过程
|
|
484
513
|
@include m.e(reasoning) {
|
|
485
514
|
margin-bottom: 4px;
|
|
@@ -1108,12 +1137,40 @@ $root-name: ai-chat;
|
|
|
1108
1137
|
@include m.ellipsis;
|
|
1109
1138
|
}
|
|
1110
1139
|
|
|
1111
|
-
@include m.e(model-trigger-
|
|
1140
|
+
@include m.e(model-trigger-arrow) {
|
|
1112
1141
|
flex-shrink: 0;
|
|
1142
|
+
font-size: 12px;
|
|
1113
1143
|
color: fn.use-var(text-color, assist);
|
|
1114
1144
|
}
|
|
1115
1145
|
|
|
1116
|
-
@include m.e(
|
|
1146
|
+
@include m.e(reasoning-trigger) {
|
|
1147
|
+
@include m.flex(flex, flex-start, center);
|
|
1148
|
+
gap: 4px;
|
|
1149
|
+
flex-shrink: 0;
|
|
1150
|
+
padding: 2px fn.use-var(gap, small);
|
|
1151
|
+
border: none;
|
|
1152
|
+
border-radius: fn.use-var(radius, default);
|
|
1153
|
+
background: transparent;
|
|
1154
|
+
color: fn.use-var(text-color, assist);
|
|
1155
|
+
font-family: inherit;
|
|
1156
|
+
font-size: inherit;
|
|
1157
|
+
cursor: pointer;
|
|
1158
|
+
transition:
|
|
1159
|
+
background-color 0.15s ease,
|
|
1160
|
+
color 0.15s ease;
|
|
1161
|
+
|
|
1162
|
+
&:hover {
|
|
1163
|
+
background-color: fn.use-var(bg-color, hover);
|
|
1164
|
+
color: fn.use-var(text-color, main);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
@include m.e(reasoning-trigger-label) {
|
|
1169
|
+
@include m.ellipsis;
|
|
1170
|
+
max-width: 72px;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
@include m.e(reasoning-trigger-arrow) {
|
|
1117
1174
|
flex-shrink: 0;
|
|
1118
1175
|
font-size: 12px;
|
|
1119
1176
|
color: fn.use-var(text-color, assist);
|
|
@@ -1151,12 +1208,32 @@ $root-name: ai-chat;
|
|
|
1151
1208
|
}
|
|
1152
1209
|
}
|
|
1153
1210
|
|
|
1154
|
-
@include m.e(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1211
|
+
@include m.e(usage-trigger) {
|
|
1212
|
+
@include m.flex(inline-flex, center, center);
|
|
1213
|
+
flex-shrink: 0;
|
|
1214
|
+
padding: 2px;
|
|
1215
|
+
border: none;
|
|
1216
|
+
border-radius: 50%;
|
|
1217
|
+
background: transparent;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
@include m.e(usage-ring) {
|
|
1221
|
+
display: block;
|
|
1222
|
+
width: 14px;
|
|
1223
|
+
height: 14px;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
@include m.e(usage-ring-track) {
|
|
1227
|
+
fill: none;
|
|
1228
|
+
stroke: fn.use-var(border-color);
|
|
1229
|
+
stroke-width: 5;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
@include m.e(usage-ring-value) {
|
|
1233
|
+
fill: none;
|
|
1234
|
+
stroke: fn.use-var(color, primary);
|
|
1235
|
+
stroke-width: 5;
|
|
1236
|
+
transition: stroke-dasharray 0.3s ease;
|
|
1160
1237
|
}
|
|
1161
1238
|
|
|
1162
1239
|
@include m.e(input-stop) {
|
|
@@ -1201,13 +1278,38 @@ $root-name: ai-chat;
|
|
|
1201
1278
|
}
|
|
1202
1279
|
}
|
|
1203
1280
|
|
|
1281
|
+
// 用量明细面板经 teleport 挂载到 popper 容器,不处于 .u-ai-chat 内,需独立定义
|
|
1282
|
+
.u-#{$root-name}__usage-panel {
|
|
1283
|
+
padding: 6px 4px;
|
|
1284
|
+
user-select: none;
|
|
1285
|
+
|
|
1286
|
+
.u-#{$root-name}__usage-row {
|
|
1287
|
+
@include m.flex(flex, space-between, center);
|
|
1288
|
+
gap: 24px;
|
|
1289
|
+
padding: 3px 8px;
|
|
1290
|
+
font-size: fn.use-var(font-size-main, small);
|
|
1291
|
+
line-height: 1.6;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
.u-#{$root-name}__usage-label {
|
|
1295
|
+
color: fn.use-var(text-color, assist);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.u-#{$root-name}__usage-value {
|
|
1299
|
+
color: fn.use-var(text-color, main);
|
|
1300
|
+
font-variant-numeric: tabular-nums;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1204
1304
|
// 模型选择面板经 teleport 挂载到 popper 容器,不处于 .u-ai-chat 内,需独立定义
|
|
1205
1305
|
.u-#{$root-name}__model-panel {
|
|
1206
1306
|
padding: 4px;
|
|
1207
1307
|
user-select: none;
|
|
1208
1308
|
|
|
1309
|
+
// 面板从贴底的输入栏向上弹出,超出高度在列表内滚动
|
|
1209
1310
|
.u-#{$root-name}__model-panel-list {
|
|
1210
1311
|
min-width: 0;
|
|
1312
|
+
max-height: 280px;
|
|
1211
1313
|
}
|
|
1212
1314
|
|
|
1213
1315
|
.u-#{$root-name}__model-option-main {
|
|
@@ -1222,60 +1324,28 @@ $root-name: ai-chat;
|
|
|
1222
1324
|
}
|
|
1223
1325
|
|
|
1224
1326
|
.u-#{$root-name}__model-option-head {
|
|
1225
|
-
@include m.flex(flex,
|
|
1226
|
-
gap:
|
|
1327
|
+
@include m.flex(flex, flex-start, center);
|
|
1328
|
+
gap: 8px;
|
|
1329
|
+
min-width: 0;
|
|
1227
1330
|
color: fn.use-var(text-color, main);
|
|
1228
1331
|
}
|
|
1229
1332
|
|
|
1230
|
-
.u-#{$root-name}__model-option-
|
|
1231
|
-
|
|
1333
|
+
.u-#{$root-name}__model-option.is-active {
|
|
1334
|
+
.u-#{$root-name}__model-option-name {
|
|
1335
|
+
color: fn.use-var(color, primary);
|
|
1336
|
+
font-weight: 600;
|
|
1337
|
+
}
|
|
1232
1338
|
}
|
|
1233
1339
|
|
|
1234
|
-
.u-#{$root-name}__model-option-
|
|
1235
|
-
@include m.
|
|
1236
|
-
flex-shrink: 0;
|
|
1237
|
-
gap: 6px;
|
|
1340
|
+
.u-#{$root-name}__model-option-name {
|
|
1341
|
+
@include m.ellipsis;
|
|
1238
1342
|
}
|
|
1239
1343
|
|
|
1240
1344
|
.u-#{$root-name}__model-option-check {
|
|
1241
1345
|
flex-shrink: 0;
|
|
1346
|
+
margin-left: auto;
|
|
1242
1347
|
font-size: 14px;
|
|
1243
1348
|
color: fn.use-var(color, primary);
|
|
1244
|
-
|
|
1245
|
-
@include m.is(hidden) {
|
|
1246
|
-
visibility: hidden;
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
.u-#{$root-name}__model-option-reasoning {
|
|
1251
|
-
@include m.flex(flex, flex-start, center);
|
|
1252
|
-
gap: 2px;
|
|
1253
|
-
flex-shrink: 0;
|
|
1254
|
-
padding: 1px 6px;
|
|
1255
|
-
border: none;
|
|
1256
|
-
border-radius: fn.use-var(radius, small);
|
|
1257
|
-
background: fn.use-var(color, primary, 'light-9');
|
|
1258
|
-
color: fn.use-var(color, primary);
|
|
1259
|
-
font-size: fn.use-var(font-size-main, small);
|
|
1260
|
-
cursor: pointer;
|
|
1261
|
-
transition:
|
|
1262
|
-
background-color 0.15s ease,
|
|
1263
|
-
color 0.15s ease;
|
|
1264
|
-
|
|
1265
|
-
&:hover {
|
|
1266
|
-
background: fn.use-var(color, primary, 'light-7');
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
.u-#{$root-name}__model-option-reasoning-arrow {
|
|
1271
|
-
font-size: 12px;
|
|
1272
|
-
transition: transform 0.2s ease;
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
.u-#{$root-name}__model-option-reasoning.is-expanded {
|
|
1276
|
-
.u-#{$root-name}__model-option-reasoning-arrow {
|
|
1277
|
-
transform: rotate(180deg);
|
|
1278
|
-
}
|
|
1279
1349
|
}
|
|
1280
1350
|
|
|
1281
1351
|
.u-#{$root-name}__model-option-desc {
|
|
@@ -1283,28 +1353,17 @@ $root-name: ai-chat;
|
|
|
1283
1353
|
font-size: fn.use-var(font-size-main, small);
|
|
1284
1354
|
color: fn.use-var(text-color, assist);
|
|
1285
1355
|
}
|
|
1356
|
+
}
|
|
1286
1357
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
transition: grid-template-rows 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1292
|
-
|
|
1293
|
-
@include m.is(expanded) {
|
|
1294
|
-
grid-template-rows: 1fr;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
.u-#{$root-name}__reasoning-levels-inner {
|
|
1299
|
-
min-height: 0;
|
|
1300
|
-
overflow: hidden;
|
|
1301
|
-
}
|
|
1358
|
+
// 推理等级选择面板经 teleport 挂载到 popper 容器,同样需独立定义
|
|
1359
|
+
.u-#{$root-name}__reasoning-panel {
|
|
1360
|
+
padding: 4px;
|
|
1361
|
+
user-select: none;
|
|
1302
1362
|
|
|
1303
1363
|
.u-#{$root-name}__reasoning-option {
|
|
1304
1364
|
@include m.flex(flex, space-between, center);
|
|
1305
1365
|
gap: 12px;
|
|
1306
|
-
|
|
1307
|
-
padding: 5px 8px;
|
|
1366
|
+
padding: 6px 8px;
|
|
1308
1367
|
border-radius: calc(fn.use-var(radius, default) + 1px);
|
|
1309
1368
|
font-size: fn.use-var(font-size-main, small);
|
|
1310
1369
|
color: fn.use-var(text-color, main);
|
|
@@ -1321,4 +1380,10 @@ $root-name: ai-chat;
|
|
|
1321
1380
|
color: fn.use-var(color, primary);
|
|
1322
1381
|
}
|
|
1323
1382
|
}
|
|
1383
|
+
|
|
1384
|
+
.u-#{$root-name}__reasoning-check {
|
|
1385
|
+
flex-shrink: 0;
|
|
1386
|
+
font-size: 14px;
|
|
1387
|
+
color: fn.use-var(color, primary);
|
|
1388
|
+
}
|
|
1324
1389
|
}
|
package/src/providers/types.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ChatModel {
|
|
|
18
18
|
reasoningLevels?: ChatReasoningLevel[]
|
|
19
19
|
/** 默认推理等级(须落在 reasoningLevels 内) */
|
|
20
20
|
defaultReasoningLevel?: string
|
|
21
|
+
/** 上下文窗口上限(token)。配置后输入栏用量环按「最近一次请求总 token / 上限」显示占比 */
|
|
22
|
+
contextWindow?: number
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/** 模型服务商配置(内嵌 models) */
|
package/src/types/ai-chat.ts
CHANGED
|
@@ -52,7 +52,8 @@ export interface AiChatProps {
|
|
|
52
52
|
rendererProps?: Record<string, unknown>
|
|
53
53
|
/**
|
|
54
54
|
* 是否展示 token 用量明细(缓存命中 / 未命中;缺字段不显示)。
|
|
55
|
-
* 默认 false
|
|
55
|
+
* 默认 false:用量环明细面板只列 总 / 输入 / 输出;true 时追加有数据的缓存项。
|
|
56
|
+
* 接口未返回 usage 时不展示用量环。
|
|
56
57
|
*/
|
|
57
58
|
tokenUsageDetail?: boolean
|
|
58
59
|
/**
|