Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
<template>
<div class="group-tool-row">
<!-- 工具行:名称 + 状态,点击展开/收起参数与结果 -->
<button type="button" class="row-header" @click="isExpanded = !isExpanded">
<span class="row-name">{{ toolItem.name || 'Tool' }}</span>
<span v-if="toolItem.status" class="row-status" :class="statusClass">{{ statusText }}</span>
<svg class="row-chevron" :class="{ open: isExpanded }" viewBox="0 0 16 16" fill="none">
<path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>

<div v-show="isExpanded" class="row-body">
<!-- 请求参数 -->
<div v-if="toolItem.arguments" class="row-section">
<div class="row-section-title">参数</div>
<pre class="row-code">{{ formattedArguments }}</pre>
</div>
<!-- 返回结果 -->
<div v-if="toolItem.content" class="row-section">
<div class="row-section-title">结果</div>
<pre class="row-code">{{ formattedResult }}</pre>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import type { ChatContent, Dict } from '@/api/types'

/** 分组工具卡表格中的一行,字段与 TOOL 内容项一致(name / arguments / content / status)。 */
const props = defineProps<{ toolItem: any }>()
defineOptions({ name: 'ConversationGroupToolItem' })

const props = defineProps<{ toolItem: ChatContent }>()

// 分组内默认工具独立展开,初始收起。
const isExpanded = ref(false)
const loading = computed(() => props.toolItem.status === 'RUNNING')
const toolTitle = computed(() => props.toolItem.name || 'Tool')

