Version: 0.9.52.dev.260428
后端: 1. 工具结果结构化切流继续推进:schedule 读工具改为“父包 adapter + 子包 view builder”,`queue_pop_head` / `queue_skip_head` 脱离 legacy wrapper,`analyze_health` / `analyze_rhythm` 补齐 `schedule.analysis_result` 诊断卡片。 2. 非 schedule 工具补齐专属结果协议:`web_search` / `web_fetch`、`upsert_task_class`、`context_tools_add` / `context_tools_remove` 全部接入结构化 `ResultView`,注册表继续去 legacy wrapper,同时保持原始 `ObservationText` 供模型链路复用。 3. 工具展示细节继续收口:参数本地化补齐 `domain` / `packs` / `mode` / `all`,deliver 阶段补发段落分隔,避免 execute 与总结正文黏连。 前端: 4. `ToolCardRenderer` 升级为多协议通用渲染器,补齐 read / analysis / web / taskclass / context 卡片渲染、参数折叠区、未知协议兜底与操作明细展示。 5. `AssistantPanel` 修正 `tool_result` 结果回填与卡片布局宽度问题,并新增结构化卡片 fixture / mock 调试入口,便于整体验收。 仓库: 6. 更新工具结果结构化交接文档,补记第四批切流范围、当前切流点与后续收尾建议。
This commit is contained in:
@@ -738,6 +738,9 @@ function appendToolTraceEvent(
|
||||
matchedPendingEvent.summary = normalizedSummary
|
||||
matchedPendingEvent.detail = normalizedDetail || matchedPendingEvent.detail
|
||||
matchedPendingEvent.toolName = normalizedToolName || matchedPendingEvent.toolName
|
||||
// 同步更新视图模型,确保 tool_result 的 result_view 能回填到已存在的卡片中
|
||||
if (argumentView) matchedPendingEvent.argumentView = argumentView
|
||||
if (resultView) matchedPendingEvent.resultView = resultView
|
||||
return
|
||||
}
|
||||
const eventSeq = nextAssistantTimelineSeq()
|
||||
@@ -3209,7 +3212,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<div v-else class="chat-message__assistant-flow">
|
||||
<TransitionGroup name="inner-fade">
|
||||
<div v-for="block in getDisplayAssistantBlocks(dm)" :key="block.id">
|
||||
<div v-for="block in getDisplayAssistantBlocks(dm)" :key="block.id" class="chat-message__block-wrapper">
|
||||
<ToolCardRenderer
|
||||
v-if="block.type === 'tool' && block.event"
|
||||
:payload="{
|
||||
@@ -3842,7 +3845,7 @@ onBeforeUnmount(() => {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: var(--assistant-history-width) auto minmax(0, 1fr);
|
||||
grid-template-columns: var(--assistant-history-width) 8px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
transition: grid-template-columns 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -3853,7 +3856,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.assistant-body--standalone {
|
||||
grid-template-columns: var(--assistant-history-width) auto minmax(0, 1fr);
|
||||
grid-template-columns: var(--assistant-history-width) 8px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@@ -4176,7 +4179,7 @@ onBeforeUnmount(() => {
|
||||
justify-content: center;
|
||||
cursor: col-resize;
|
||||
width: 8px;
|
||||
margin: 0 -4px;
|
||||
margin: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -4201,6 +4204,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.assistant-chat {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
@@ -4243,6 +4248,13 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.assistant-message-list {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.assistant-chat--empty .assistant-messages {
|
||||
@@ -4494,13 +4506,16 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.assistant-messages {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 24px 28px 18px;
|
||||
overscroll-behavior: contain;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
align-content: start;
|
||||
scrollbar-gutter: stable;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(249, 251, 253, 0.42), rgba(255, 255, 255, 0.9) 28%, rgba(255, 255, 255, 1)),
|
||||
radial-gradient(circle at top center, rgba(129, 171, 255, 0.1), transparent 34%);
|
||||
@@ -4519,6 +4534,14 @@ onBeforeUnmount(() => {
|
||||
border-radius: 12px;
|
||||
color: #92400e;
|
||||
font-size: 13px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-message__reasoning {
|
||||
@@ -4593,12 +4616,19 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.chat-message__assistant-flow {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: min(92%, 860px);
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chat-message__block-wrapper {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-message__assistant-content {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ function getOperationFallbackLabel(op: string) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 简短指标区 -->
|
||||
<div v-if="!expanded && payload.result_view?.collapsed?.metrics" class="tool-card__metrics">
|
||||
<!-- 简短指标区 (优先读取 result_view.collapsed.metrics) -->
|
||||
<div v-if="payload.result_view?.collapsed?.metrics" class="tool-card__metrics">
|
||||
<div v-for="(m, mi) in payload.result_view.collapsed.metrics" :key="mi" class="metric-item">
|
||||
<span class="metric-value">{{ m.value }}</span>
|
||||
<span class="metric-label">{{ m.label }}</span>
|
||||
@@ -95,23 +95,32 @@ function getOperationFallbackLabel(op: string) {
|
||||
<section v-if="expanded" class="tool-card__content">
|
||||
<div class="tool-card__divider"></div>
|
||||
|
||||
<!-- 2.1 参数展示 (优先读取 argument_view) -->
|
||||
<div v-if="payload.argument_view" class="section-block section-arguments">
|
||||
<h4 class="detail-section-title">参数详情</h4>
|
||||
<p v-if="payload.argument_view.collapsed?.summary" class="arg-summary">
|
||||
{{ payload.argument_view.collapsed.summary }}
|
||||
</p>
|
||||
<div v-if="payload.argument_view.expanded?.fields" class="arg-fields">
|
||||
<div v-for="(f, fi) in payload.argument_view.expanded.fields" :key="fi" class="arg-field-item">
|
||||
<span class="arg-label">{{ f.label }}</span>
|
||||
<span class="arg-value">{{ f.display }}</span>
|
||||
<!-- 2.1 参数展示 (如果有 argument_view) -->
|
||||
<div v-if="payload.argument_view" class="section-block view-arguments">
|
||||
<details class="arguments-details">
|
||||
<summary class="arguments-summary">
|
||||
<span class="summary-label">输入参数</span>
|
||||
<span class="summary-preview">{{ payload.argument_view.collapsed?.summary }}</span>
|
||||
</summary>
|
||||
<div class="kv-grid kv-grid--arguments">
|
||||
<div v-for="field in payload.argument_view.expanded?.fields" :key="field.key" class="kv-item">
|
||||
<span class="kv-label">{{ field.label }}</span>
|
||||
<span class="kv-value">{{ field.display || field.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- 2.2 结果渲染: schedule.operation_result -->
|
||||
<div v-if="payload.result_view?.view_type === 'schedule.operation_result'" class="section-block view-operation">
|
||||
<h4 class="detail-section-title">操作结果</h4>
|
||||
<h4 class="detail-section-title">操作变更明细</h4>
|
||||
|
||||
<!-- 影响日期汇总 -->
|
||||
<div v-if="payload.result_view.expanded?.affected_days_label" class="affected-days-box">
|
||||
<span class="affected-label">影响日期:</span>
|
||||
<span class="affected-value">{{ payload.result_view.expanded.affected_days_label }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="payload.result_view.expanded?.changes?.length" class="changes-list">
|
||||
<div v-for="(change, idx) in payload.result_view.expanded.changes" :key="idx" class="change-item">
|
||||
<div class="change-item__header">
|
||||
@@ -119,14 +128,14 @@ function getOperationFallbackLabel(op: string) {
|
||||
<span class="change-item__task-name">{{ change.task_label }}</span>
|
||||
<span v-if="change.status_label" class="change-item__status-tag">{{ change.status_label }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="change-item__path">
|
||||
<div class="slot-box slot-box--before">
|
||||
<span class="slot-tag">之前</span>
|
||||
<div class="slot-text">{{ change.before_label || '未排程' }}</div>
|
||||
</div>
|
||||
<div class="path-arrow">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
<polyline points="12 5 19 12 12 19"></polyline>
|
||||
</svg>
|
||||
@@ -138,7 +147,7 @@ function getOperationFallbackLabel(op: string) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 队列快照 (带标签) -->
|
||||
<div v-if="payload.result_view.expanded?.queue_snapshot" class="queue-snapshot">
|
||||
<h5 class="sub-section-title">{{ payload.result_view.expanded.queue_snapshot.summary_label || '队列变更' }}</h5>
|
||||
@@ -152,7 +161,7 @@ function getOperationFallbackLabel(op: string) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 失败信息 -->
|
||||
<div v-if="payload.result_view.expanded?.failure_reason" class="failure-box">
|
||||
<span class="failure-icon">!</span>
|
||||
@@ -160,7 +169,94 @@ function getOperationFallbackLabel(op: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2.3 结果渲染: legacy_text -->
|
||||
<!-- 2.3 结果渲染: 结构化渲染 (read_result, analysis_result, search_result, fetch_result, write_result, context_result) -->
|
||||
<div v-else-if="[
|
||||
'schedule.read_result',
|
||||
'schedule.analysis_result',
|
||||
'web.search_result',
|
||||
'web.fetch_result',
|
||||
'taskclass.write_result',
|
||||
'tool.context_result'
|
||||
].includes(payload.result_view?.view_type || '')" class="section-block view-read">
|
||||
<!-- 优先展示 sections -->
|
||||
<template v-if="payload.result_view?.expanded?.sections?.length">
|
||||
<div v-for="(section, sidx) in payload.result_view.expanded.sections" :key="sidx" class="read-section">
|
||||
<!-- 1. items 类型: 渲染列表 -->
|
||||
<div v-if="section.type === 'items'" class="read-section__items">
|
||||
<h4 v-if="section.title" class="detail-section-title">{{ section.title }}</h4>
|
||||
<p v-if="section.summary" class="section-summary">{{ section.summary }}</p>
|
||||
<div class="items-list">
|
||||
<div v-for="(item, iidx) in section.items" :key="iidx" class="list-item-card">
|
||||
<div class="item-main">
|
||||
<div class="item-title">{{ item.title }}</div>
|
||||
<div class="item-subtitle" v-if="item.subtitle">{{ item.subtitle }}</div>
|
||||
</div>
|
||||
<div class="item-tags" v-if="item.tags?.length">
|
||||
<span v-for="tag in item.tags" :key="tag" class="item-tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div class="item-details" v-if="item.detail_lines?.length">
|
||||
<p v-for="line in item.detail_lines" :key="line" class="detail-line">{{ line }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. kv 类型: 渲染字段列表 -->
|
||||
<div v-else-if="section.type === 'kv'" class="read-section__kv">
|
||||
<h4 v-if="section.title" class="detail-section-title">{{ section.title }}</h4>
|
||||
<div class="kv-grid">
|
||||
<div v-for="(field, fidx) in section.fields" :key="fidx" class="kv-item">
|
||||
<span class="kv-label">{{ field.label }}</span>
|
||||
<span class="kv-value">{{ field.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. callout 类型: 渲染提示块 -->
|
||||
<div v-else-if="section.type === 'callout'" class="read-section__callout" :class="`tone--${section.tone || 'info'}`">
|
||||
<div class="callout-header">
|
||||
<span class="callout-title">{{ section.title }}</span>
|
||||
</div>
|
||||
<p v-if="section.summary || section.subtitle" class="callout-summary">{{ section.summary || section.subtitle }}</p>
|
||||
<div v-if="section.detail_lines?.length" class="callout-details">
|
||||
<p v-for="line in section.detail_lines" :key="line" class="detail-line">{{ line }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 降级展示: 未知类型 -->
|
||||
<div v-else class="read-section__fallback">
|
||||
<h4 v-if="section.title" class="detail-section-title">{{ section.title }}</h4>
|
||||
<p v-if="section.summary" class="section-summary">{{ section.summary }}</p>
|
||||
<div v-if="section.detail_lines?.length" class="fallback-details">
|
||||
<p v-for="line in section.detail_lines" :key="line" class="detail-line">{{ line }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 如果没有 sections,尝试展示 items -->
|
||||
<template v-else-if="payload.result_view?.expanded?.items?.length">
|
||||
<div class="read-section__items">
|
||||
<h4 class="detail-section-title">结果列表</h4>
|
||||
<div class="items-list">
|
||||
<div v-for="(item, iidx) in payload.result_view.expanded.items" :key="iidx" class="list-item-card">
|
||||
<div class="item-main">
|
||||
<div class="item-title">{{ item.title }}</div>
|
||||
<div class="item-subtitle" v-if="item.subtitle">{{ item.subtitle }}</div>
|
||||
</div>
|
||||
<div class="item-tags" v-if="item.tags?.length">
|
||||
<span v-for="tag in item.tags" :key="tag" class="item-tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div v-if="item.detail_lines?.length" class="item-details">
|
||||
<p v-for="line in item.detail_lines" :key="line" class="detail-line">{{ line }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 2.4 结果渲染: legacy_text -->
|
||||
<div v-else-if="payload.result_view?.view_type === 'legacy_text'" class="section-block view-legacy">
|
||||
<h4 class="detail-section-title">{{ payload.result_view.expanded?.raw_text_label || '输出内容' }}</h4>
|
||||
<div class="raw-text-container">
|
||||
@@ -168,7 +264,21 @@ function getOperationFallbackLabel(op: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2.4 旧协议兜底 -->
|
||||
<!-- 2.5 未知协议展示: 只要有 collapsed 就显示基础信息 -->
|
||||
<div v-else-if="payload.result_view?.collapsed" class="section-block view-unknown">
|
||||
<h4 class="detail-section-title">结果详情 (未知协议)</h4>
|
||||
<div class="unknown-content">
|
||||
<p class="unknown-subtitle">{{ payload.result_view.collapsed.subtitle }}</p>
|
||||
<div v-if="payload.result_view.collapsed.metrics" class="unknown-metrics">
|
||||
<div v-for="(m, mi) in payload.result_view.collapsed.metrics" :key="mi" class="metric-chip">
|
||||
<span class="chip-label">{{ m.label }}:</span>
|
||||
<span class="chip-value">{{ m.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2.6 旧协议兜底 -->
|
||||
<div v-else-if="!payload.result_view" class="section-block view-old-fallback">
|
||||
<h4 class="detail-section-title">工具输出 (兼容模式)</h4>
|
||||
<div class="fallback-summary-box">
|
||||
@@ -197,9 +307,14 @@ function getOperationFallbackLabel(op: string) {
|
||||
border: 1px solid #eef2f6;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow-x: hidden;
|
||||
transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s, background-color 0.3s;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02);
|
||||
margin: 8px 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.tool-card:hover {
|
||||
@@ -227,6 +342,9 @@ function getOperationFallbackLabel(op: string) {
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-card__icon-box {
|
||||
@@ -344,6 +462,10 @@ function getOperationFallbackLabel(op: string) {
|
||||
/* Content */
|
||||
.tool-card__content {
|
||||
padding: 0 16px 20px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tool-card__divider {
|
||||
@@ -365,41 +487,56 @@ function getOperationFallbackLabel(op: string) {
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Arguments */
|
||||
.arg-summary {
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
/* Arguments Section */
|
||||
.view-arguments {
|
||||
margin-bottom: 16px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.arg-fields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
background: #f8fafc;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
.arguments-details {
|
||||
border: 1px solid #f1f5f9;
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arg-field-item {
|
||||
.arguments-summary {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.arg-label {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
.arguments-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.arg-value {
|
||||
.arguments-summary .summary-label {
|
||||
font-size: 12px;
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
background: #fff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.arguments-summary .summary-preview {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.kv-grid--arguments {
|
||||
padding: 8px 12px 12px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* Operation Changes */
|
||||
@@ -434,6 +571,7 @@ function getOperationFallbackLabel(op: string) {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.change-item__status-tag {
|
||||
@@ -455,6 +593,8 @@ function getOperationFallbackLabel(op: string) {
|
||||
flex: 1;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slot-box--before {
|
||||
@@ -480,6 +620,7 @@ function getOperationFallbackLabel(op: string) {
|
||||
.slot-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Queue Snapshot */
|
||||
@@ -518,10 +659,59 @@ function getOperationFallbackLabel(op: string) {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
/* Operation View Styles */
|
||||
.affected-days-box {
|
||||
margin: -4px 0 16px;
|
||||
padding: 10px 14px;
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.affected-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.affected-value {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.path-arrow {
|
||||
color: #cbd5e1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.tool-card--expanded .path-arrow {
|
||||
color: #3b82f6;
|
||||
filter: drop-shadow(0 0 4px rgba(59, 130, 246, 0.2));
|
||||
}
|
||||
|
||||
.queue-arrow {
|
||||
flex: 2;
|
||||
height: 2px;
|
||||
background: #e2e8f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.queue-arrow::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-left: 6px solid #e2e8f0;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
}
|
||||
|
||||
/* Legacy Text */
|
||||
@@ -617,6 +807,221 @@ function getOperationFallbackLabel(op: string) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Read Result Styles */
|
||||
.read-section + .read-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.section-summary {
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
line-height: 1.6;
|
||||
margin: -4px 0 12px;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.list-item-card {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #f1f5f9;
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.list-item-card:hover {
|
||||
background: #f1f5f9;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
.item-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.item-subtitle {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.item-tag {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
padding: 1px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #dbeafe;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.detail-line {
|
||||
font-size: 12px;
|
||||
color: #475569;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* KV Grid */
|
||||
.kv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 1fr));
|
||||
gap: 12px;
|
||||
background: #f8fafc;
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.kv-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kv-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.kv-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Callout */
|
||||
.read-section__callout {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border-left: 4px solid #cbd5e1;
|
||||
background: #f8fafc;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.read-section__callout.tone--info {
|
||||
background: #f0f9ff;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.read-section__callout.tone--info .callout-title { color: #0369a1; }
|
||||
.read-section__callout.tone--info .callout-summary { color: #0c4a6e; }
|
||||
|
||||
.read-section__callout.tone--warning {
|
||||
background: #fffbeb;
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
|
||||
.read-section__callout.tone--warning .callout-title { color: #b45309; }
|
||||
.read-section__callout.tone--warning .callout-summary { color: #78350f; }
|
||||
|
||||
.read-section__callout.tone--danger {
|
||||
background: #fef2f2;
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
.read-section__callout.tone--danger .callout-title { color: #b91c1c; }
|
||||
.read-section__callout.tone--danger .callout-summary { color: #7f1d1d; }
|
||||
|
||||
.callout-header {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.callout-title {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.callout-summary {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.callout-details {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Unknown View Styles */
|
||||
.unknown-subtitle {
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.unknown-metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-chip {
|
||||
background: #f1f5f9;
|
||||
padding: 4px 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.chip-label {
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip-value {
|
||||
color: #0f172a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.tool-expand-enter-active,
|
||||
.tool-expand-leave-active {
|
||||
|
||||
@@ -45,6 +45,16 @@ const router = createRouter({
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/debug/tool-card',
|
||||
name: 'debug-tool-card',
|
||||
component: () => import('@/views/debug/ToolCardFixture.vue'),
|
||||
},
|
||||
{
|
||||
path: '/debug/tool-cards',
|
||||
name: 'debug-tool-cards',
|
||||
component: () => import('@/views/debug/ToolCardMockPage.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
632
frontend/src/views/debug/ToolCardFixture.vue
Normal file
632
frontend/src/views/debug/ToolCardFixture.vue
Normal file
@@ -0,0 +1,632 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import ToolCardRenderer from '@/components/dashboard/ToolCardRenderer.vue'
|
||||
import type { TimelineToolPayload } from '@/api/schedule_agent'
|
||||
|
||||
const analysisPayload: TimelineToolPayload = {
|
||||
"name": "analyze_health",
|
||||
"status": "done",
|
||||
"summary": "综合体检报告",
|
||||
"arguments_preview": "无参数",
|
||||
"result_view": {
|
||||
"view_type": "schedule.analysis_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "综合体检报告",
|
||||
"subtitle": "发现 2 个关键冲突,3 个节奏风险。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "冲突项", "value": "2 个" },
|
||||
{ "label": "风险点", "value": "3 个" },
|
||||
{ "label": "健康分", "value": "72" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "方案 A:优先保证英语作文",
|
||||
"subtitle": "移动 3 个任务,消除所有硬冲突",
|
||||
"tags": ["推荐方案", "低风险"],
|
||||
"detail_lines": ["涉及任务:[91]英语作文、[52]组合逻辑电路分析"],
|
||||
"meta": { "decision_id": "plan_a" }
|
||||
},
|
||||
{
|
||||
"title": "方案 B:最小化变动",
|
||||
"subtitle": "仅移动 [91],保留 1 个软冲突",
|
||||
"tags": ["保守方案"],
|
||||
"detail_lines": ["涉及任务:[91]英语作文"],
|
||||
"meta": { "decision_id": "plan_b" }
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "健康指标详情",
|
||||
"fields": [
|
||||
{ "label": "硬性冲突", "value": "2 处 (时段重叠)" },
|
||||
{ "label": "软性约束", "value": "1 处 (先修课顺序)" },
|
||||
{ "label": "学习节奏", "value": "偏紧 (第 11-13 天)" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "核心建议",
|
||||
"subtitle": "建议在第 11 天前完成英语作文的初稿安排。",
|
||||
"tone": "info",
|
||||
"detail_lines": ["这样可以避开第 13 天的专业课复习高峰。"]
|
||||
}
|
||||
],
|
||||
"raw_text": "体检分析 debug 原文,不要默认主展示"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const movePayload: TimelineToolPayload = {
|
||||
"name": "move",
|
||||
"status": "done",
|
||||
"summary": "移动任务成功",
|
||||
"arguments_preview": "任务:[52]组合逻辑电路分析,目标日期:第11天,目标时段:第7-8节",
|
||||
"result_view": {
|
||||
"view_type": "schedule.operation_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "移动任务成功",
|
||||
"subtitle": "[52]组合逻辑电路分析:从第15天 第7-8节移动到第11天 第7-8节",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "任务数量", "value": "1个" },
|
||||
{ "label": "影响天数", "value": "2天" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"affected_days_label": "第11天、第15天",
|
||||
"changes": [
|
||||
{
|
||||
"task_id": 52,
|
||||
"task_label": "[52]组合逻辑电路分析",
|
||||
"before_label": "第15天 第7-8节",
|
||||
"after_label": "第11天 第7-8节",
|
||||
"status_label": "已预排 -> 已预排",
|
||||
"operation_key": "move"
|
||||
}
|
||||
],
|
||||
"raw_text": "移动完成 debug 原文,不要默认主展示"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const overviewPayload: TimelineToolPayload = {
|
||||
"name": "get_overview",
|
||||
"status": "done",
|
||||
"summary": "当前排程总览",
|
||||
"arguments_preview": "无参数",
|
||||
"result_view": {
|
||||
"view_type": "schedule.read_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "当前排程总览",
|
||||
"subtitle": "15 天窗口,已占用 82/180 节,待安排 6 项。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "已占用", "value": "82 节" },
|
||||
{ "label": "空闲", "value": "98 节" },
|
||||
{ "label": "待安排", "value": "6 项" },
|
||||
{ "label": "课程占位", "value": "14 项" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "第1天(第1周 周一)",
|
||||
"subtitle": "总占用 6/12 节,任务占用 4/12 节",
|
||||
"tags": ["任务 2 项"],
|
||||
"detail_lines": [
|
||||
"[52]组合逻辑电路分析|已预排|第7-8节",
|
||||
"[43]谓词逻辑基础(量词与公式)|已预排|第9-10节"
|
||||
],
|
||||
"meta": { "day": 1 }
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "窗口概况",
|
||||
"fields": [
|
||||
{ "label": "规划天数", "value": "15 天" },
|
||||
{ "label": "总时段", "value": "180 节" },
|
||||
{ "label": "已占用", "value": "82 节" },
|
||||
{ "label": "空闲", "value": "98 节" },
|
||||
{ "label": "课程占位", "value": "14 项" },
|
||||
{ "label": "已预排任务", "value": "22 项" },
|
||||
{ "label": "待安排任务", "value": "6 项" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "每日概况",
|
||||
"items": [
|
||||
{
|
||||
"title": "第1天(第1周 周一)",
|
||||
"subtitle": "总占用 6/12 节,任务占用 4/12 节",
|
||||
"tags": ["任务 2 项"],
|
||||
"detail_lines": [
|
||||
"[52]组合逻辑电路分析|已预排|第7-8节",
|
||||
"[43]谓词逻辑基础(量词与公式)|已预排|第9-10节"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "第2天(第1周 周二)",
|
||||
"subtitle": "总占用 8/12 节,任务占用 6/12 节",
|
||||
"tags": ["任务 3 项"],
|
||||
"detail_lines": [
|
||||
"[36]向量组的线性相关性|已预排|第3-4节",
|
||||
"[68]社会主义改造理论|已预排|第5-6节"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "任务清单",
|
||||
"items": [
|
||||
{
|
||||
"title": "[52]组合逻辑电路分析",
|
||||
"subtitle": "学习|已预排",
|
||||
"tags": ["已预排"],
|
||||
"detail_lines": [
|
||||
"时段:第1天(第1周 周一) 第7-8节",
|
||||
"来源:任务项"
|
||||
],
|
||||
"meta": { "task_id": 52, "status": "suggested" }
|
||||
},
|
||||
{
|
||||
"title": "[91]英语作文",
|
||||
"subtitle": "英语|待安排",
|
||||
"tags": ["待安排"],
|
||||
"detail_lines": [
|
||||
"时段:尚未落位",
|
||||
"来源:任务项"
|
||||
],
|
||||
"meta": { "task_id": 91, "status": "pending" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "任务类约束",
|
||||
"items": [
|
||||
{
|
||||
"title": "英语",
|
||||
"subtitle": "均匀分布",
|
||||
"tags": [],
|
||||
"detail_lines": [
|
||||
"排程策略:均匀分布",
|
||||
"总预算:6 节",
|
||||
"允许嵌入水课:是"
|
||||
],
|
||||
"meta": { "task_class_id": 7, "strategy": "steady" }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"machine_payload": {
|
||||
"total_days": 15,
|
||||
"total_slots": 180,
|
||||
"total_occupied": 82,
|
||||
"task_pending_count": 6
|
||||
},
|
||||
"raw_text": "总览 debug 原文,不要默认主展示"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queuePayload: TimelineToolPayload = {
|
||||
"name": "queue_status",
|
||||
"status": "done",
|
||||
"summary": "队列待处理 3 项",
|
||||
"arguments_preview": "无参数",
|
||||
"result_view": {
|
||||
"view_type": "schedule.read_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "队列待处理 3 项",
|
||||
"subtitle": "当前处理:[52]组合逻辑电路分析,第 2 次尝试。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "待处理", "value": "3 项" },
|
||||
{ "label": "已完成", "value": "1 项" },
|
||||
{ "label": "已跳过", "value": "0 项" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "[52]组合逻辑电路分析",
|
||||
"subtitle": "学习|已预排",
|
||||
"tags": ["当前处理"],
|
||||
"detail_lines": [
|
||||
"时段:第11天(第15周 周四) 第7-8节",
|
||||
"任务类 ID:3",
|
||||
"当前尝试:第 2 次"
|
||||
],
|
||||
"meta": { "task_id": 52, "status": "suggested" }
|
||||
},
|
||||
{
|
||||
"title": "[43]谓词逻辑基础(量词与公式)",
|
||||
"subtitle": "学习|已预排",
|
||||
"tags": ["待处理"],
|
||||
"detail_lines": [
|
||||
"时段:第15天(第16周 周一) 第7-8节"
|
||||
],
|
||||
"meta": { "task_id": 43, "queue_index": 0 }
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "items",
|
||||
"title": "当前处理",
|
||||
"items": [
|
||||
{
|
||||
"title": "[52]组合逻辑电路分析",
|
||||
"subtitle": "学习|已预排",
|
||||
"tags": ["当前处理"],
|
||||
"detail_lines": [
|
||||
"时段:第11天(第15周 周四) 第7-8节",
|
||||
"任务类 ID:3",
|
||||
"当前尝试:第 2 次"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "待处理队列",
|
||||
"items": [
|
||||
{
|
||||
"title": "[43]谓词逻辑基础(量词与公式)",
|
||||
"subtitle": "学习|已预排",
|
||||
"tags": ["待处理"],
|
||||
"detail_lines": [
|
||||
"时段:第15天(第16周 周一) 第7-8节"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "[91]英语作文",
|
||||
"subtitle": "英语|待安排",
|
||||
"tags": ["待处理"],
|
||||
"detail_lines": [
|
||||
"时段:尚未落位"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "运行概况",
|
||||
"fields": [
|
||||
{ "label": "待处理", "value": "3 项" },
|
||||
{ "label": "已完成", "value": "1 项" },
|
||||
{ "label": "已跳过", "value": "0 项" },
|
||||
{ "label": "当前任务", "value": "[52]组合逻辑电路分析" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "最近一次失败",
|
||||
"subtitle": "队列中保留了上一轮 apply 的失败原因。",
|
||||
"tone": "warning",
|
||||
"detail_lines": [
|
||||
"移动失败:目标位置已被占用,请重新选择候选时段。"
|
||||
]
|
||||
}
|
||||
],
|
||||
"machine_payload": {
|
||||
"pending_count": 3,
|
||||
"completed_count": 1,
|
||||
"skipped_count": 0,
|
||||
"current_task_id": 52,
|
||||
"current_attempt": 2,
|
||||
"next_task_ids": [43, 91, 88]
|
||||
},
|
||||
"raw_text": "队列状态 debug 原文,不要默认主展示"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const swapPayload: TimelineToolPayload = {
|
||||
"name": "swap",
|
||||
"status": "done",
|
||||
"summary": "交换任务成功",
|
||||
"arguments_preview": "任务A:[52]组合逻辑电路分析,任务B:[43]谓词逻辑基础(量词与公式)",
|
||||
"result_view": {
|
||||
"view_type": "schedule.operation_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "交换任务成功",
|
||||
"subtitle": "[52]组合逻辑电路分析 与 [43]谓词逻辑基础(量词与公式) 已交换位置",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "任务数量", "value": "2个" },
|
||||
{ "label": "影响天数", "value": "2天" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"affected_days_label": "第11天、第15天",
|
||||
"changes": [
|
||||
{
|
||||
"task_id": 52,
|
||||
"task_label": "[52]组合逻辑电路分析",
|
||||
"before_label": "第15天 第7-8节",
|
||||
"after_label": "第11天 第7-8节",
|
||||
"status_label": "已预排 -> 已预排"
|
||||
},
|
||||
{
|
||||
"task_id": 43,
|
||||
"task_label": "[43]谓词逻辑基础(量词与公式)",
|
||||
"before_label": "第11天 第7-8节",
|
||||
"after_label": "第15天 第7-8节",
|
||||
"status_label": "已预排 -> 已预排"
|
||||
}
|
||||
],
|
||||
"raw_text": "交换完成:这里是 debug 原文,不要默认主展示"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queryRangePayload: TimelineToolPayload = {
|
||||
"name": "query_range",
|
||||
"status": "done",
|
||||
"summary": "第1天(第1周 周一)全日概况",
|
||||
"arguments_preview": "目标日期:第1天(第1周 周一)",
|
||||
"result_view": {
|
||||
"view_type": "schedule.read_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "第1天(第1周 周一)全日概况",
|
||||
"subtitle": "已占用 6/12 节,连续空闲 3 段。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "总占用", "value": "6/12" },
|
||||
{ "label": "任务占用", "value": "4/12" },
|
||||
{ "label": "空闲段", "value": "3 段" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "第1-2节",
|
||||
"subtitle": "1 个事项",
|
||||
"tags": ["2 节", "已占用"],
|
||||
"detail_lines": ["[52]组合逻辑电路分析|已预排|学习"],
|
||||
"meta": {
|
||||
"day": 1,
|
||||
"slot_start": 1,
|
||||
"slot_end": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "第3-4节",
|
||||
"subtitle": "空闲",
|
||||
"tags": ["2 节", "空闲"],
|
||||
"detail_lines": ["这一段当前可直接安排任务。"],
|
||||
"meta": {
|
||||
"day": 1,
|
||||
"slot_start": 3,
|
||||
"slot_end": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "当日概况",
|
||||
"fields": [
|
||||
{ label: "总占用", value: "6/12 节" },
|
||||
{ label: "任务占用", value: "4/12 节" },
|
||||
{ label: "连续空闲段", value: "3 段" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "时段分布",
|
||||
"items": [
|
||||
{
|
||||
"title": "第1-2节",
|
||||
"subtitle": "1 个事项",
|
||||
"tags": ["2 节", "已占用"],
|
||||
"detail_lines": ["[52]组合逻辑电路分析|已预排|学习"]
|
||||
},
|
||||
{
|
||||
"title": "第3-4节",
|
||||
"subtitle": "空闲",
|
||||
"tags": ["2 节", "空闲"],
|
||||
"detail_lines": ["这一段当前可直接安排任务。"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "提示",
|
||||
"subtitle": "当前日期仍有可用时段。",
|
||||
"tone": "info",
|
||||
"detail_lines": ["可以继续查询可用时段或选择任务落位。"]
|
||||
}
|
||||
],
|
||||
"raw_text": "debug 原始 observation,不要默认主展示",
|
||||
"machine_payload": {
|
||||
"mode": "full_day",
|
||||
"day": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const expandedMap = ref<Record<string, boolean>>({
|
||||
analysis: true,
|
||||
move: true,
|
||||
overview: true,
|
||||
queue: true,
|
||||
swap: true,
|
||||
query: true
|
||||
})
|
||||
|
||||
function toggle(key: string) {
|
||||
expandedMap.value[key] = !expandedMap.value[key]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixture-page">
|
||||
<header class="fixture-header">
|
||||
<h1>ToolCardRenderer Fixture</h1>
|
||||
<p>验证两类新协议卡片的通用性与健壮性</p>
|
||||
</header>
|
||||
|
||||
<main class="fixture-content">
|
||||
<section class="fixture-section">
|
||||
<h2>1. analysis_result: analyze_health (综合体检)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="analysisPayload"
|
||||
:expanded="expandedMap.analysis"
|
||||
@toggle="toggle('analysis')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>2. operation_result: move (移动任务)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="movePayload"
|
||||
:expanded="expandedMap.move"
|
||||
@toggle="toggle('move')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>3. read_result: get_overview (排程总览)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="overviewPayload"
|
||||
:expanded="expandedMap.overview"
|
||||
@toggle="toggle('overview')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>4. read_result: queue_status (队列状态 + Warning)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="queuePayload"
|
||||
:expanded="expandedMap.queue"
|
||||
@toggle="toggle('queue')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>5. operation_result: swap (交换任务)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="swapPayload"
|
||||
:expanded="expandedMap.swap"
|
||||
@toggle="toggle('swap')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>6. read_result: query_range (全日概况)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="queryRangePayload"
|
||||
:expanded="expandedMap.query"
|
||||
@toggle="toggle('query')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fixture-section">
|
||||
<h2>7. 降级测试 (未知协议)</h2>
|
||||
<div class="card-wrapper">
|
||||
<ToolCardRenderer
|
||||
:payload="{
|
||||
name: 'unknown_tool',
|
||||
status: 'done',
|
||||
summary: '这是 fallback summary',
|
||||
result_view: {
|
||||
view_type: 'unknown.type',
|
||||
collapsed: {
|
||||
title: '未知协议标题',
|
||||
subtitle: '这是从 collapsed 中读取的副标题',
|
||||
status_label: '进行中',
|
||||
metrics: [{ label: '测试', value: '100' }]
|
||||
}
|
||||
}
|
||||
}"
|
||||
:expanded="false"
|
||||
@toggle="() => {}"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fixture-page {
|
||||
padding: 40px;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.fixture-header {
|
||||
margin-bottom: 40px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.fixture-header h1 {
|
||||
font-size: 28px;
|
||||
color: #1e293b;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.fixture-header p {
|
||||
color: #64748b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fixture-content {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.fixture-section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.fixture-section h2 {
|
||||
font-size: 18px;
|
||||
color: #334155;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
481
frontend/src/views/debug/ToolCardMockPage.vue
Normal file
481
frontend/src/views/debug/ToolCardMockPage.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import ToolCardRenderer from '@/components/dashboard/ToolCardRenderer.vue'
|
||||
|
||||
// 后端真实结构全家桶 mock
|
||||
const mockData = [
|
||||
{
|
||||
"tool": "web_search",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "找到 2 条网页结果",
|
||||
"arguments_preview": "查询内容:高中数学学习方法,结果上限:2",
|
||||
"argument_view": {
|
||||
"view_type": "tool.arguments",
|
||||
"version": 1,
|
||||
"collapsed": { "summary": "查询内容:高中数学学习方法,结果上限:2", "args_count": 2 },
|
||||
"expanded": {
|
||||
"fields": [
|
||||
{ "key": "query", "label": "查询内容", "value": "高中数学学习方法", "display": "高中数学学习方法" },
|
||||
{ "key": "top_k", "label": "数量上限", "value": 2, "display": "2" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"result_view": {
|
||||
"view_type": "web.search_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "找到 2 条网页结果",
|
||||
"subtitle": "关键词:高中数学学习方法",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "结果数", "value": "2" },
|
||||
{ "label": "时效", "value": "近 30 天" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "高中数学如何高效提分",
|
||||
"subtitle": "example.edu",
|
||||
"tags": ["example.edu", "2026-04-20"],
|
||||
"detail_lines": ["系统梳理基础概念、错题复盘和专题训练。", "https://example.edu/math-study"],
|
||||
"meta": { "url": "https://example.edu/math-study" }
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "搜索参数",
|
||||
"fields": [
|
||||
{ "label": "关键词", "value": "高中数学学习方法" },
|
||||
{ "label": "结果上限", "value": "2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "搜索结果",
|
||||
"items": [
|
||||
{
|
||||
"title": "高中数学如何高效提分",
|
||||
"subtitle": "example.edu",
|
||||
"tags": ["example.edu", "2026-04-20"],
|
||||
"detail_lines": ["系统梳理基础概念、错题复盘和专题训练。"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"raw_text": "{\"tool\":\"web_search\",\"query\":\"高中数学学习方法\",\"count\":2}",
|
||||
"machine_payload": { "tool": "web_search", "query": "高中数学学习方法", "count": 2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool": "web_fetch",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "已抓取:高中数学如何高效提分",
|
||||
"result_view": {
|
||||
"view_type": "web.fetch_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "已抓取:高中数学如何高效提分",
|
||||
"subtitle": "来源:example.edu",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "正文长度", "value": "1280 字" },
|
||||
{ "label": "是否截断", "value": "否" },
|
||||
{ "label": "来源", "value": "example.edu" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "高中数学如何高效提分",
|
||||
"subtitle": "https://example.edu/math-study",
|
||||
"tags": ["example.edu"],
|
||||
"detail_lines": ["第一段正文预览。", "第二段正文预览。"]
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "页面信息",
|
||||
"fields": [
|
||||
{ "label": "链接", "value": "https://example.edu/math-study" },
|
||||
{ "label": "标题", "value": "高中数学如何高效提分" },
|
||||
{ "label": "正文长度", "value": "1280 字" },
|
||||
{ "label": "是否截断", "value": "否" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "正文预览",
|
||||
"subtitle": "这是一段网页正文摘要预览。",
|
||||
"tone": "info",
|
||||
"detail_lines": ["第一段正文预览。", "第二段正文预览。"]
|
||||
}
|
||||
],
|
||||
"raw_text": "{\"tool\":\"web_fetch\",\"url\":\"https://example.edu/math-study\"}",
|
||||
"machine_payload": { "tool": "web_fetch", "url": "https://example.edu/math-study", "truncated": false }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool": "upsert_task_class",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "任务类已创建",
|
||||
"result_view": {
|
||||
"view_type": "taskclass.write_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "任务类已创建",
|
||||
"subtitle": "已创建「数学压轴题」,共 3 项任务",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "任务类数量", "value": "1 个" },
|
||||
{ "label": "任务项数量", "value": "3 项" },
|
||||
{ "label": "来源", "value": "对话" },
|
||||
{ "label": "写入方式", "value": "创建" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "完成圆锥曲线专项",
|
||||
"subtitle": "第 1 项",
|
||||
"tags": ["顺序 1", "未指定嵌入时间"],
|
||||
"detail_lines": ["内容:完成圆锥曲线专项", "嵌入时间:未指定"]
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "写入结果",
|
||||
"subtitle": "已创建任务类,结果可直接用于后续排程。",
|
||||
"tone": "success",
|
||||
"detail_lines": ["任务类:数学压轴题", "任务类 ID:88", "任务项数量:3 项"]
|
||||
},
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "任务类字段",
|
||||
"fields": [
|
||||
{ "label": "任务类 ID", "value": "88" },
|
||||
{ "label": "名称", "value": "数学压轴题" },
|
||||
{ "label": "模式", "value": "自动排布" },
|
||||
{ "label": "学科类型", "value": "计算型" },
|
||||
{ "label": "难度等级", "value": "高" },
|
||||
{ "label": "认知强度", "value": "高" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "items",
|
||||
"title": "任务项列表",
|
||||
"items": [
|
||||
{
|
||||
"title": "完成圆锥曲线专项",
|
||||
"subtitle": "第 1 项",
|
||||
"tags": ["顺序 1"],
|
||||
"detail_lines": ["内容:完成圆锥曲线专项"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"raw_text": "{\"tool\":\"upsert_task_class\",\"success\":true,\"task_class_id\":88,\"created\":true}",
|
||||
"machine_payload": { "parsed_result": { "task_class_id": 88, "created": true } }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool": "context_tools_add",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "已激活排程工具域",
|
||||
"result_view": {
|
||||
"view_type": "tool.context_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "已激活排程工具域",
|
||||
"subtitle": "排程工具域已激活,模式=替换,启用 排程改写、健康分析。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "域", "value": "排程" },
|
||||
{ "label": "包", "value": "2 个" },
|
||||
{ "label": "模式", "value": "替换" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "排程工具域",
|
||||
"subtitle": "排程工具域已激活,模式=替换,启用 排程改写、健康分析。",
|
||||
"tags": ["激活", "替换", "2 个包"],
|
||||
"detail_lines": ["工具域:排程工具域", "工具包:排程改写、健康分析", "注入模式:替换"]
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "动态工具区已更新",
|
||||
"summary": "排程工具域已激活,模式=替换,启用 排程改写、健康分析。",
|
||||
"tone": "info",
|
||||
"detail_lines": ["已激活目标工具域,可继续调用对应业务工具。"]
|
||||
},
|
||||
{
|
||||
"type": "kv",
|
||||
"title": "当前工具区参数",
|
||||
"fields": [
|
||||
{ "label": "工具域", "value": "排程工具域" },
|
||||
{ "label": "工具包", "value": "排程改写、健康分析" },
|
||||
{ "label": "注入模式", "value": "替换" },
|
||||
{ "label": "清空全部", "value": "否" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"raw_text": "{\"tool\":\"context_tools_add\",\"success\":true,\"action\":\"activate\"}",
|
||||
"machine_payload": { "tool": "context_tools_add", "domain": "schedule", "packs": ["mutation", "analyze"] }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool": "queue_pop_head",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "已获取队首任务",
|
||||
"result_view": {
|
||||
"view_type": "schedule.read_result",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "已获取队首任务",
|
||||
"subtitle": "[101]数学压轴题,待处理 2 项。",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"metrics": [
|
||||
{ "label": "待处理", "value": "2 项" },
|
||||
{ "label": "已完成", "value": "1 项" },
|
||||
{ "label": "已跳过", "value": "0 项" }
|
||||
]
|
||||
},
|
||||
"expanded": {
|
||||
"items": [
|
||||
{
|
||||
"title": "[101]数学压轴题",
|
||||
"subtitle": "学习,已预排",
|
||||
"tags": ["当前处理", "已预排", "2 节"],
|
||||
"detail_lines": ["时段:第4天 第7-8节", "任务类 ID:88", "时长需求:2 节"]
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"type": "items",
|
||||
"title": "当前处理",
|
||||
"items": [
|
||||
{
|
||||
"title": "[101]数学压轴题",
|
||||
"subtitle": "学习,已预排",
|
||||
"tags": ["当前处理"],
|
||||
"detail_lines": ["时段:第4天 第7-8节"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"title": "队首任务已就位",
|
||||
"summary": "可以继续调用 queue_apply_head_move 或 queue_skip_head。",
|
||||
"tone": "info",
|
||||
"detail_lines": ["待处理:2 项", "已完成:1 项", "已跳过:0 项"]
|
||||
}
|
||||
],
|
||||
"raw_text": "{\"tool\":\"queue_pop_head\",\"has_head\":true}",
|
||||
"machine_payload": { "tool": "queue_pop_head", "has_head": true, "pending_count": 2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool": "legacy_tool",
|
||||
"status": "done",
|
||||
"success": true,
|
||||
"summary": "旧协议兜底",
|
||||
"result_view": {
|
||||
"view_type": "legacy_text",
|
||||
"version": 1,
|
||||
"collapsed": {
|
||||
"title": "旧协议工具已完成",
|
||||
"status": "done",
|
||||
"status_label": "已完成",
|
||||
"tool": "legacy_tool",
|
||||
"tool_label": "旧协议工具",
|
||||
"has_output": true
|
||||
},
|
||||
"expanded": {
|
||||
"raw_text_label": "原始结果",
|
||||
"raw_text": "这里是 legacy_text 的原始文本。"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const expandedStates = ref<Record<number, boolean>>({
|
||||
0: true, 1: true, 2: true, 3: true, 4: true, 5: true
|
||||
})
|
||||
|
||||
function toggle(index: number) {
|
||||
expandedStates.value[index] = !expandedStates.value[index]
|
||||
}
|
||||
|
||||
const showDebug = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mock-page">
|
||||
<header class="mock-header">
|
||||
<div class="mock-header__content">
|
||||
<h1>ToolCardRenderer 全家桶验收页</h1>
|
||||
<p>集成后端所有当前稳定 view_type,验证结构化渲染及降级逻辑。</p>
|
||||
</div>
|
||||
<div class="mock-header__actions">
|
||||
<button class="debug-toggle" @click="showDebug = !showDebug">
|
||||
{{ showDebug ? '隐藏调试信息' : '显示调试信息' }}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="mock-content">
|
||||
<section v-for="(payload, idx) in mockData" :key="idx" class="fixture-item">
|
||||
<div class="fixture-item__label">
|
||||
<span class="tool-tag">{{ payload.tool }}</span>
|
||||
<span class="view-tag">{{ payload.result_view.view_type }}</span>
|
||||
</div>
|
||||
|
||||
<div class="fixture-item__card">
|
||||
<ToolCardRenderer
|
||||
:payload="{
|
||||
name: payload.tool,
|
||||
status: payload.status,
|
||||
summary: payload.summary,
|
||||
arguments_preview: payload.arguments_preview || '',
|
||||
argument_view: payload.argument_view,
|
||||
result_view: payload.result_view
|
||||
}"
|
||||
:expanded="!!expandedStates[idx]"
|
||||
@toggle="toggle(idx)"
|
||||
/>
|
||||
|
||||
<div v-if="showDebug" class="debug-info">
|
||||
<div class="debug-section">
|
||||
<span class="debug-label">raw_text:</span>
|
||||
<pre>{{ payload.result_view.expanded?.raw_text }}</pre>
|
||||
</div>
|
||||
<div class="debug-section">
|
||||
<span class="debug-label">machine_payload:</span>
|
||||
<pre>{{ JSON.stringify(payload.result_view.expanded?.machine_payload, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mock-page {
|
||||
padding: 40px;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.mock-header {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.mock-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.debug-toggle {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mock-content {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.fixture-item {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.fixture-item__label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tool-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.view-tag {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
background: #f1f5f9;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: #1e293b;
|
||||
border-radius: 12px;
|
||||
color: #e2e8f0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.debug-info pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.fixture-item {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user