import type { LogEntry } from "@umt/contracts";
import { Bot, ExternalLink, History } from "lucide-react";
import type { ReactNode } from "react";
import { Link } from "react-router";
import { useEngineRun } from "~/api/engine.ts";
import { type AgentContext, openAgentDrawer } from "~/components/agent/agent-store.ts";
import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx";
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
import { JsonView } from "~/components/console/json-view.tsx";
import { KvList } from "~/components/console/kv-list.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { TechInfo } from "~/components/console/tech-info.tsx";
import { runTitle } from "~/components/tasks/labels.ts";
import { RunActions, TaskDetailBody, TaskStatusLine } from "~/components/tasks/task-detail.tsx";
import { Button } from "~/components/ui/button.tsx";
import { actorTypeLabel, formatDuration } from "~/lib/labels.ts";
import { formatDateTime } from "~/lib/utils.ts";
import {
actorLabel,
agentGoalFor,
LOG_TYPE_LABEL,
logBadge,
logsPath,
logTitle,
objectLogsPath,
objectText,
TARGET_LABEL,
} from "./labels.ts";
// 日志详情抽屉:任务复用原任务详情(业务摘要、时间线、引擎步骤、取消 / 重跑);定时任务运行、后台调查、审计各自一组字段。
// 底部「问智能体」带上这条日志打开智能体抽屉。
/** 选中的日志 → 智能体上下文(单条时带上能识别的对象 id,服务端据此取数) */
export function agentContextFor(entries: LogEntry[]): AgentContext {
const ctx: AgentContext = {
label: entries.length === 1 ? `日志:${logTitle(entries[0]!)}` : `日志:${entries.length} 条`,
goal: agentGoalFor(entries),
};
if (entries.length === 1) {
const e = entries[0]!;
if (e.source === "engine_run") ctx.taskId = e.refId;
const o = e.object;
if (o?.id) {
if (o.type === "download_task") ctx.downloadTaskId = o.id;
else if (o.type === "organization_record") ctx.recordId = o.id;
else if (o.type === "media_entity") ctx.entityId = o.id;
else if (o.type === "site") ctx.siteId = o.id;
}
}
return ctx;
}
export function AskAgentButton({ entries, size = "sm" }: { entries: LogEntry[]; size?: "sm" | "xs" }) {
return (
);
}
function LinkRow({ links }: { links: { to: string; label: string; icon?: ReactNode }[] }) {
return (
{links.map((l) => (
-
{l.icon ?? }
{l.label}
))}
);
}
function ScheduleRunBody({ e }: { e: LogEntry }) {
const x = e.extra as {
jobId?: string;
jobKind?: string;
trigger?: string;
agentRunId?: string | null;
summary?: string | null;
error?: string | null;
};
const jobId = x.jobId ?? e.object?.id ?? "";
return (
<>
{x.error ? {x.error}
: null}
,
},
]
: []),
...(x.agentRunId ? [{ to: `/console/agent/${encodeURIComponent(x.agentRunId)}`, label: "巡检会话" }] : []),
]}
/>
>
);
}
function AgentRunBody({ e }: { e: LogEntry }) {
const x = e.extra as { toolCalls?: number; spawnReason?: string | null; model?: string | null };
return (
<>
{e.title}, wide: true },
{ label: "开始", value: formatDateTime(e.at) },
{ label: "耗时", value: e.durationMs == null ? "—" : formatDuration(e.durationMs / 1000) },
{ label: "结果", value: e.message, wide: true },
{ label: "工具调用", value: x.toolCalls ?? 0 },
]}
/>
>
);
}
function AuditBody({ e }: { e: LogEntry }) {
const x = e.extra as { action?: string; traceId?: string | null; payload?: unknown };
const o = e.object;
return (
<>
{o?.id ? (
,
},
]}
/>
) : null}
>
);
}
/** 任务:按引擎运行 id 取详情(列表里没有这条也能打开,旧链接 ?run= 用) */
function RunSheet({
runId,
entry,
onOpenRun,
onClose,
}: {
runId: string;
entry: LogEntry | null;
onOpenRun: (id: string) => void;
onClose: () => void;
}) {
const q = useEngineRun(runId);
const r = q.data;
return (
!o && onClose()}
width="lg"
title={r ? runTitle(r) : entry ? logTitle(entry) : "任务"}
description={r ? : undefined}
footer={
r ? (
<>
{entry ? : null}
>
) : null
}
>
{q.isPending ? (
) : q.isError ? (
q.refetch()} />
) : r ? (
) : null}
);
}
/**
* 日志详情抽屉。open = 日志 id(:<原始 id>);任务类只要 id 就能打开,其余要在已加载的列表里找到这条。
*/
export function LogDetailSheet({
logId,
entry,
onOpenLog,
onClose,
}: {
logId: string | null;
entry: LogEntry | null;
onOpenLog: (id: string) => void;
onClose: () => void;
}) {
if (!logId) return null;
const sep = logId.indexOf(":");
const source = logId.slice(0, sep);
const refId = logId.slice(sep + 1);
if (source === "engine_run")
return onOpenLog(`engine_run:${id}`)} onClose={onClose} />;
const badge = entry ? logBadge(entry) : null;
return (
!o && onClose()}
title={entry ? logTitle(entry) : "日志"}
description={
entry ? (
{badge ? {badge.label} : null}
{LOG_TYPE_LABEL[entry.type]}
· {formatDateTime(entry.at)}
{entry.object ? · {objectText(entry.object)} : null}
) : undefined
}
footer={entry ? : null}
>
{!entry ? (
) : entry.source === "schedule_run" ? (
) : entry.source === "agent_run" ? (
) : (
)}
);
}