const STATUS_TEXT: Record<string, string> = {
const STATUS_TEXT: Dict<string> = {
RUNNING: '执行中',
SUCCESS: '成功',
FAIL: '失败',
FAILURE: '失败',
}
const statusText = computed(() => STATUS_TEXT[props.toolItem.status] ?? props.toolItem.status)
const statusText = computed(() => {
const status = props.toolItem.status ?? ''
return STATUS_TEXT[status] ?? status
})

const statusClass = computed(() => {
const status = props.toolItem.status
if (status === 'SUCCESS') return 'success'
if (status === 'FAIL' || status === 'FAILURE') return 'error'
return 'running'
if (status === 'SUCCESS') return 'text-success'
if (status === 'FAIL' || status === 'FAILURE') return 'text-danger'
return 'text-N600'
})

// 参数为 JSON 字符串,尝试格式化展示,失败则原样展示
Expand All @@ -68,94 +50,35 @@ const formattedArguments = computed(() => tryFormatJson(props.toolItem.arguments
const formattedResult = computed(() => tryFormatJson(props.toolItem.content))
</script>

<style scoped lang="scss">
.row-header {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
border: none;
background: none;
cursor: pointer;
font-family: inherit;
font-size: 13px;
color: var(--t1, #303133);
text-align: left;
transition: background 0.15s;
}

.row-header:hover {
background: var(--hv, #f5f7fa);
}

.row-name {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.row-status {
flex-shrink: 0;
font-size: 11px;
padding: 1px 6px;
border-radius: 4px;
background: var(--hv, #f5f7fa);
}

.row-status.success {
color: #67c23a;
}

.row-status.error {
color: #f56c6c;
}

.row-status.running {
color: var(--t3, #909399);
}

.row-chevron {
width: 12px;
height: 12px;
flex-shrink: 0;
color: var(--t3, #909399);
transition: transform 0.15s;
}

.row-chevron.open {
transform: rotate(90deg);
}

.row-body {
padding: 0 12px 10px;
}

.row-section + .row-section {
margin-top: 8px;
}

.row-section-title {
font-size: 11px;
font-weight: 500;
color: var(--t3, #909399);
margin-bottom: 4px;
}

.row-code {
margin: 0;
padding: 8px 10px;
border-radius: 6px;
background: var(--hv, #f5f7fa);
border: 1px solid var(--bd, #ebeef5);
font-size: 12px;
line-height: 1.6;
color: var(--t1, #303133);
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
white-space: pre-wrap;
word-break: break-word;
max-height: 320px;
overflow: auto;
}
</style>
<template>
<!-- 展开或收起分组内工具参数 -->
<MkCollapse class="w-full" v-model:expanded="isExpanded" indicator-position="after" trigger-class="group text-N500! mb-2 w-fit hover:text-N900!">
<template #indicator="{ expanded }">
<MkIcon
name="icon_right_outlined"
:size="12"
class="transition-transform text-N500! group-hover:text-N900!"
:class="{ 'rotate-90': expanded }"
/>
</template>
<template #label>
<span v-if="loading" class="mk-conversation-loading" />
<MkIcon name="icon_busy_outlined" />
<span class="min-w-0 max-w-[300px] truncate" :title="toolTitle">{{ toolTitle }}</span>
<span v-if="toolItem.status" class="shrink-0 rounded bg-N100 px-[6px] py-px text-[11px]" :class="statusClass">{{ statusText }}</span>
</template>

<div class="space-y-2">
<!-- 请求参数 -->
<div v-if="toolItem.arguments" class="mk-gray-card-lg py-3!">
<h6 class="mb-2">输入参数</h6>
<div class="whitespace-pre-wrap break-words">{{ formattedArguments }}</div>
</div>
<!-- 返回结果 -->
<div v-if="toolItem.content" class="mk-gray-card-lg py-3!">
<h6 class="mb-2">输出参数</h6>
<div class="whitespace-pre-wrap break-words">{{ formattedResult }}</div>
</div>
</div>
</MkCollapse>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const isExpanded = computed({
</template>
<template #label>
<span v-if="groupStatus === 'running'" class="mk-conversation-loading" />
<MkIcon name="icon_busy_outlined" />
<span>调用了 {{ toolItems.length }} 个工具</span>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function getAttachmentDescription(file: QuestionAttachment) {
<template>
<div class="mk-conversation-question-content space-y-3">
<!-- 查看已发送附件,超出当前宽度时自动换行。 -->
<div v-if="attachments.length" class="flex max-w-full gap-2">
<div v-if="attachments.length" class="flex-wrap max-w-full justify-end gap-2">
<template v-for="file in attachments" :key="file.key">
<div :class="file.field === 'image_list' ? 'shrink-0' : 'w-60 max-w-full'">
<!-- 查看图片 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const showThink = ref(true)
<!-- 展开或收起思考过程 -->
<div class="flex-between cursor-pointer" @click="showThink = !showThink">
<div class="flex-align-center" style="line-height: 20px">
<MkIcon name="icon_two-ellipses_outlined" />
<!-- <MkIcon name="icon_two-ellipses_outlined" /> -->
<span class="ml-1">思考过程</span>
</div>
<el-icon class="transition-transform" :class="{ 'rotate-180': showThink }">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { CaretBottom } from '@element-plus/icons-vue'
import Content from './content/index.vue'
import { type ToolCalls } from './index'

Expand Down Expand Up @@ -28,7 +28,7 @@ const showContent = ref(false)
<span class="ml-1">{{ toolCallsContent.title || '-' }}</span>
</div>
<el-icon class="transition-transform" :class="{ 'rotate-180': showContent }">
<ArrowDown />
<CaretBottom />
</el-icon>
</div>
<el-collapse-transition>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/router/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const router = createRouter({
history: createWebHistory(ADMIN_BASE_PATH),
routes: [
{ path: '/', redirect: { name: 'workspace-home', params: { workspaceId: 'default' } } },
// TODO(demo): 执行详情样式预览,验收后删除
// (demo): 执行详情样式预览,不涉及权限,不加入权限路由
{ path: '/details-demo', name: 'details-demo', component: () => import('@/views/details-demo/index.vue'), meta: { title: '执行详情 Demo' } },
...loginRoutes,
...workflowRoutes,
Expand Down
68 changes: 53 additions & 15 deletions ui/src/workflow-canvas/nodes/application-node/details/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,29 @@ defineProps<{
<h6 class="mb-2">输入参数</h6>
<div class="space-y-2">
<p>
<span class="mr-1 text-N600">问题:</span>
<span class="text-N600">问题:</span>
{{ data.question || '-' }}
</p>

<p v-for="(f, i) in data.global_fields" :key="i">
<span class="mr-1 text-N600">{{ f.label }}:</span>
<span class="text-N600">{{ f.label }}:</span>
{{ f.value }}
</p>

<div v-if="data.document_list?.length > 0">
<p class="mb-1 text-N600">文档:</p>
<div class="flex flex-wrap gap-2">
<div v-for="(f, i) in data.document_list" :key="i" class="flex-align-center rounded-md bg-white px-2 py-1">
<img :src="getFileIconUrl(f?.name)" alt="" width="24" />
<span class="ml-1 max-w-[200px] truncate" :title="f?.name">{{ f?.name }}</span>
</div>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<template v-for="(f, i) in data.document_list" :key="i">
<!-- 在新标签页打开或下载附件 -->
<a :href="f.url || undefined" target="_blank" rel="noopener noreferrer" :download="f.name" class="block min-w-0">
<el-card shadow="never" class="small cursor-pointer">
<div class="flex-align-center gap-2">
<img :src="getFileIconUrl(f?.name)" alt="" width="24" />
<span class="min-w-0 truncate" :title="f?.name">{{ f?.name }}</span>
</div>
</el-card>
</a>
</template>
</div>
</div>

Expand All @@ -57,22 +64,53 @@ defineProps<{
</div>

<div v-if="data.audio_list?.length > 0">
<p class="mb-1 text-N600">音频文件:</p>
<audio v-for="(f, i) in data.audio_list" :key="i" :src="f.url" controls class="rounded-md" style="width: 300px; height: 43px" />
<p class="mb-1 text-N600">音频:</p>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<template v-for="(file, index) in data.audio_list" :key="index">
<!-- 播放音频 -->
<MkAudio :src="file.url" :name="file.name">
<el-card shadow="never" class="small cursor-pointer">
<div class="flex-align-center gap-2">
<img :src="getFileIconUrl(file?.name)" alt="" width="24" />
<span class="min-w-0 truncate" :title="file?.name">{{ file?.name }}</span>
</div>
</el-card>
</MkAudio>
</template>
</div>
</div>

<div v-if="data.video_list?.length > 0">
<p class="mb-1 text-N600">视频:</p>
<video v-for="(f, i) in data.video_list" :key="i" :src="f.url" controls autoplay class="block rounded-md" style="width: 170px" />
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<template v-for="(file, index) in data.video_list" :key="index">
<!-- 播放视频 -->
<MkVideo :src="file.url" :name="file.name">
<el-card shadow="never" class="small cursor-pointer">
<div class="flex-align-center gap-2">
<img :src="getFileIconUrl(file?.name)" alt="" width="24" />
<span class="min-w-0 truncate" :title="file?.name">{{ file?.name }}</span>
</div>
</el-card>
</MkVideo>
</template>
</div>
</div>

<div v-if="data.other_list?.length > 0">
<p class="mb-1 text-N600">其他:</p>
<div class="flex flex-wrap gap-2">
<div v-for="(f, i) in data.other_list" :key="i" class="flex-align-center rounded-md bg-white px-2 py-1">
<img :src="getFileIconUrl(f?.name)" alt="" width="24" />
<span class="ml-1 max-w-[200px] truncate" :title="f?.name">{{ f?.name }}</span>
</div>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<template v-for="(f, i) in data.other_list" :key="i">
<!-- 在新标签页打开或下载附件 -->
<a :href="f.url || undefined" target="_blank" rel="noopener noreferrer" :download="f.name" class="block min-w-0">
<el-card shadow="never" class="small cursor-pointer">
<div class="flex-align-center gap-2">
<img :src="getFileIconUrl(f?.name)" alt="" width="24" />
<span class="min-w-0 truncate" :title="f?.name">{{ f?.name }}</span>
</div>
</el-card>
</a>
</template>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,26 @@ const fileList = computed<SourceFile[]>(() => (props.data?.file_list as SourceFi

<div class="mk-gray-card-sm">
<h6 class="mb-2">文件列表</h6>
<div class="space-y-2">
<template v-if="fileList.length > 0">
<div
v-for="(file, index) in fileList"
:key="file.file_id || index"
class="mb-2 flex-align-center gap-1 overflow-hidden rounded-md bg-white p-2 last:mb-0"
<div v-if="fileList.length > 0" class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<template v-for="(file, index) in fileList" :key="file.file_id || index">
<!-- 在新标签页打开或下载文件 -->
<a
:href="file.file_id ? getFileUrl(file.file_id) : undefined"
target="_blank"
rel="noopener noreferrer"
:download="file.name"
class="block min-w-0"
>
<img :src="getFileIconUrl(file.name || '')" alt="" width="20" />
<a v-if="file.file_id" :href="getFileUrl(file.file_id)" target="_blank" class="truncate" :title="file.name">
{{ file.name }}
</a>
<span v-else class="truncate" :title="file.name">{{ file.name || '-' }}</span>
</div>
<el-card shadow="never" class="small" :class="{ 'cursor-pointer': file.file_id }">
<div class="flex-align-center gap-2">
<img :src="getFileIconUrl(file.name || '')" alt="" width="24" />
<span class="min-w-0 truncate" :title="file.name">{{ file.name || '-' }}</span>
</div>
</el-card>
</a>
</template>
<template v-else>-</template>
</div>
<template v-else>-</template>
</div>
</DetailContainer>
</template>
Loading
Loading