Skip to content

Commit 5081879

Browse files
jamebalBobDu
andauthored
feat: implement the expand/collapse function for reasoning process (#602)
* perf: implement the expand/collapse function for reasoning process * perf: correct the erroneous text * perf: optimize the display logic in thinking, after reasoning, hide the thoughts in the thinking * feat: expand reasoning text default Signed-off-by: Bob Du <[email protected]> --------- Signed-off-by: Bob Du <[email protected]> Co-authored-by: Bob Du <[email protected]>
1 parent 5a347c3 commit 5081879

File tree

8 files changed

+172
-12
lines changed

8 files changed

+172
-12
lines changed

src/locales/en-US.ts

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export default {
7878
deleteKeyConfirm: 'Are you sure to delete this key?',
7979
disable2FA: 'Disable 2FA',
8080
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
81+
thinking: 'Thinking',
82+
reasoningProcess: 'Reasoning Process',
83+
noReasoningProcess: 'No Reasoning Process',
84+
expandCollapseReasoningProcess: 'Expand/Collapse Reasoning Process',
8185
},
8286
setting: {
8387
overRide: 'Enable Override',

src/locales/ko-KR.ts

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export default {
7878
deleteKeyConfirm: 'Are you sure to delete this key?',
7979
disable2FA: 'Disable 2FA',
8080
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
81+
thinking: '생각 중',
82+
reasoningProcess: '추론 과정',
83+
noReasoningProcess: '추론 과정 없음',
84+
expandCollapseReasoningProcess: '추론 과정 펼치기/접기',
8185
},
8286
setting: {
8387
overRide: '덮어쓰기 활성화',

src/locales/zh-CN.ts

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export default {
7878
deleteKeyConfirm: '你确定要删除这个 key 吗?',
7979
disable2FA: '禁用 2FA',
8080
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
81+
thinking: '思考中',
82+
reasoningProcess: '推理过程',
83+
noReasoningProcess: '无推理过程',
84+
expandCollapseReasoningProcess: '展开/折叠推理过程',
8185
},
8286
setting: {
8387
overRide: '开启覆写',

src/locales/zh-TW.ts

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export default {
7878
deleteKeyConfirm: '你確定要刪除這個 key 嗎?',
7979
disable2FA: '禁用 2FA',
8080
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
81+
thinking: '思考中',
82+
reasoningProcess: '推理過程',
83+
noReasoningProcess: '無推理過程',
84+
expandCollapseReasoningProcess: '展開/收合推理過程',
8185
},
8286
setting: {
8387
overRide: '開啟覆寫',

src/typings/chat.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ declare namespace Chat {
33
uuid?: number
44
dateTime: string
55
reasoning?: string
6+
finish_reason?: string
67
text: string
78
images?: string[]
89
inversion?: boolean
+151-11
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,178 @@
11
<script lang="ts" setup>
2-
import { computed, ref } from 'vue'
2+
import { computed, getCurrentInstance, ref } from 'vue'
33
import { useBasicLayout } from '@/hooks/useBasicLayout'
4+
import { t } from '@/locales'
45
56
interface Props {
67
reasoning?: string
8+
reasonEnd?: boolean
79
loading?: boolean
810
}
911
10-
defineProps<Props>()
12+
const props = defineProps<Props>()
1113
1214
const { isMobile } = useBasicLayout()
15+
const instance = getCurrentInstance()
16+
const uid = instance?.uid || Date.now() + Math.random().toString(36).substring(2)
1317
1418
const textRef = ref<HTMLElement>()
19+
const isCollapsed = ref(false)
1520
16-
const wrapClass = computed(() => {
21+
const reasoningBtnTitle = computed(() => {
22+
return t('chat.expandCollapseReasoningProcess')
23+
})
24+
25+
const shouldShowThinkingIndicator = computed(() => {
26+
return props.loading && !props.reasonEnd
27+
})
28+
29+
const hasReasoningText = computed(() => {
30+
return props.reasoning && props.reasoning.trim() !== ''
31+
})
32+
33+
const headerComputedClass = computed(() => {
34+
return [
35+
'flex items-center justify-between',
36+
'p-2 pl-3 w-48',
37+
'bg-gray-200 dark:bg-slate-700',
38+
'text-xs select-none font-medium',
39+
'transition-all duration-200 ease-in-out',
40+
hasReasoningText.value ? 'cursor-pointer hover:bg-gray-300 dark:hover:bg-slate-600' : 'cursor-default',
41+
(isCollapsed.value || !hasReasoningText.value) ? 'rounded-md' : 'rounded-t-md',
42+
isMobile.value ? 'max-w-full' : 'max-w-md',
43+
'shadow-sm',
44+
]
45+
})
46+
47+
const contentWrapperComputedClass = computed(() => {
48+
return [
49+
'overflow-hidden',
50+
'transition-all duration-300 ease-in-out',
51+
(isCollapsed.value || !hasReasoningText.value) ? 'max-h-0 opacity-0' : 'max-h-[500px] opacity-100',
52+
]
53+
})
54+
55+
const actualContentComputedClass = computed(() => {
1756
return [
18-
'text-wrap',
19-
'min-w-[20px]',
20-
'rounded-md',
21-
isMobile.value ? 'p-2' : 'px-3 py-2',
57+
'p-3',
58+
'bg-gray-50 dark:bg-slate-800',
59+
'rounded-b-md shadow-sm',
60+
'text-xs leading-relaxed break-words',
61+
'prose prose-sm dark:prose-invert max-w-none',
2262
]
2363
})
64+
65+
function toggleCollapse() {
66+
if (hasReasoningText.value)
67+
isCollapsed.value = !isCollapsed.value
68+
}
2469
</script>
2570

2671
<template>
27-
<div class="text-black" :class="wrapClass">
28-
<div ref="textRef" class="leading-relaxed break-words">
29-
<div class="flex items-end">
30-
<div class="w-full dark:text-gray-50 text-xs" v-text="reasoning" />
72+
<div class="my-2">
73+
<div
74+
:class="headerComputedClass"
75+
:role="hasReasoningText ? 'button' : undefined"
76+
:tabindex="hasReasoningText ? 0 : -1"
77+
:aria-expanded="hasReasoningText ? !isCollapsed : undefined"
78+
:aria-controls="hasReasoningText ? `reasoning-details-${uid}` : undefined"
79+
@click="hasReasoningText ? toggleCollapse() : null"
80+
@keydown.enter="hasReasoningText ? toggleCollapse() : null"
81+
@keydown.space="hasReasoningText ? toggleCollapse() : null"
82+
>
83+
<div class="flex items-center pr-2">
84+
<template v-if="shouldShowThinkingIndicator">
85+
<svg
86+
class="animate-spin mr-2 h-4 w-4 text-blue-500 dark:text-blue-400 shrink-0"
87+
xmlns="http://www.w3.org/2000/svg"
88+
fill="none"
89+
viewBox="0 0 24 24"
90+
>
91+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
92+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
93+
</svg>
94+
<span class="text-gray-700 dark:text-gray-200 truncate">{{ $t('chat.thinking') }}</span>
95+
<span v-if="hasReasoningText" class="mx-1.5 text-gray-400 dark:text-gray-500">|</span>
96+
</template>
97+
<span v-if="hasReasoningText" class="text-gray-800 dark:text-gray-100 truncate">{{ $t('chat.reasoningProcess') }}</span>
98+
<span v-else-if="!shouldShowThinkingIndicator && !hasReasoningText" class="text-gray-500 dark:text-gray-400">({{ $t('chat.noReasoningProcess') }})</span>
99+
</div>
100+
<button
101+
v-if="hasReasoningText"
102+
type="button"
103+
class="ml-auto flex items-center text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 focus:outline-none p-1 shrink-0 rounded-full hover:bg-gray-300 dark:hover:bg-slate-600"
104+
:aria-expanded="!isCollapsed"
105+
:aria-controls="`reasoning-details-${uid}`"
106+
:title="reasoningBtnTitle"
107+
@click.stop="toggleCollapse"
108+
@keydown.enter.stop="toggleCollapse"
109+
@keydown.space.stop="toggleCollapse"
110+
>
111+
<svg
112+
class="w-4 h-4 transform transition-transform duration-200"
113+
:class="{ 'rotate-180': !isCollapsed }"
114+
fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
115+
>
116+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
117+
</svg>
118+
</button>
119+
</div>
120+
121+
<div :class="contentWrapperComputedClass">
122+
<div
123+
v-if="hasReasoningText"
124+
:id="`reasoning-details-${uid}`"
125+
ref="textRef"
126+
:class="actualContentComputedClass"
127+
role="region"
128+
:aria-hidden="isCollapsed"
129+
>
130+
<div class="w-full" v-text="props.reasoning" />
31131
</div>
32132
</div>
33133
</div>
34134
</template>
35135

36136
<style lang="less">
37137
@import url(./style.less);
138+
139+
.prose {
140+
code {
141+
background-color: rgba(209,213,219,0.3);
142+
padding: .2em .4em;
143+
margin: 0;
144+
font-size: 85%;
145+
border-radius: 3px;
146+
}
147+
pre {
148+
background-color: rgba(229,231,235,1);
149+
color: rgba(55,65,81,1);
150+
padding: 0.75rem;
151+
border-radius: 0.25rem;
152+
overflow-x: auto;
153+
code {
154+
background-color: transparent;
155+
padding: 0;
156+
margin: 0;
157+
font-size: inherit;
158+
border-radius: 0;
159+
color: inherit;
160+
}
161+
}
162+
}
163+
.dark .prose {
164+
color: rgba(209,213,219,1);
165+
code {
166+
background-color: rgba(55,65,81,0.5);
167+
color: rgba(229,231,235,1);
168+
}
169+
pre {
170+
background-color: rgba(31,41,55,1);
171+
color: rgba(229,231,235,1);
172+
}
173+
}
174+
175+
.whitespace-pre-wrap {
176+
white-space: normal;
177+
}
38178
</style>

src/views/chat/components/Message/index.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface Props {
2323
dateTime?: string
2424
model?: string
2525
reasoning?: string
26+
finishReason?: string
2627
text?: string
2728
images?: string[]
2829
isRecord?: boolean
@@ -225,7 +226,7 @@ function isEventTargetValid(event: any) {
225226
</template>
226227
</NSpace>
227228
</p>
228-
<Reasoning v-if="reasoning" :reasoning="reasoning" :loading="loading" />
229+
<Reasoning v-if="reasoning" :reasoning="reasoning" :reason-end="text ? text.length > 0 : false" :loading="loading" />
229230
<div
230231
class="flex items-end gap-1 mt-2"
231232
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"

src/views/chat/index.vue

+2
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ async function onRegenerate(index: number) {
320320
{
321321
dateTime: new Date().toLocaleString(),
322322
reasoning: data?.reasoning,
323+
finish_reason: data?.finish_reason,
323324
text: lastText + (data.text ?? ''),
324325
inversion: false,
325326
responseCount,
@@ -687,6 +688,7 @@ onUnmounted(() => {
687688
:current-nav-index="currentNavIndexRef"
688689
:date-time="item.dateTime"
689690
:reasoning="item?.reasoning"
691+
:finish-reason="item?.finish_reason"
690692
:text="item.text"
691693
:images="item.images"
692694
:inversion="item.inversion"

0 commit comments

Comments
 (0)