Z ZPI 文档 v0.3.8
介绍页

源码

packages/ui/src/components/conversation-find.ts

typescript135 行查看原文
import { useEffect, useMemo, useState } from "react";
import { parseSelectionPrompt } from "../conversation-selections.ts";
import type { FindTarget } from "../find.ts";
import type { LinkContext } from "../link-target.ts";
import type { RunView } from "../types.ts";
import { createMarkdownFindText } from "./markdown-find.ts";
import { runPresentation } from "./process-presentation.ts";
import { messageReferences } from "./Reference.tsx";

const emptyTargets: readonly FindTarget[] = [];

export function createConversationFindIndex(
  workspace: LinkContext | undefined,
  commandNames: readonly string[],
  agentNames: readonly string[],
) {
  let markdown: ReturnType<typeof createMarkdownFindText> | undefined;
  const cache = new WeakMap<RunView, readonly FindTarget[]>();
  const userMessageText = (message: string, paths?: readonly string[]): string => {
    const user = parseSelectionPrompt(message).text;
    const references = messageReferences(user, paths, commandNames, agentNames);
    let text = "",
      from = 0;
    for (const mention of references.inline) {
      text += user.slice(from, mention.start) + mention.label;
      from = mention.end;
    }
    return text + user.slice(from) + references.trailing.map((reference) => reference.label).join("");
  };
  const read = (run: RunView): readonly FindTarget[] => {
    const cached = cache.get(run);
    if (cached) return cached;
    const targets: FindTarget[] = [];
    if (run.kind !== "compact") {
      if (run.subagentNotification)
        targets.push({
          key: `${run.runId}:user`,
          text: run.subagentNotification.description,
          runId: run.runId,
        });
      if (!run.subagentNotification) {
        targets.push({
          key: `${run.runId}:user`,
          text: userMessageText(run.userMessage, run.fileReferences),
          runId: run.runId,
        });
      }
      for (const guide of run.guides ?? []) {
        targets.push({
          key: `${run.runId}:guide:${guide.id}`,
          text: userMessageText(guide.text, guide.fileReferences),
          runId: run.runId,
        });
      }
      const { answer } = runPresentation(run);
      for (const block of run.orderedBlocks) {
        if (block.type !== "text") continue;
        let text: string | undefined;
        if (run.status !== "running") {
          markdown ??= createMarkdownFindText(workspace);
          text = markdown(block.text);
        }
        targets.push({
          key: block === answer ? `${run.runId}:answer` : block.id,
          text: text ?? "",
          runId: run.runId,
          rendered: text === undefined,
        });
      }
    }
    cache.set(run, targets);
    return targets;
  };
  return { read, peek: (run: RunView) => cache.get(run) };
}

/** Index saved history in short tasks; cancelling a query/session never finishes the old queue. */
export function useConversationFindTargets({
  runs,
  active,
  sessionId,
  workspace,
  commandNames,
  agentNames,
}: {
  runs: RunView[];
  active: boolean;
  sessionId: string;
  workspace?: LinkContext;
  commandNames: readonly string[];
  agentNames: readonly string[];
}) {
  const commands = JSON.stringify(commandNames),
    agents = JSON.stringify(agentNames);
  const index = useMemo(
    () => createConversationFindIndex(workspace, JSON.parse(commands), JSON.parse(agents)),
    [sessionId, workspace?.cwd, workspace?.home, workspace?.documentPath, commands, agents],
  );
  const [state, setState] = useState<{ sessionId: string; index: typeof index; targets: FindTarget[] }>();
  useEffect(() => {
    if (!active) {
      setState(undefined);
      return;
    }
    let cursor = 0;
    const slots = runs.map((run) => index.peek(run));
    const pending = runs.flatMap((_, i) => (slots[i] ? [] : [i]));
    const step = () => {
      const start = performance.now();
      let count = 0;
      while (cursor < pending.length && count++ < 4) {
        const i = pending[cursor++];
        slots[i] = index.read(runs[i]);
        if (performance.now() - start >= 8) break;
      }
      const targets = slots.flatMap((targets) => targets ?? []);
      setState((current) => {
        if (current?.sessionId === sessionId && current.index !== index && cursor < pending.length)
          return current;
        if (
          current?.index === index &&
          current.targets.length === targets.length &&
          targets.every((target, i) => target === current.targets[i])
        )
          return current;
        return { sessionId, index, targets };
      });
      if (cursor < pending.length) timer = setTimeout(step, 0);
    };
    let timer = setTimeout(step, 0);
    return () => clearTimeout(timer);
  }, [index, active, runs, sessionId]);
  return active && state?.sessionId === sessionId ? state.targets : emptyTargets;
}