fix(chatui): reasoning summary

This commit is contained in:
Soulter
2026-05-30 20:06:20 +08:00
parent 0da17485bd
commit b0bb5c7477
6 changed files with 79 additions and 10 deletions

View File

@@ -2,7 +2,7 @@
<transition name="slide-left">
<aside v-if="modelValue" class="reasoning-sidebar">
<div class="reasoning-sidebar-header">
<div class="reasoning-sidebar-title">{{ tm("reasoning.thinking") }}</div>
<div class="reasoning-sidebar-title">{{ reasoningTitle }}</div>
<v-btn icon="mdi-close" size="small" variant="text" @click="close" />
</div>
@@ -14,7 +14,7 @@
:is-dark="isDark"
/>
<div v-else class="reasoning-sidebar-empty">
{{ tm("reasoning.thinking") }}
{{ reasoningTitle }}
</div>
</div>
</aside>
@@ -22,11 +22,16 @@
</template>
<script setup lang="ts">
import type { MessagePart } from "@/composables/useMessages";
import { computed } from "vue";
import {
reasoningActivityCounts,
reasoningActivityTitle,
type MessagePart,
} from "@/composables/useMessages";
import { useModuleI18n } from "@/i18n/composables";
import ReasoningTimeline from "@/components/chat/message_list_comps/ReasoningTimeline.vue";
defineProps<{
const props = defineProps<{
modelValue: boolean;
parts: MessagePart[];
reasoning?: string;
@@ -39,6 +44,14 @@ const emit = defineEmits<{
const { tm } = useModuleI18n("features/chat");
const activityCounts = computed(() =>
reasoningActivityCounts(props.parts, props.reasoning || ""),
);
const reasoningTitle = computed(() =>
reasoningActivityTitle(activityCounts.value, tm),
);
function close() {
emit("update:modelValue", false);
}

View File

@@ -7,7 +7,7 @@
@click="handlePrimaryAction"
>
<span class="reasoning-title">
{{ tm("reasoning.thinking") }}
{{ reasoningTitle }}
</span>
<v-icon
size="22"
@@ -40,7 +40,11 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from "vue";
import type { MessagePart } from "@/composables/useMessages";
import {
reasoningActivityCounts,
reasoningActivityTitle,
type MessagePart,
} from "@/composables/useMessages";
import { useModuleI18n } from "@/i18n/composables";
import ReasoningTimeline from "@/components/chat/message_list_comps/ReasoningTimeline.vue";
@@ -75,6 +79,14 @@ const renderParts = computed<MessagePart[]>(() => {
const openInSidebar = computed(() => Boolean(props.openInSidebar));
const activityCounts = computed(() =>
reasoningActivityCounts(renderParts.value, props.reasoning || ""),
);
const reasoningTitle = computed(() =>
reasoningActivityTitle(activityCounts.value, tm),
);
const thinkingText = computed(() =>
renderParts.value
.filter((part) => part.type === "think")
@@ -214,14 +226,11 @@ onBeforeUnmount(() => {
color: rgba(var(--v-theme-on-surface), 0.88);
}
.reasoning-header--trigger {
align-items: flex-start;
}
.reasoning-icon {
color: currentcolor;
transition: transform 0.2s ease;
flex-shrink: 0;
align-self: center;
}
.reasoning-title {

View File

@@ -794,6 +794,44 @@ export function extractReasoningText(
return text || legacyReasoning;
}
export function reasoningActivityCounts(
parts: MessagePart[] | unknown,
legacyReasoning = "",
) {
const normalizedParts = Array.isArray(parts)
? parts
: normalizeMessageParts(parts, legacyReasoning);
let thinkCount = 0;
let toolCount = 0;
for (const part of normalizedParts) {
if (part.type === "think" && String(part.think || "").trim()) {
thinkCount += 1;
}
if (part.type === "tool_call" && Array.isArray(part.tool_calls)) {
toolCount += part.tool_calls.length;
}
}
return { thinkCount, toolCount };
}
export function reasoningActivityTitle(
counts: ReturnType<typeof reasoningActivityCounts>,
tm: (key: string, params?: Record<string, string | number>) => string,
) {
return [
counts.thinkCount > 0
? tm("reasoning.thinkSummary", { count: counts.thinkCount })
: "",
counts.toolCount > 0
? tm("reasoning.toolSummary", { count: counts.toolCount })
: "",
]
.filter(Boolean)
.join(tm("reasoning.summarySeparator")) || tm("reasoning.thinking");
}
export function thinkingParts(content: ChatContent): MessagePart[] {
const firstThinkingBlock = messageBlocks(content).find(
(block) => block.kind === "thinking",

View File

@@ -114,6 +114,9 @@
},
"reasoning": {
"thinking": "Thinking Process",
"summarySeparator": ", ",
"thinkSummary": "Thought {count} times",
"toolSummary": "used {count} tools",
"think": "Thinking",
"toolUsed": "Using Tool"
},

View File

@@ -114,6 +114,9 @@
},
"reasoning": {
"thinking": "Рассуждение",
"summarySeparator": ", ",
"thinkSummary": "Размышлений: {count}",
"toolSummary": "использований инструментов: {count}",
"think": "Размышление",
"toolUsed": "Использование инструмента"
},

View File

@@ -114,6 +114,9 @@
},
"reasoning": {
"thinking": "思考过程",
"summarySeparator": "",
"thinkSummary": "思考了 {count} 次",
"toolSummary": "使用了 {count} 次工具",
"think": "思考",
"toolUsed": "使用工具"
},