| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- import { ChevronRight, ExternalLink } from "lucide-react";
- import type {
- InspectorDetailProjection,
- RetrievalActivity,
- RetrievalOutcome,
- RetrievalResultItem,
- } from "@/lib/types";
- import { RichText } from "@/components/ui/RichText";
- import { ValueView } from "@/components/ui/ValueView";
- const STATE_LABELS: Record<string, string> = {
- hit: "查询命中",
- empty: "查询成功 · 无匹配",
- failure: "查询失败",
- running: "查询中",
- interrupted: "构建中断",
- unknown: "状态未知",
- success: "已完成",
- };
- export function RetrievalDetailRenderer({
- projection,
- onNavigate,
- }: {
- projection: InspectorDetailProjection;
- onNavigate?: (activity: RetrievalActivity) => void;
- }) {
- if (projection.detailKind === "retrieval-event") {
- return <RetrievalEvent projection={projection} />;
- }
- return <RetrievalActivityGroup projection={projection} onNavigate={onNavigate} />;
- }
- function RetrievalActivityGroup({
- projection,
- onNavigate,
- }: {
- projection: InspectorDetailProjection;
- onNavigate?: (activity: RetrievalActivity) => void;
- }) {
- const activities = projection.activities || [];
- return <div className="retrievalInspector">
- {(projection.businessSections || []).map((section) => <section className={`inspectorPanel is-${section.variant || "default"}`} key={section.id}>
- <h3>{section.title}</h3>
- {section.content ? <RichText value={section.content} /> : null}
- </section>)}
- <section className="inspectorPanel retrievalActivityPanel">
- <div className="retrievalSectionHeader"><h3>{projection.retrievalKind === "direct-tools" ? "每次工具调用" : "查询记录"}</h3><span>{activities.length} 次</span></div>
- {activities.length ? <ol className="retrievalActivityList">
- {activities.map((activity, index) => <li key={`${activity.id}-${index}`}>
- <button type="button" onClick={() => onNavigate?.(activity)} disabled={!onNavigate || !activity.detailRef}>
- <span className="retrievalActivityIndex">{String(index + 1).padStart(2, "0")}</span>
- <span className="retrievalActivityCopy"><strong>{activity.label}</strong>{activity.summary ? <small>{activity.summary}</small> : null}</span>
- <span className={`retrievalActivityStatus is-${activity.status}`}>{activity.resultCount != null && activity.resultCount > 0 ? `${activity.resultCount} 条` : STATE_LABELS[activity.status] || "查看详情"}</span>
- <ChevronRight aria-hidden="true" size={16} />
- </button>
- </li>)}
- </ol> : <p className="retrievalMissingNotice">{projection.missingActivityNotice || "本次运行没有保存可下钻的结构化查询记录。"}</p>}
- </section>
- </div>;
- }
- function RetrievalEvent({ projection }: { projection: InspectorDetailProjection }) {
- const outcome = projection.outcome || { state: "unknown", message: "现有记录不足以可靠判断查询结果。" };
- const conditions = projection.queryConditions || [];
- const items = projection.items || [];
- const completeness = projection.completeness;
- return <div className="retrievalInspector retrievalEventDetail">
- <OutcomePanel outcome={outcome} />
- <section className="inspectorPanel retrievalConditionPanel">
- <h3>{projection.sourceKind === "direct-tool" ? "调用输入" : "查询条件"}</h3>
- {conditions.length ? <dl className="retrievalConditions">
- {conditions.map((condition) => <div key={condition.key}><dt>{condition.label}</dt><dd><StructuredValue value={condition.value} /></dd></div>)}
- </dl> : <p className="retrievalMissingNotice">本次调用没有保存额外输入条件。</p>}
- </section>
- {items.length ? <section className="inspectorPanel retrievalResultsPanel">
- <div className="retrievalSectionHeader"><h3>{projection.sourceKind === "direct-tool" ? "读取结果" : "返回结果"}</h3><span>{items.length} 项</span></div>
- <div className="retrievalResultList">{items.map((item, index) => <ResultDisclosure key={`${item.id}-${index}`} item={item} defaultOpen={items.length === 1} />)}</div>
- </section> : null}
- {!completeness?.bodyAvailable ? <p className="retrievalCompletenessNotice is-warning">仅保存了摘要,完整返回不可用。</p> : null}
- {completeness?.truncated ? <p className="retrievalCompletenessNotice is-warning">返回内容超过安全展示上限,已省略 {completeness.omittedCharacters || "部分"} 个字符。</p> : null}
- </div>;
- }
- function OutcomePanel({ outcome }: { outcome: RetrievalOutcome }) {
- return <section className={`retrievalOutcome is-${outcome.state}`} aria-live="polite">
- <span>{STATE_LABELS[outcome.state] || "查询状态"}</span>
- <strong>{outcome.message}</strong>
- </section>;
- }
- function ResultDisclosure({ item, defaultOpen }: { item: RetrievalResultItem; defaultOpen: boolean }) {
- const safeHref = typeof item.href === "string" && /^https?:\/\//i.test(item.href) ? item.href : undefined;
- return <details className="retrievalResult" open={defaultOpen || undefined}>
- <summary>
- <span className="retrievalResultChevron"><ChevronRight aria-hidden="true" size={17} /></span>
- <span className="retrievalResultHeading"><strong>{item.title}</strong>{item.subtitle ? <small>{item.subtitle}</small> : null}</span>
- {item.meta?.length ? <span className="retrievalResultMeta">{item.meta.map((entry) => <b key={entry.label}>{entry.label}:{scalarText(entry.value)}</b>)}</span> : null}
- </summary>
- <div className="retrievalResultBody">
- {safeHref ? <a className="retrievalResultLink" href={safeHref} target="_blank" rel="noreferrer">查看原始内容<ExternalLink aria-hidden="true" size={14} /></a> : null}
- {item.sections.map((section, index) => <section className="retrievalResultSection" key={`${section.title}-${index}`}><h4>{section.title}</h4><StructuredValue value={section.value} /></section>)}
- </div>
- </details>;
- }
- function StructuredValue({ value }: { value: unknown }) {
- if (typeof value === "string") return <RichText value={value} />;
- if (value == null) return <span className="retrievalEmptyValue">未记录</span>;
- return <ValueView value={value} />;
- }
- function scalarText(value: unknown) {
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
- return "已记录";
- }
|