fix: tighten agent trace workbench UI
This commit is contained in:
@@ -357,7 +357,9 @@ function isSetupTraceEvent(event: TraceEvent): boolean {
|
||||
const label = String(event.label ?? "");
|
||||
return /^session:|^stdio:|^prompt:|^thread:|^turn:started|^turn:start|turn\/start|codex turn|tool:codex-app-server/iu.test(label) ||
|
||||
/^agentrun:backend:(codex-app-server-starting|initialize:completed|thread\/start|thread\/resume|thread\/started|turn\/start|turn\/started)/iu.test(label) ||
|
||||
/^agentrun:(run|command|runner-job):(reuse-skipped|created)$/iu.test(label) ||
|
||||
/^agentrun:run:(reuse-skipped|reused)$/iu.test(label) ||
|
||||
/^agentrun:command:created$/iu.test(label) ||
|
||||
/^agentrun:runner-job:(created|ensured|reused)$/iu.test(label) ||
|
||||
/^agentrun:session-storage-recover-warning$/iu.test(label);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,17 @@ test("R1 shared trace renderer starts at the first user-meaningful event", () =>
|
||||
assert.doesNotMatch(rows[0]?.header ?? "", /runner-job|resource-bundle/u);
|
||||
});
|
||||
|
||||
test("R1 shared trace renderer suppresses AgentRun reuse diagnostics", () => {
|
||||
const rows = traceDisplayRows({ traceId: "trc_reuse", status: "running" }, [
|
||||
{ label: "agentrun:run:reused", status: "reused", message: "run reused" },
|
||||
{ label: "agentrun:runner-job:ensured", status: "runner-job-ensured", message: "runner ensured" },
|
||||
{ label: "agentrun:assistant:message", type: "assistant", message: "OK", source: "agentrun", sourceSeq: 3 }
|
||||
]);
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0]?.body, "OK");
|
||||
assert.doesNotMatch(rows[0]?.header ?? "", /reused|ensured|runner-job/u);
|
||||
});
|
||||
|
||||
test("R1 trace API snapshots stay authoritative over compact result traces", () => {
|
||||
const previous = {
|
||||
traceId: "trc_merge",
|
||||
|
||||
@@ -69,6 +69,15 @@ function rowKey(row: TraceEventRow, index: number): string {
|
||||
return `${row.rowId}-${row.seq ?? "no-seq"}-${index}`;
|
||||
}
|
||||
|
||||
function onDisclosureToggle(event: Event): void {
|
||||
const target = event.currentTarget;
|
||||
if (target instanceof HTMLDetailsElement) expanded.value = target.open;
|
||||
}
|
||||
|
||||
function rowIsTool(row: TraceEventRow): boolean {
|
||||
return row.rowId.startsWith("tool:") || /commandExecution/u.test(row.header);
|
||||
}
|
||||
|
||||
function traceRecord(trace: RunnerTrace | null | undefined): Record<string, unknown> {
|
||||
return trace && typeof trace === "object" ? trace as Record<string, unknown> : {};
|
||||
}
|
||||
@@ -81,44 +90,61 @@ function recordValue(value: unknown): Record<string, unknown> | null {
|
||||
|
||||
<template>
|
||||
<section v-if="trace" class="trace-timeline" :data-status="trace.status" :data-following="following ? 'true' : 'false'">
|
||||
<header class="trace-toolbar">
|
||||
<button class="trace-toggle" type="button" @click="expanded = !expanded">{{ expanded ? "收起" : "展开" }}</button>
|
||||
<strong>Trace</strong>
|
||||
</header>
|
||||
<details class="trace-meta-details">
|
||||
<summary>{{ detailTitle }}</summary>
|
||||
<div class="trace-meta-panel">
|
||||
<dl class="trace-meta-grid">
|
||||
<div><dt>trace</dt><dd>{{ trace.traceId || "pending" }}</dd></div>
|
||||
<div><dt>rows</dt><dd>{{ readableRows.length }} / {{ eventCount }}</dd></div>
|
||||
<div v-if="noiseCount > 0"><dt>hidden</dt><dd>{{ noiseCount }}</dd></div>
|
||||
<div><dt>last</dt><dd>{{ lastEventLabel }}</dd></div>
|
||||
</dl>
|
||||
<div v-if="summaryItems.length > 0" class="trace-summary-strip" aria-label="Trace identity summary">
|
||||
<span v-for="item in summaryItems" :key="item.label" class="trace-summary-chip"><b>{{ item.label }}</b>{{ item.value }}</span>
|
||||
</div>
|
||||
<p v-if="trace.eventsCompacted" class="trace-warning">当前 result 返回被压缩,已通过 trace replay 入口请求完整事件;完成前不要把压缩窗口当完整 trace。</p>
|
||||
<p v-if="noiseCount > 0 && !rawMode" class="trace-warning">已聚合隐藏 {{ noiseCount }} 条 backend/chunk/token 噪声事件;原始 payload 仍可在原始事件中审计。</p>
|
||||
<div class="trace-meta-actions">
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="following = !following">{{ following ? "跟随" : "暂停" }}</button>
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="jumpToBottom">到底部</button>
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="rawMode = !rawMode">{{ rawMode ? "可读视图" : "原始事件" }}</button>
|
||||
</div>
|
||||
<details class="trace-disclosure" :open="expanded" @toggle="onDisclosureToggle">
|
||||
<summary class="trace-disclosure-summary" aria-label="折叠运行记录">
|
||||
<span class="trace-disclosure-caret" aria-hidden="true" />
|
||||
<span class="trace-disclosure-count">{{ readableRows.length }}/{{ eventCount }}</span>
|
||||
<span v-if="noiseCount > 0" class="trace-disclosure-hidden">+{{ noiseCount }}</span>
|
||||
</summary>
|
||||
<div class="trace-disclosure-body">
|
||||
<details class="trace-meta-details">
|
||||
<summary :aria-label="detailTitle" :title="detailTitle">!</summary>
|
||||
<div class="trace-meta-panel">
|
||||
<dl class="trace-meta-grid">
|
||||
<div><dt>trace</dt><dd>{{ trace.traceId || "pending" }}</dd></div>
|
||||
<div><dt>rows</dt><dd>{{ readableRows.length }} / {{ eventCount }}</dd></div>
|
||||
<div v-if="noiseCount > 0"><dt>hidden</dt><dd>{{ noiseCount }}</dd></div>
|
||||
<div><dt>last</dt><dd>{{ lastEventLabel }}</dd></div>
|
||||
</dl>
|
||||
<div v-if="summaryItems.length > 0" class="trace-summary-strip" aria-label="Trace identity summary">
|
||||
<span v-for="item in summaryItems" :key="item.label" class="trace-summary-chip"><b>{{ item.label }}</b>{{ item.value }}</span>
|
||||
</div>
|
||||
<p v-if="trace.eventsCompacted" class="trace-warning">当前 result 返回被压缩,已通过 trace replay 入口请求完整事件;完成前不要把压缩窗口当完整 trace。</p>
|
||||
<p v-if="noiseCount > 0 && !rawMode" class="trace-warning">已聚合隐藏 {{ noiseCount }} 条 backend/chunk/token 噪声事件;原始 payload 仍可在原始事件中审计。</p>
|
||||
<div class="trace-meta-actions">
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="following = !following">{{ following ? "跟随" : "暂停" }}</button>
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="jumpToBottom">到底部</button>
|
||||
<button class="btn btn-secondary btn-sm" type="button" @click="rawMode = !rawMode">{{ rawMode ? "可读视图" : "原始事件" }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<ol ref="listRef" @scroll="onScroll">
|
||||
<template v-if="!rawMode">
|
||||
<li v-for="(row, index) in readableRows" :key="rowKey(row, index)" class="trace-render-row" :data-event-status="row.tone" :data-terminal="row.terminal ? 'true' : 'false'" :data-row-kind="rowIsTool(row) ? 'tool' : 'message'">
|
||||
<details v-if="rowIsTool(row)" class="trace-tool-details">
|
||||
<summary><code>{{ row.header }}</code></summary>
|
||||
<MessageMarkdown v-if="row.body && row.bodyFormat === 'markdown'" class="trace-row-body trace-row-markdown" :source="row.body" />
|
||||
<pre v-else-if="row.body" class="trace-row-body">{{ row.body }}</pre>
|
||||
<span v-else class="trace-row-body muted">已记录</span>
|
||||
</details>
|
||||
<template v-else>
|
||||
<code>{{ row.header }}</code>
|
||||
<MessageMarkdown v-if="row.body && row.bodyFormat === 'markdown'" class="trace-row-body trace-row-markdown" :source="row.body" />
|
||||
<pre v-else-if="row.body" class="trace-row-body">{{ row.body }}</pre>
|
||||
<span v-else class="trace-row-body muted">已记录</span>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li v-for="(event, index) in events" :key="eventKey(event, index)" class="trace-raw-row" :data-event-status="event.status || 'source'">
|
||||
<code>{{ traceEventLabel(event) }}</code>
|
||||
<span>{{ traceEventBody(event) }}</span>
|
||||
<small v-if="traceEventMeta(event)">{{ traceEventMeta(event) }}</small>
|
||||
</li>
|
||||
</template>
|
||||
</ol>
|
||||
<p v-if="readableRows.length === 0 && !rawMode" class="trace-empty">id={{ trace.traceId || "pending" }};等待后端事件。</p>
|
||||
</div>
|
||||
</details>
|
||||
<ol v-show="expanded" ref="listRef" @scroll="onScroll">
|
||||
<li v-for="(row, index) in readableRows" v-if="!rawMode" :key="rowKey(row, index)" class="trace-render-row" :data-event-status="row.tone" :data-terminal="row.terminal ? 'true' : 'false'">
|
||||
<code>{{ row.header }}</code>
|
||||
<MessageMarkdown v-if="row.body && row.bodyFormat === 'markdown'" class="trace-row-body trace-row-markdown" :source="row.body" />
|
||||
<pre v-else-if="row.body" class="trace-row-body">{{ row.body }}</pre>
|
||||
<span v-else class="trace-row-body muted">已记录</span>
|
||||
</li>
|
||||
<li v-for="(event, index) in events" v-else :key="eventKey(event, index)" class="trace-raw-row" :data-event-status="event.status || 'source'">
|
||||
<code>{{ traceEventLabel(event) }}</code>
|
||||
<span>{{ traceEventBody(event) }}</span>
|
||||
<small v-if="traceEventMeta(event)">{{ traceEventMeta(event) }}</small>
|
||||
</li>
|
||||
</ol>
|
||||
<p v-if="expanded && readableRows.length === 0 && !rawMode" class="trace-empty">trace={{ trace.traceId || "pending" }};等待后端事件。</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import type { ChatMessage } from "@/types";
|
||||
import StatusBadge from "@/components/common/StatusBadge.vue";
|
||||
import TraceTimeline from "@/components/agent/TraceTimeline.vue";
|
||||
@@ -13,13 +13,54 @@ import { traceIdentityText } from "./message-rendering";
|
||||
|
||||
const workbench = useWorkbenchStore();
|
||||
const clipboard = useClipboard();
|
||||
const panelRef = ref<HTMLElement | null>(null);
|
||||
const following = ref(true);
|
||||
const detailMessageId = ref<string | null>(null);
|
||||
const detailMessage = computed(() => workbench.messages.find((message) => message.id === detailMessageId.value) ?? null);
|
||||
const scrollSignature = computed(() => workbench.messages.map((message) => [
|
||||
message.id,
|
||||
message.status,
|
||||
message.text?.length ?? 0,
|
||||
message.updatedAt ?? "",
|
||||
message.runnerTrace?.eventCount ?? message.runnerTrace?.events?.length ?? 0,
|
||||
message.runnerTrace?.status ?? ""
|
||||
].join(":")));
|
||||
|
||||
watch(scrollSignature, async () => {
|
||||
if (!following.value) return;
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
});
|
||||
|
||||
function openDetails(message: ChatMessage): void {
|
||||
detailMessageId.value = message.id;
|
||||
}
|
||||
|
||||
function isRunningMessage(message: ChatMessage): boolean {
|
||||
return message.role === "agent" && String(message.status ?? "").toLowerCase() === "running";
|
||||
}
|
||||
|
||||
function showStatusBadge(message: ChatMessage): boolean {
|
||||
return message.role !== "agent" || !isRunningMessage(message);
|
||||
}
|
||||
|
||||
function onPanelScroll(): void {
|
||||
const panel = panelRef.value;
|
||||
if (!panel) return;
|
||||
following.value = panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 56;
|
||||
}
|
||||
|
||||
function scrollToBottom(): void {
|
||||
const panel = panelRef.value;
|
||||
if (!panel) return;
|
||||
panel.scrollTop = panel.scrollHeight;
|
||||
}
|
||||
|
||||
function traceDefaultExpanded(message: ChatMessage): boolean {
|
||||
return !isTerminalStatus(message.runnerTrace?.status ?? message.status);
|
||||
}
|
||||
@@ -30,13 +71,14 @@ function traceStorageKey(message: ChatMessage): string {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="conversation-panel" id="conversation-list">
|
||||
<section id="conversation-list" ref="panelRef" class="conversation-panel" :data-following="following ? 'true' : 'false'" @scroll="onPanelScroll">
|
||||
<article v-for="message in workbench.messages" :key="message.id" class="message-card" :data-role="message.role" :data-status="message.status">
|
||||
<header v-if="message.role !== 'user'">
|
||||
<strong>{{ message.title }}</strong>
|
||||
<div class="message-header-actions">
|
||||
<StatusBadge :status="message.status" />
|
||||
<button v-if="message.role === 'agent'" class="message-detail-button" type="button" aria-label="消息详情" title="消息详情" @click="openDetails(message)">i</button>
|
||||
<span v-if="isRunningMessage(message)" class="agent-running-indicator" aria-label="运行中" role="status"><span /><span /><span /></span>
|
||||
<StatusBadge v-if="showStatusBadge(message)" :status="message.status" />
|
||||
<button v-if="message.role === 'agent'" class="message-detail-button" type="button" aria-label="运行详情" title="运行详情" @click="openDetails(message)">!</button>
|
||||
</div>
|
||||
</header>
|
||||
<TraceTimeline v-if="message.role === 'agent' && message.runnerTrace" :trace="message.runnerTrace" :default-expanded="traceDefaultExpanded(message)" :storage-key="traceStorageKey(message)" />
|
||||
|
||||
@@ -142,7 +142,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const sessionId = composer.value.sessionId;
|
||||
const threadId = composer.value.threadId;
|
||||
const user = makeMessage("user", value, "sent", { traceId: steerTraceId ?? traceId, conversationId, sessionId, threadId, title: steerMode ? "用户引导" : "用户" });
|
||||
const pending = makeMessage("agent", "正在处理这次 Code Agent 请求;复杂问题可能需要几分钟。", "running", { traceId, conversationId, sessionId, threadId, title: "Code Agent 处理中", retryInput: value });
|
||||
const pending = makeMessage("agent", "", "running", { traceId, conversationId, sessionId, threadId, title: "Code Agent", retryInput: value });
|
||||
messages.value.push(user, pending);
|
||||
chatPending.value = true;
|
||||
currentRequest.value = { traceId, conversationId, sessionId, threadId, status: "running" };
|
||||
@@ -262,7 +262,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
|
||||
function reattachTrace(traceId: string): void {
|
||||
const initial: AgentChatResponse = { status: "running", traceId, resultUrl: `/v1/agent/chat/result/${encodeURIComponent(traceId)}?projectId=${encodeURIComponent(activeProjectId.value)}` };
|
||||
if (!messages.value.some((message) => message.traceId === traceId)) messages.value.push(makeMessage("agent", "正在重新连接运行中的 trace。", "running", { traceId, title: "Trace reattach" }));
|
||||
if (!messages.value.some((message) => message.traceId === traceId)) messages.value.push(makeMessage("agent", "", "running", { traceId, title: "Code Agent" }));
|
||||
subscribe(traceId, initial);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,23 +35,23 @@
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
grid-template-rows: 58px minmax(0, 1fr);
|
||||
grid-template-rows: 44px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.platform-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #d8e1eb;
|
||||
background: rgba(248, 250, 252, 0.92);
|
||||
padding: 0 18px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.platform-content {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
padding: 22px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.platform-content > * {
|
||||
@@ -515,7 +515,7 @@
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
grid-template-columns: auto minmax(0, 1fr) minmax(280px, 360px);
|
||||
gap: 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.workbench-center,
|
||||
@@ -548,8 +548,8 @@
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
align-content: start;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@
|
||||
.conversation-panel {
|
||||
display: grid;
|
||||
min-height: 0;
|
||||
gap: 10px;
|
||||
gap: 6px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -623,7 +623,8 @@
|
||||
|
||||
.conversation-panel {
|
||||
align-content: start;
|
||||
padding: 14px;
|
||||
padding: 8px;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.conversation-empty-hint {
|
||||
@@ -634,17 +635,36 @@
|
||||
}
|
||||
|
||||
.message-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
align-self: start;
|
||||
gap: 6px;
|
||||
gap: 5px;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-card[data-role="agent"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-card[data-role="agent"][data-status="running"] {
|
||||
border-color: #7dd3fc;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fdff 100%);
|
||||
}
|
||||
|
||||
.message-card[data-role="agent"][data-status="running"]::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #0891b2, #22c55e, transparent);
|
||||
content: "";
|
||||
transform: translateX(-100%);
|
||||
animation: agent-run-scan 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.message-card[data-role="user"] {
|
||||
justify-self: end;
|
||||
width: max-content;
|
||||
@@ -681,9 +701,45 @@
|
||||
}
|
||||
|
||||
.message-detail-button {
|
||||
border: 0;
|
||||
display: inline-flex;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 999px;
|
||||
background: white;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.agent-running-indicator {
|
||||
display: inline-flex;
|
||||
width: 28px;
|
||||
height: 18px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.agent-running-indicator span {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: #0891b2;
|
||||
animation: agent-run-dot 900ms ease-in-out infinite;
|
||||
}
|
||||
|
||||
.agent-running-indicator span:nth-child(2) {
|
||||
animation-delay: 120ms;
|
||||
}
|
||||
|
||||
.agent-running-indicator span:nth-child(3) {
|
||||
animation-delay: 240ms;
|
||||
}
|
||||
|
||||
.message-text,
|
||||
@@ -743,37 +799,105 @@
|
||||
|
||||
.trace-timeline {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 3px;
|
||||
background: #f8fafc;
|
||||
padding: 6px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.trace-toolbar {
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
min-height: 26px;
|
||||
.trace-disclosure {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.trace-disclosure summary::-webkit-details-marker,
|
||||
.trace-meta-details summary::-webkit-details-marker,
|
||||
.trace-tool-details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.trace-disclosure-summary {
|
||||
display: inline-flex;
|
||||
width: max-content;
|
||||
min-height: 22px;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border: 1px solid #d8e1eb;
|
||||
border-radius: 3px;
|
||||
background: white;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
list-style: none;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.trace-disclosure-caret {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 6px solid #0f766e;
|
||||
transition: transform 120ms ease;
|
||||
}
|
||||
|
||||
.trace-disclosure[open] > .trace-disclosure-summary .trace-disclosure-caret {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.trace-disclosure-count,
|
||||
.trace-disclosure-hidden {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.trace-disclosure-hidden {
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.trace-disclosure-body {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.trace-meta-details {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
display: grid;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.trace-meta-details summary {
|
||||
display: inline-flex;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 999px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
padding: 5px 7px;
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.trace-meta-details[open] summary {
|
||||
border-color: #0891b2;
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.trace-meta-panel {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 3px;
|
||||
background: white;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
@@ -840,7 +964,6 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.trace-toolbar strong,
|
||||
.trace-id {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -855,17 +978,6 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trace-toggle {
|
||||
min-height: 24px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 3px;
|
||||
background: white;
|
||||
color: #334155;
|
||||
padding: 0 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.trace-warning,
|
||||
.trace-empty {
|
||||
margin: 0;
|
||||
@@ -897,6 +1009,43 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.trace-tool-details {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.trace-tool-details > summary {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.trace-tool-details > summary::before {
|
||||
width: 0;
|
||||
height: 0;
|
||||
flex: 0 0 auto;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 6px solid #64748b;
|
||||
content: "";
|
||||
transition: transform 120ms ease;
|
||||
}
|
||||
|
||||
.trace-tool-details[open] > summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.trace-tool-details > summary code {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.trace-timeline li {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
@@ -1037,6 +1186,16 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@keyframes agent-run-scan {
|
||||
0% { transform: translateX(-100%); }
|
||||
55%, 100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
@keyframes agent-run-dot {
|
||||
0%, 80%, 100% { opacity: 0.35; transform: translateY(0); }
|
||||
40% { opacity: 1; transform: translateY(-3px); }
|
||||
}
|
||||
|
||||
.command-composer {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
@@ -2104,7 +2263,7 @@
|
||||
.platform-main {
|
||||
height: 100dvh;
|
||||
min-height: 0;
|
||||
grid-template-rows: 58px minmax(0, 1fr);
|
||||
grid-template-rows: 44px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.platform-content {
|
||||
|
||||
Reference in New Issue
Block a user