Files
tutti/packages/agent/gui/agent-message-center/workspaceAgentMessageCenterViewModel.ts
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

436 lines
12 KiB
TypeScript

import type { TranslateFn } from "../i18n/index";
import { workspaceAgentProviderLabel } from "../shared/workspaceAgentProviderLabel";
import {
isWaitingMessageCenterItem,
type WorkspaceAgentMessageCenterIdentity,
type WorkspaceAgentMessageCenterItem,
type WorkspaceAgentMessageCenterModel
} from "./workspaceAgentMessageCenterModel";
export type MessageCenterGroupBy = "priority" | "status" | "agent" | "time";
export type MessageCenterStatusFilter =
| "waiting"
| "working"
| "completed"
| "failed";
export type MessageCenterTranslate = TranslateFn;
export interface MessageCenterStatusOption {
count: number;
label: string;
value: MessageCenterStatusFilter;
}
export interface MessageCenterProviderOption {
count: number;
label: string;
value: string;
}
export interface MessageCenterGroup {
id: string;
identity?: WorkspaceAgentMessageCenterIdentity | null;
label: string;
items: WorkspaceAgentMessageCenterItem[];
provider?: string;
userId?: string | null;
}
export interface MessageCenterAgentUserStack {
id: string;
provider: string;
userId: string | null;
items: WorkspaceAgentMessageCenterItem[];
}
export function partitionMessageCenterItemsByAgentUser(
items: readonly WorkspaceAgentMessageCenterItem[]
): MessageCenterAgentUserStack[] {
const stacks = new Map<string, MessageCenterAgentUserStack>();
for (const item of items) {
const stackId = messageCenterAgentUserStackId(item);
const stack = stacks.get(stackId);
if (stack) {
stack.items.push(item);
} else {
stacks.set(stackId, {
id: stackId,
provider: item.provider,
userId: item.userId,
items: [item]
});
}
}
return [...stacks.values()];
}
export function messageCenterAgentUserStackId(
item: Pick<WorkspaceAgentMessageCenterItem, "provider" | "userId">
): string {
const provider = item.provider.trim().toLowerCase() || "unknown-agent";
const userId = item.userId?.trim() || "unknown-user";
return `agent-user:${provider}:${userId}`;
}
export function messageCenterStackRenderId(
groupId: string,
stackId: string
): string {
return groupId === stackId ? stackId : `${groupId}:${stackId}`;
}
export function messageCenterStackScrollSyncSegment({
expanded,
groupId,
stack
}: {
expanded: boolean;
groupId: string;
stack: MessageCenterAgentUserStack;
}): string {
const stackId = messageCenterStackRenderId(groupId, stack.id);
if (expanded) {
return `expanded:${stackId}:${stack.items.map((item) => item.id).join("|")}`;
}
return `collapsed:${stackId}:${messageCenterCollapsedStackSignature(
stack.items
)}`;
}
function messageCenterCollapsedStackSignature(
items: readonly WorkspaceAgentMessageCenterItem[]
): string {
const firstItem = items[0];
if (!firstItem) {
return "0";
}
const hasWaiting = items.some(isWaitingMessageCenterItem) ? "1" : "0";
const summary =
firstItem.digest.primary.summary.trim() ||
firstItem.lastAgentMessageSummary.trim() ||
firstItem.title.trim();
return [
items.length,
firstItem.id,
firstItem.provider,
firstItem.userId ?? "",
firstItem.identity?.userName ?? "",
firstItem.identity?.agentName ?? "",
hasWaiting,
summary
].join(":");
}
export function buildMessageCenterStatusOptions(
counts: WorkspaceAgentMessageCenterModel["counts"],
t: MessageCenterTranslate
): MessageCenterStatusOption[] {
return [
{
count: counts.waiting,
label: t("agentHost.workspaceAgentMessageCenterFilterWaiting"),
value: "waiting"
},
{
count: counts.failed,
label: t("agentHost.workspaceAgentMessageCenterFilterFailed"),
value: "failed"
},
{
count: counts.working,
label: t("agentHost.workspaceAgentMessageCenterFilterWorking"),
value: "working"
},
{
count: counts.completed,
label: t("agentHost.workspaceAgentMessageCenterFilterCompleted"),
value: "completed"
}
];
}
export function buildMessageCenterProviderOptions(
items: readonly WorkspaceAgentMessageCenterItem[]
): MessageCenterProviderOption[] {
const counts = new Map<string, { count: number; label: string }>();
for (const item of items) {
const current = counts.get(item.provider);
counts.set(item.provider, {
count: (current?.count ?? 0) + 1,
label:
current?.label ||
item.agentName?.trim() ||
workspaceAgentProviderLabel(item.provider)
});
}
return [...counts.entries()]
.map(([value, option]) => ({
count: option.count,
label: option.label,
value
}))
.sort((left, right) => left.label.localeCompare(right.label));
}
export function itemMatchesViewFilters({
item,
providerFilters,
statusFilters
}: {
item: WorkspaceAgentMessageCenterItem;
providerFilters: Set<string> | null;
statusFilters: Set<MessageCenterStatusFilter> | null;
}): boolean {
if (
statusFilters &&
!statusFilters.has(messageCenterStatusFilterValue(item))
) {
return false;
}
if (providerFilters && !providerFilters.has(item.provider)) {
return false;
}
return true;
}
export function messageCenterStatusFilterValue(
item: WorkspaceAgentMessageCenterItem
): MessageCenterStatusFilter {
if (isWaitingMessageCenterItem(item)) {
return "waiting";
}
if (item.status === "failed") {
return "failed";
}
if (item.status === "working") {
return "working";
}
return "completed";
}
export function groupMessageCenterItems(
items: readonly WorkspaceAgentMessageCenterItem[],
groupBy: MessageCenterGroupBy,
t: MessageCenterTranslate
): MessageCenterGroup[] {
switch (groupBy) {
case "status":
return groupByFixedDefinitions(items, [
{
id: "waiting",
label: t("agentHost.workspaceAgentMessageCenterFilterWaiting"),
match: (item) => messageCenterStatusFilterValue(item) === "waiting"
},
{
id: "failed",
label: t("agentHost.workspaceAgentMessageCenterFilterFailed"),
match: (item) => messageCenterStatusFilterValue(item) === "failed"
},
{
id: "working",
label: t("agentHost.workspaceAgentMessageCenterFilterWorking"),
match: (item) => messageCenterStatusFilterValue(item) === "working"
},
{
id: "completed",
label: t("agentHost.workspaceAgentMessageCenterFilterCompleted"),
match: (item) => messageCenterStatusFilterValue(item) === "completed"
}
]);
case "agent":
return groupByDynamicKey(items, (item) => ({
id: messageCenterAgentUserStackId(item),
identity: item.identity,
label: messageCenterAgentUserGroupLabel(item),
provider: item.provider,
userId: item.userId
}));
case "time":
return groupByFixedDefinitions(items, [
{
id: "today",
label: t("agentHost.workspaceAgentMessageCenterGroupToday"),
match: (item) => messageCenterTimeGroup(item) === "today"
},
{
id: "yesterday",
label: t("agentHost.workspaceAgentMessageCenterGroupYesterday"),
match: (item) => messageCenterTimeGroup(item) === "yesterday"
},
{
id: "previous-seven-days",
label: t(
"agentHost.workspaceAgentMessageCenterGroupPreviousSevenDays"
),
match: (item) =>
messageCenterTimeGroup(item) === "previous-seven-days"
},
{
id: "older",
label: t("agentHost.workspaceAgentMessageCenterGroupOlder"),
match: (item) => messageCenterTimeGroup(item) === "older"
}
]);
case "priority":
default: {
const nowUnixMs = Date.now();
return groupByFixedDefinitions(items, [
{
id: "needs-attention",
label: t("agentHost.workspaceAgentMessageCenterGroupNeedsAttention"),
match: (item) => messageCenterStatusFilterValue(item) === "waiting"
},
{
id: "failed",
label: t("agentHost.workspaceAgentMessageCenterFilterFailed"),
match: (item) => messageCenterStatusFilterValue(item) === "failed"
},
{
id: "working",
label: t("agentHost.workspaceAgentMessageCenterFilterWorking"),
match: (item) => messageCenterStatusFilterValue(item) === "working"
},
{
id: "recently-completed",
label: t(
"agentHost.workspaceAgentMessageCenterGroupRecentlyCompleted"
),
match: (item) => isRecentlyCompletedMessageCenterItem(item, nowUnixMs)
},
{
id: "completed",
label: t("agentHost.workspaceAgentMessageCenterFilterCompleted"),
match: (item) =>
messageCenterStatusFilterValue(item) === "completed" &&
!isRecentlyCompletedMessageCenterItem(item, nowUnixMs)
}
]);
}
}
}
function messageCenterAgentUserGroupLabel(
item: WorkspaceAgentMessageCenterItem
): string {
if (item.identity) {
return `${item.identity.userName} & ${item.identity.agentName}`;
}
return item.agentName?.trim() || workspaceAgentProviderLabel(item.provider);
}
const RECENTLY_COMPLETED_WINDOW_MS = 10 * 60 * 1000;
export function isRecentlyCompletedMessageCenterItem(
item: WorkspaceAgentMessageCenterItem,
nowUnixMs: number
): boolean {
if (messageCenterStatusFilterValue(item) !== "completed") {
return false;
}
if (item.imported) {
return false;
}
const completedAtUnixMs =
item.sortTimeUnixMs || item.lastAgentMessageAtUnixMs || 0;
if (completedAtUnixMs <= 0) {
return false;
}
return completedAtUnixMs >= nowUnixMs - RECENTLY_COMPLETED_WINDOW_MS;
}
export function messageCenterGroupLabel(
groupBy: MessageCenterGroupBy,
t: MessageCenterTranslate
): string {
switch (groupBy) {
case "status":
return t("agentHost.workspaceAgentMessageCenterGroupStatus");
case "agent":
return t("agentHost.workspaceAgentMessageCenterGroupAgent");
case "time":
return t("agentHost.workspaceAgentMessageCenterGroupTime");
case "priority":
default:
return t("agentHost.workspaceAgentMessageCenterGroupPriority");
}
}
export function statusFilterSummary(
statusFilters: Set<MessageCenterStatusFilter> | null,
statusOptions: readonly MessageCenterStatusOption[]
): string {
if (statusFilters === null || statusFilters.size === statusOptions.length) {
return statusOptions.map((option) => option.label).join(", ");
}
return statusOptions
.filter((option) => statusFilters.has(option.value))
.map((option) => option.label)
.join(", ");
}
function groupByFixedDefinitions(
items: readonly WorkspaceAgentMessageCenterItem[],
definitions: Array<{
id: string;
label: string;
match: (item: WorkspaceAgentMessageCenterItem) => boolean;
}>
): MessageCenterGroup[] {
return definitions
.map((definition) => ({
id: definition.id,
label: definition.label,
items: items.filter(definition.match)
}))
.filter((group) => group.items.length > 0);
}
function groupByDynamicKey(
items: readonly WorkspaceAgentMessageCenterItem[],
keyForItem: (item: WorkspaceAgentMessageCenterItem) => {
id: string;
identity?: WorkspaceAgentMessageCenterIdentity | null;
label: string;
provider?: string;
userId?: string | null;
}
): MessageCenterGroup[] {
const groups = new Map<string, MessageCenterGroup>();
for (const item of items) {
const key = keyForItem(item);
const group = groups.get(key.id);
if (group) {
group.items.push(item);
if (!group.identity && key.identity) {
group.identity = key.identity;
group.label = key.label;
}
} else {
groups.set(key.id, { ...key, items: [item] });
}
}
return [...groups.values()];
}
function messageCenterTimeGroup(
item: WorkspaceAgentMessageCenterItem
): "older" | "previous-seven-days" | "today" | "yesterday" {
const timestamp = item.sortTimeUnixMs || item.lastAgentMessageAtUnixMs || 0;
if (timestamp <= 0) {
return "older";
}
const ageMs = Date.now() - timestamp;
const dayMs = 24 * 60 * 60 * 1000;
if (ageMs < dayMs) {
return "today";
}
if (ageMs < dayMs * 2) {
return "yesterday";
}
if (ageMs < dayMs * 7) {
return "previous-seven-days";
}
return "older";
}