feat(ai): unify expandable process details

This commit is contained in:
digua committed 2026-08-19 18:49:20 +08:00
1 parent 236dd9bbc0
commit c44180ca11
4 files changed
+58 -33

No files matched your search

+13 -24
View File
@@ -9,6 +9,7 @@ import ErrorBlock from './ErrorBlock.vue'
import ChartBlockRenderer from './ChartBlockRenderer.vue'
import EvidenceBlock from './EvidenceBlock.vue'
import ProcessDisclosure from './ProcessDisclosure.vue'
import ProcessDisclosureIcon from './ProcessDisclosureIcon.vue'
import { useToast } from '@/composables/useToast'
import { stripChartImagePlaceholders } from '@/services/ai/chartMarkdownPlaceholders'
import { shouldHideRecoverableChartError } from '@/stores/aiChatChartBlocks'
@@ -939,12 +940,12 @@ async function handleCopyMarkdown() {
<!-- 思考块(默认折叠) -->
<ProcessDisclosure
v-else-if="block.type === 'think'"
class="group group/think w-full text-sm text-gray-500 dark:text-gray-400"
class="w-full text-sm text-gray-500 dark:text-gray-400"
>
<template #summary="{ open, toggle }">
<button
type="button"
class="ai-live-row flex w-full min-w-0 cursor-pointer select-none items-center text-left transition-colors hover:text-gray-700 dark:hover:text-gray-300"
class="ai-live-row group/process-toggle flex w-full min-w-0 cursor-pointer select-none items-center text-left transition-colors hover:text-gray-700 dark:hover:text-gray-300"
:class="[
isCompletedProcessSummary(segment) ? 'h-7 text-sm leading-7' : 'h-6 text-sm leading-6',
]"
@@ -952,18 +953,7 @@ async function handleCopyMarkdown() {
:aria-expanded="open"
@click="toggle"
>
<span class="relative mr-1.5 flex h-4 w-4 shrink-0 items-center justify-center">
<UIcon
v-if="!open"
name="i-heroicons-light-bulb"
class="absolute h-3.5 w-3.5 text-gray-400 transition-opacity duration-100 group-hover/think:opacity-0 dark:text-gray-500"
/>
<UIcon
name="i-heroicons-chevron-down"
class="absolute h-3.5 w-3.5 text-gray-500 transition-opacity duration-100 dark:text-gray-400"
:class="open ? 'opacity-100' : 'opacity-0 group-hover/think:opacity-100'"
/>
</span>
<ProcessDisclosureIcon icon="i-heroicons-light-bulb" :open="open" class="mr-1.5" />
<span class="shrink-0 text-gray-600 dark:text-gray-300">
{{ getThinkLabel(block.tag) }}
</span>
@@ -1058,15 +1048,12 @@ async function handleCopyMarkdown() {
<template #summary="{ open, toggle }">
<button
type="button"
class="flex w-full cursor-pointer select-none items-center gap-2 rounded-md text-left text-gray-500 transition-colors hover:bg-gray-50/80 hover:text-gray-700 dark:hover:bg-gray-800/30 dark:hover:text-gray-300"
class="group/process-toggle flex w-full cursor-pointer select-none items-center gap-2 rounded-md text-left text-gray-500 transition-colors hover:bg-gray-50/80 hover:text-gray-700 dark:hover:bg-gray-800/30 dark:hover:text-gray-300"
:class="[isCompletedProcessSummary(segment) ? 'h-7 text-sm leading-7' : 'py-1 text-xs']"
:aria-expanded="open"
@click="toggle"
>
<UIcon
name="i-heroicons-clipboard-document-list"
class="h-3.5 w-3.5 shrink-0 text-gray-400"
/>
<ProcessDisclosureIcon icon="i-heroicons-clipboard-document-list" :open="open" />
<span class="min-w-0 truncate text-gray-600 dark:text-gray-300">
{{ t('ai.chat.message.plan.label') }} · {{ block.plan.title }}
</span>
@@ -1152,7 +1139,7 @@ async function handleCopyMarkdown() {
<template #summary="{ open, toggle }">
<button
type="button"
class="flex w-full cursor-pointer select-none items-center gap-2 rounded-md text-left text-gray-500 transition-colors hover:bg-gray-50/80 dark:text-gray-400 dark:hover:bg-gray-800/30"
class="group/process-toggle flex w-full cursor-pointer select-none items-center gap-2 rounded-md text-left text-gray-500 transition-colors hover:bg-gray-50/80 dark:text-gray-400 dark:hover:bg-gray-800/30"
:class="[
isCompletedProcessSummary(segment) ? 'h-7 text-sm leading-7' : 'py-1 text-xs',
block.tool.status === 'error' ? 'text-amber-700 dark:text-amber-400' : '',
@@ -1160,14 +1147,16 @@ async function handleCopyMarkdown() {
:aria-expanded="open"
@click="toggle"
>
<UIcon
:name="
<ProcessDisclosureIcon
:icon="
block.tool.status === 'error'
? 'i-heroicons-exclamation-circle'
: 'i-heroicons-wrench-screwdriver'
"
class="h-3.5 w-3.5 shrink-0"
:class="[block.tool.status === 'error' ? 'text-amber-500' : 'text-gray-400']"
:open="open"
:icon-class="
block.tool.status === 'error' ? 'text-amber-500' : 'text-gray-400 dark:text-gray-500'
"
/>
<span
class="shrink-0"
+5 -8
View File
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
import type { ChatEvidencePayload, EvidenceStatus } from '@openchatlab/core'
import { useLayoutStore } from '@/stores/layout'
import ProcessDisclosure from './ProcessDisclosure.vue'
import ProcessDisclosureIcon from './ProcessDisclosureIcon.vue'
const props = defineProps<{
evidence: ChatEvidencePayload
@@ -47,15 +48,15 @@ function viewSource(messageId: number): void {
</script>
<template>
<ProcessDisclosure class="w-full text-[13px]">
<ProcessDisclosure lazy class="w-full text-[13px]">
<template #summary="{ open, toggle }">
<button
type="button"
class="flex h-7 w-full items-center gap-2 rounded-md text-left text-sm leading-7 text-gray-500 transition-colors hover:bg-gray-50/80 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800/30 dark:hover:text-gray-300"
class="group/process-toggle flex h-7 w-full items-center gap-2 rounded-md text-left text-sm leading-7 text-gray-500 transition-colors hover:bg-gray-50/80 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800/30 dark:hover:text-gray-300"
:aria-expanded="open"
@click="toggle"
>
<UIcon name="i-heroicons-document-magnifying-glass" class="h-3.5 w-3.5 shrink-0 text-gray-400" />
<ProcessDisclosureIcon icon="i-heroicons-document-magnifying-glass" :open="open" />
<span class="shrink-0 text-gray-600 dark:text-gray-300">{{ t('ai.chat.evidence.title') }}</span>
<div class="ml-auto flex min-w-0 items-center gap-2">
@@ -70,10 +71,6 @@ function viewSource(messageId: number): void {
<span v-if="statusCounts.length === 0" class="truncate text-[11px] text-gray-400 dark:text-gray-500">
{{ t('ai.chat.evidence.empty') }}
</span>
<UIcon
:name="open ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'"
class="h-3.5 w-3.5 shrink-0 text-gray-400 dark:text-gray-500"
/>
</div>
</button>
</template>
@@ -158,7 +155,7 @@ function viewSource(messageId: number): void {
<style scoped>
.evidence-details {
max-height: min(18rem, 40vh);
max-height: min(24rem, 50vh);
overflow: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
@@ -1,9 +1,20 @@
<script setup lang="ts">
import { ref } from 'vue'
const props = withDefaults(
defineProps<{
lazy?: boolean
}>(),
{
lazy: false,
}
)
const open = ref(false)
const hasOpened = ref(false)
function toggle() {
if (!open.value) hasOpened.value = true
open.value = !open.value
}
</script>
@@ -13,7 +24,7 @@ function toggle() {
<slot name="summary" :open="open" :toggle="toggle" />
<div class="ai-process-fold" :data-open="open || undefined" :aria-hidden="!open" :inert="open ? undefined : true">
<div class="ai-process-fold-inner">
<slot />
<slot v-if="!props.lazy || hasOpened" />
</div>
</div>
</div>
@@ -0,0 +1,28 @@
<script setup lang="ts">
withDefaults(
defineProps<{
icon: string
open: boolean
iconClass?: string
}>(),
{
iconClass: 'text-gray-400 dark:text-gray-500',
}
)
</script>
<template>
<span class="relative flex h-4 w-4 shrink-0 items-center justify-center" aria-hidden="true">
<UIcon
v-if="!open"
:name="icon"
class="absolute h-3.5 w-3.5 transition-opacity duration-100 group-hover/process-toggle:opacity-0"
:class="iconClass"
/>
<UIcon
name="i-heroicons-chevron-down"
class="absolute h-3.5 w-3.5 text-gray-500 transition-opacity duration-100 dark:text-gray-400"
:class="open ? 'opacity-100' : 'opacity-0 group-hover/process-toggle:opacity-100'"
/>
</span>
</template>