Files
tutti/packages/agent/gui/agent-message-center/workspaceAgentMessageCenterPreview.tsx
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

240 lines
7.2 KiB
TypeScript

import type { ReactElement, ReactNode } from "react";
import { AppWindow } from "lucide-react";
import {
isRichTextMentionHref,
parseRichTextMentionHref
} from "@tutti-os/ui-rich-text/core";
import { getActiveUiLanguage, useTranslation } from "../i18n/index";
import { formatAgentSessionMentionText } from "../shared/utils/agentSessionMentionText";
import { workspaceAgentProviderLabel } from "../shared/workspaceAgentProviderLabel";
import type { WorkspaceAgentMessageCenterItem } from "./workspaceAgentMessageCenterModel";
function messageCenterStackRawPreviewText(
item: WorkspaceAgentMessageCenterItem
): string {
return (
item.digest.primary.summary.trim() ||
item.lastAgentMessageSummary.trim() ||
item.title
);
}
export function messageCenterStackPreviewText(
item: WorkspaceAgentMessageCenterItem
): string {
return formatAgentSessionMentionText(messageCenterStackRawPreviewText(item), {
language: getActiveUiLanguage()
});
}
const MESSAGE_CENTER_PREVIEW_MARKDOWN_LINK_PATTERN =
/\[((?:\\.|[^\]\\])*)\]\(([^)\s]+)\)/g;
const MESSAGE_CENTER_PREVIEW_LABEL_ESCAPE_PATTERN = /\\([\\[\]()])/g;
type MessageCenterPreviewMentionKind =
| "session"
| "workspace-app"
| "workspace-issue";
/**
* 收起态预览只展示纯文本 + 一个静态(不可点击)的 mention 图标,复用
* AgentMessageMarkdown 里那套富文本 chip 的视觉样式,但不渲染成 <a>——
* 这块预览本身嵌套在外层切换展开/收起的 <button> 里,塞一个可点击链接
* 会出现交互元素嵌套交互元素的问题。
*/
export function messageCenterStackPreviewNodes(
item: WorkspaceAgentMessageCenterItem
): ReactNode[] {
const text = messageCenterStackRawPreviewText(item);
const nodes: ReactNode[] = [];
let lastIndex = 0;
let mentionIndex = 0;
for (const match of text.matchAll(
MESSAGE_CENTER_PREVIEW_MARKDOWN_LINK_PATTERN
)) {
const [fullMatch, rawLabel = "", href = ""] = match;
const matchStart = match.index ?? 0;
if (matchStart > lastIndex) {
nodes.push(text.slice(lastIndex, matchStart));
}
const label = rawLabel.replace(
MESSAGE_CENTER_PREVIEW_LABEL_ESCAPE_PATTERN,
"$1"
);
const mention = parseRichTextMentionHref(href, label);
if (!mention) {
nodes.push(isRichTextMentionHref(href) ? label : fullMatch);
} else {
const kind = messageCenterPreviewMentionKind(mention.providerId);
const displayLabel = label || mention.label;
nodes.push(
kind ? (
<MessageCenterPreviewMentionChip
key={`mention-${mentionIndex}`}
kind={kind}
label={displayLabel}
/>
) : (
displayLabel
)
);
mentionIndex += 1;
}
lastIndex = matchStart + fullMatch.length;
}
if (lastIndex < text.length) {
nodes.push(text.slice(lastIndex));
}
return nodes;
}
function messageCenterPreviewMentionKind(
providerId: string
): MessageCenterPreviewMentionKind | null {
switch (providerId.trim().toLowerCase()) {
case "agent-session":
return "session";
case "workspace-app":
return "workspace-app";
case "workspace-issue":
return "workspace-issue";
default:
return null;
}
}
function MessageCenterPreviewMentionChip({
kind,
label
}: {
kind: MessageCenterPreviewMentionKind;
label: string;
}): ReactElement {
return (
<span
className="tsh-agent-object-token tsh-agent-object-token--entity"
data-agent-mention-kind={kind}
>
<span className="tsh-agent-object-token__kind" aria-hidden="true">
{kind === "workspace-app" ? (
<AppWindow className="size-3.5" aria-hidden="true" />
) : (
<span
className="tsh-agent-object-token__kind-icon"
aria-hidden="true"
/>
)}
</span>
<span className="tsh-agent-object-token__main">{label}</span>
</span>
);
}
export function resolveMessageCenterNotificationAction(
item: WorkspaceAgentMessageCenterItem,
input: {
action?: string;
optionId?: string;
}
): string | null {
if (input.action) {
return normalizeMessageCenterNotificationAction(input.action);
}
if (!input.optionId) {
return null;
}
const prompt = item.pendingPrompt;
const option =
prompt && "options" in prompt
? prompt.options.find((candidate) => candidate.id === input.optionId)
: null;
const optionToken = `${option?.kind ?? ""}:${input.optionId}`.toLowerCase();
if (optionToken.includes("allow") || optionToken.includes("accept")) {
return "accept";
}
if (
optionToken.includes("deny") ||
optionToken.includes("reject") ||
optionToken.includes("disallow")
) {
return "reject";
}
return input.optionId;
}
export function buildWorkspaceAgentInteractivePromptLabels(
t: ReturnType<typeof useTranslation>["t"],
provider?: string
) {
return {
approvalLead: t("agentHost.agentGui.approvalRequired", {
provider: provider
? workspaceAgentProviderLabel(provider)
: t("agentHost.workspaceAgentsGenericAgentName")
}),
fileChangeApprovalLead: t("agentHost.agentGui.fileChangeApprovalRequired", {
provider: provider
? workspaceAgentProviderLabel(provider)
: t("agentHost.workspaceAgentsGenericAgentName")
}),
planLead: t("agentHost.agentGui.planLead"),
planModes: [
{
id: "acceptEdits",
label: t("agentHost.agentGui.planModes.acceptEdits.label"),
description: t("agentHost.agentGui.planModes.acceptEdits.description")
},
{
id: "default",
label: t("agentHost.agentGui.planModes.askFirst.label"),
description: t("agentHost.agentGui.planModes.askFirst.description")
},
{
id: "bypassPermissions",
label: t("agentHost.agentGui.planModes.allowAll.label"),
description: t("agentHost.agentGui.planModes.allowAll.description")
},
{
id: "auto",
label: t("agentHost.agentGui.planModes.auto.label"),
description: t("agentHost.agentGui.planModes.auto.description")
}
],
stayInPlan: t("agentHost.agentGui.stayInPlan"),
sendFeedback: t("agentHost.agentGui.sendFeedback"),
feedbackPlaceholder: t("agentHost.agentGui.feedbackPlaceholder"),
previousQuestion: t("agentHost.agentGui.previousQuestion"),
nextQuestion: t("agentHost.agentGui.nextQuestion"),
submitAnswers: t("agentHost.agentGui.submitAnswers"),
answerPlaceholder: t("agentHost.agentGui.answerPlaceholder"),
waitingForAnswer: t("agentHost.agentGui.waitingForAnswer"),
planImplementationLead: t("agentHost.agentGui.planImplementationLead"),
planImplementationConfirm: t(
"agentHost.agentGui.planImplementationConfirm"
),
planImplementationFeedbackPlaceholder: t(
"agentHost.agentGui.planImplementationFeedbackPlaceholder"
),
planImplementationSend: t("agentHost.agentGui.planImplementationSend"),
planImplementationSkip: t("agentHost.agentGui.planImplementationSkip")
};
}
function normalizeMessageCenterNotificationAction(action: string): string {
switch (action) {
case "allow":
return "accept";
case "deny":
return "reject";
default:
return action;
}
}