Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

82 lines
2.0 KiB
TypeScript

"use client";
import type { ReactNode } from "react";
import {
AlertCircleIcon,
CheckIcon,
LoaderIcon,
XCircleIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
export type ToolCallStatusLike =
| {
type: string;
reason?: string;
}
| undefined;
export const truncate = (value: string, max = 80): string =>
value.length > max ? `${value.slice(0, max - 3)}...` : value;
export const str = (value: unknown): string =>
typeof value === "string" ? value : "";
export const basename = (filepath: string): string => {
const parts = filepath.split("/").filter(Boolean);
return parts[parts.length - 1] ?? filepath;
};
const unique = <T,>(items: readonly T[]) => [...new Set(items)];
export const getPatchInfo = (patchText: string) => {
if (!patchText) return { files: [] as string[], added: 0, removed: 0 };
const files = unique(
[...patchText.matchAll(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/gm)]
.map((match) => basename(match[1]!.trim()))
.filter(Boolean),
);
let added = 0;
let removed = 0;
for (const line of patchText.split("\n")) {
if (line.startsWith("+")) added += 1;
else if (line.startsWith("-")) removed += 1;
}
return { files, added, removed };
};
export const isCancelledToolStatus = (status?: ToolCallStatusLike) =>
status?.type === "incomplete" && status.reason === "cancelled";
export const ToolStatusIcon = ({
status,
completeIcon,
}: {
status?: ToolCallStatusLike;
completeIcon?: ReactNode;
}) => {
const statusType = status?.type ?? "complete";
const isCancelled = isCancelledToolStatus(status);
if (statusType === "running") {
return <LoaderIcon className="size-3 shrink-0 animate-spin" />;
}
if (statusType === "requires-action") {
return <AlertCircleIcon className="size-3 shrink-0 text-amber-600" />;
}
if (statusType === "incomplete") {
return (
<XCircleIcon
className={cn("size-3 shrink-0", !isCancelled && "text-destructive")}
/>
);
}
return completeIcon ?? <CheckIcon className="size-3 shrink-0" />;
};