@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.
Files changed (33) hide show
  1. package/dist/chat/use-chat.d.ts +15 -0
  2. package/dist/chat/use-chat.js +6 -0
  3. package/dist/chat/use-chat.js.map +1 -1
  4. package/dist/components/ai-chat/ai-chat.js +3 -1
  5. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  6. package/dist/components/ai-chat/chat-input.js +160 -77
  7. package/dist/components/ai-chat/chat-input.js.map +1 -1
  8. package/dist/components/ai-chat/format-duration.js +13 -0
  9. package/dist/components/ai-chat/format-duration.js.map +1 -0
  10. package/dist/components/ai-chat/message-item.js +32 -23
  11. package/dist/components/ai-chat/message-item.js.map +1 -1
  12. package/dist/components/ai-chat/message-list.js +40 -5
  13. package/dist/components/ai-chat/message-list.js.map +1 -1
  14. package/dist/components/ai-chat/model-picker.js +31 -95
  15. package/dist/components/ai-chat/model-picker.js.map +1 -1
  16. package/dist/components/ai-chat/reasoning-picker.js +68 -0
  17. package/dist/components/ai-chat/reasoning-picker.js.map +1 -0
  18. package/dist/components/ai-chat/style2.css +123 -68
  19. package/dist/providers/types.d.ts +2 -0
  20. package/dist/types/ai-chat.d.ts +2 -1
  21. package/package.json +1 -1
  22. package/src/chat/use-chat.ts +6 -0
  23. package/src/components/ai-chat/__test__/ai-chat.test.ts +79 -28
  24. package/src/components/ai-chat/ai-chat.vue +2 -0
  25. package/src/components/ai-chat/chat-input.vue +91 -16
  26. package/src/components/ai-chat/format-duration.ts +8 -0
  27. package/src/components/ai-chat/message-item.vue +6 -0
  28. package/src/components/ai-chat/message-list.vue +48 -1
  29. package/src/components/ai-chat/model-picker.vue +11 -110
  30. package/src/components/ai-chat/reasoning-picker.vue +70 -0
  31. package/src/components/ai-chat/style.scss +137 -72
  32. package/src/providers/types.ts +2 -0
  33. 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="220px"
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
- <div :class="cls.e('model-panel-list')">
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
- <span :class="cls.e('model-option-actions')">
35
- <button
36
- v-if="item.reasoningLevels?.length"
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
- </div>
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, nextTick, shallowRef, useTemplateRef } from 'vue'
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
- const currentReasoningLabel = computed(() => {
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
- max-width: 800px;
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 fn.use-var(gap, default) fn.use-var(gap, small);
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-reasoning) {
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(model-trigger-arrow) {
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(input-usage) {
1155
- min-width: 0;
1156
- @include m.ellipsis;
1157
- font-size: fn.use-var(font-size-main, small);
1158
- color: fn.use-var(text-color, assist);
1159
- line-height: 1.4;
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, space-between, center);
1226
- gap: 12px;
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-name {
1231
- @include m.ellipsis;
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-actions {
1235
- @include m.flex(flex, flex-start, center);
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
- // 内联思考强度:grid 行高 0fr → 1fr 过渡,展开/收起平滑无跳变
1288
- .u-#{$root-name}__reasoning-levels {
1289
- display: grid;
1290
- grid-template-rows: 0fr;
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
- margin-left: 8px;
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
  }
@@ -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) */
@@ -52,7 +52,8 @@ export interface AiChatProps {
52
52
  rendererProps?: Record<string, unknown>
53
53
  /**
54
54
  * 是否展示 token 用量明细(缓存命中 / 未命中;缺字段不显示)。
55
- * 默认 false:仅在拿到 usage 时显示会话累计「总 token」。接口未返回 usage 时不展示。
55
+ * 默认 false:用量环明细面板只列 总 / 输入 / 输出;true 时追加有数据的缓存项。
56
+ * 接口未返回 usage 时不展示用量环。
56
57
  */
57
58
  tokenUsageDetail?: boolean
58
59
  /**