Fix streaming response UI issues in OutputPanel

- Fix loading indicator visibility during streaming
- Fix copy button not showing due to hideTitle attribute
- Prevent token count reset after stream completion
- Add resultTitle prop for custom panel titles
This commit is contained in:
Joseph Hau committed 2025-03-13 19:18:38 +08:00
1 parent 3b8b59941c
commit fd44067c86
4 files changed
+47 -38

No files matched your search

+21 -9
View File
@@ -1,14 +1,17 @@
<!-- 输出面板组件 -->
<template>
<div class="flex flex-col h-full">
<div v-if="!hideTitle" class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold theme-text">{{ t('output.title') }}</h3>
<div class="flex items-center justify-between mb-3">
<h3 v-if="resultTitle" class="text-lg font-semibold theme-text">{{ resultTitle }}</h3>
<div v-else-if="!hideTitle" class="text-lg font-semibold theme-text">{{ t('output.title') }}</div>
<div v-else></div>
<button
v-if="result"
v-if="contentTokens.length > 0 && !isStreaming"
@click="copySelectedText"
class="px-3 py-1.5 theme-button-secondary flex items-center space-x-2"
>
<span>{{ t('common.copy') }}</span>
<span>{{ t('prompt.copy') }}</span>
</button>
</div>
@@ -32,11 +35,11 @@
</div>
<div
v-if="loading && !isStreaming"
v-if="(loading || (isStreaming && contentTokens.length === 0))"
class="absolute top-2 right-2 theme-loading px-3 py-1.5 rounded-lg flex items-center space-x-2"
>
<span class="animate-spin">⏳</span>
<span>{{ t('common.processing') }}</span>
<span>{{ t('output.processing') }}</span>
</div>
</div>
</div>
@@ -81,6 +84,7 @@ interface Props {
error?: string;
result?: string;
hideTitle?: boolean;
resultTitle?: string;
}
const props = defineProps<Props>()
@@ -92,7 +96,7 @@ const emit = defineEmits<{
// 监听 result 变化
watch(() => props.result, (newVal) => {
if (!isStreaming.value && newVal) {
if (!selfUpdate && !isStreaming.value && newVal) {
contentTokens.value = [newVal]
}
})
@@ -109,7 +113,7 @@ interface StreamHandlers {
onComplete: () => void;
onError: (error: Error) => void;
}
let selfUpdate = false;
// 处理流式响应
const handleStream = (): StreamHandlers => {
console.log('Starting stream handling, container exists:', !!resultContainer.value);
@@ -130,8 +134,16 @@ const handleStream = (): StreamHandlers => {
},
onComplete: () => {
console.log('Stream completed, total tokens:', contentTokens.value.length);
const finalContent = contentTokens.value.join(''); // 保存一份最终内容
isStreaming.value = false;
emit('update:result', displayContent.value);
// 设置标记,表示这是自身触发的更新
selfUpdate = true;
emit('update:result', finalContent);
// 给标记一个恢复的延时
setTimeout(() => {
selfUpdate = false;
}, 100);
},
onError: (error: Error) => {
console.error('Stream error:', error);
+1 -1
View File
@@ -34,7 +34,7 @@
@click="copyPrompt"
class="px-3 py-1.5 theme-button-secondary flex items-center space-x-2"
>
<span>{{ t('common.copy') }}</span>
<span>{{ t('prompt.copy') }}</span>
</button>
</div>
</div>
+23 -26
View File
@@ -42,39 +42,35 @@
<div class="flex-1 min-h-0 overflow-y-auto mt-5">
<div class="relative h-full">
<!-- Original Prompt Test Result -->
<div
v-show="isCompareMode"
<div
v-show="isCompareMode"
class="absolute inset-0 flex flex-col md:w-[calc(50%-6px)] md:mr-3"
>
<h3 class="text-lg font-semibold theme-text mb-2">{{ t('test.originalResult') }}</h3>
<OutputPanelUI
ref="originalOutputPanelRef"
:loading="isTestingOriginal"
:error="originalTestError"
:result="originalTestResult"
v-model:result="originalTestResult"
class="flex-1"
hide-title
:resultTitle="t('test.originalResult')"
/>
</div>
<!-- Optimized Prompt Test Result -->
<div
class="absolute inset-0 flex flex-col"
<div
class="absolute inset-0 flex flex-col"
:class="{
'md:w-[calc(50%-6px)] md:left-[calc(50%+6px)] transition-[width,left] duration-300': isCompareMode,
'w-full': !isCompareMode
'md:w-[calc(50%-6px)] md:left-[calc(50%+6px)] transition-[width,left] duration-300': isCompareMode,
'w-full': !isCompareMode
}"
>
<h3 class="text-lg font-semibold theme-text mb-2 truncate">
{{ isCompareMode ? t('test.optimizedResult') : t('test.testResult') }}
</h3>
<OutputPanelUI
ref="optimizedOutputPanelRef"
:loading="isTestingOptimized"
:error="optimizedTestError"
:result="optimizedTestResult"
v-model:result="optimizedTestResult"
class="flex-1"
hide-title
:resultTitle="isCompareMode ? t('test.optimizedResult') : t('test.testResult')"
/>
</div>
</div>
@@ -162,18 +158,18 @@ const ensureString = (value) => {
// Test original prompt
const testOriginalPrompt = async () => {
if (!props.originalPrompt || !testContent.value) return
isTestingOriginal.value = true
originalTestError.value = ''
try {
if (originalOutputPanelRef.value) {
const streamHandler = originalOutputPanelRef.value.handleStream()
// Ensure prompt is a string
const promptStr = ensureString(props.originalPrompt)
console.log('[TestPanel] Original prompt type:', typeof promptStr, promptStr.substring(0, 30))
await props.promptService.testPromptStream(
promptStr,
testContent.value,
@@ -197,20 +193,20 @@ const testOriginalPrompt = async () => {
// Test optimized prompt
const testOptimizedPrompt = async () => {
if (!props.optimizedPrompt || !testContent.value) return
isTestingOptimized.value = true
optimizedTestError.value = ''
try {
const outputPanel = optimizedOutputPanelRef.value
if (outputPanel) {
const streamHandler = outputPanel.handleStream()
// Ensure prompt is a string
const promptStr = ensureString(props.optimizedPrompt)
console.log('[TestPanel] Optimized prompt type:', typeof promptStr, promptStr.substring(0, 30))
await props.promptService.testPromptStream(
promptStr,
testContent.value,
@@ -239,7 +235,7 @@ const handleTest = async () => {
optimizedTestError.value = errorMsg
return
}
if (isCompareMode.value) {
// Compare test mode: test both original and optimized prompts
try {
@@ -264,6 +260,7 @@ const handleTest = async () => {
// Component mounted, if there is a default model, select it automatically
onMounted(() => {
console.log("hideTitle value:", originalOutputPanelRef.value?.hideTitle);
if (props.modelValue) {
selectedTestModel.value = props.modelValue
}
@@ -288,4 +285,4 @@ defineExpose({
border-radius: 0.25rem;
cursor: pointer;
}
</style>
</style>
+2 -2
View File
@@ -622,7 +622,7 @@ html * {
/* ===== 状态组件 ===== */
/* 主题加载中 */
.theme-loading {
@apply text-teal-700;
@apply text-teal-300;
}
/* 主题错误 */
.theme-error {
@@ -1100,7 +1100,7 @@ html * {
/* ===== 状态组件 ===== */
/* 主题加载中 */
.theme-loading {
@apply text-purple-400 animate-pulse;
@apply text-purple-400;
}
/* 主题错误 */