92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import { Badge } from '@/components/ui/badge';
|
|
import { CheckCircleIcon, CircleIcon, ClockIcon, XCircleIcon } from 'lucide-react';
|
|
import type { ReactNode } from 'react';
|
|
import { createElement } from 'react';
|
|
|
|
/**
|
|
* Map SSE status strings to i18n keys under `actions.status.*`
|
|
*/
|
|
const statusKeyMap: Record<string, string> = {
|
|
'input-streaming': 'inputStreaming',
|
|
'input-available': 'inputAvailable',
|
|
'output-available': 'outputAvailable',
|
|
'output-error': 'outputError',
|
|
'output-denied': 'outputDenied',
|
|
running: 'running',
|
|
result: 'result',
|
|
error: 'error',
|
|
};
|
|
|
|
/**
|
|
* Resolve an action name to its i18n display name.
|
|
* Falls back to the raw actionName if no translation exists.
|
|
*/
|
|
export function getActionDisplayName(t: (key: string) => string, actionName: string): string {
|
|
const translated = t(`actions.names.${actionName}`);
|
|
// t() returns the key itself when translation is missing
|
|
return translated === `actions.names.${actionName}` ? actionName : translated;
|
|
}
|
|
|
|
/**
|
|
* Get a localized status badge for action state.
|
|
*/
|
|
export function getStatusBadge(t: (key: string) => string, state: string): ReactNode {
|
|
const iconMap: Record<string, ReactNode> = {
|
|
'input-streaming': createElement(CircleIcon, { className: 'size-4' }),
|
|
'input-available': createElement(ClockIcon, {
|
|
className: 'size-4 animate-pulse',
|
|
}),
|
|
'output-available': createElement(CheckCircleIcon, {
|
|
className: 'size-4 text-green-600',
|
|
}),
|
|
'output-error': createElement(XCircleIcon, {
|
|
className: 'size-4 text-red-600',
|
|
}),
|
|
'output-denied': createElement(XCircleIcon, {
|
|
className: 'size-4 text-orange-600',
|
|
}),
|
|
running: createElement(ClockIcon, { className: 'size-4 animate-pulse' }),
|
|
result: createElement(CheckCircleIcon, {
|
|
className: 'size-4 text-green-600',
|
|
}),
|
|
error: createElement(XCircleIcon, { className: 'size-4 text-red-600' }),
|
|
};
|
|
|
|
const i18nKey = statusKeyMap[state];
|
|
const label = i18nKey ? t(`actions.status.${i18nKey}`) : state;
|
|
|
|
return createElement(
|
|
Badge,
|
|
{
|
|
className: 'gap-1.5 rounded-full text-xs',
|
|
variant: 'secondary' as const,
|
|
},
|
|
iconMap[state] || createElement(CircleIcon, { className: 'size-4' }),
|
|
label,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract text parts from a message
|
|
*/
|
|
export function getMessageTextParts(message: {
|
|
parts?: Array<{ type: string; text?: string; [key: string]: unknown }>;
|
|
}) {
|
|
if (!message.parts || message.parts.length === 0) {
|
|
return [];
|
|
}
|
|
return message.parts.filter((part) => part.type === 'text' || part.type === 'step-start');
|
|
}
|
|
|
|
/**
|
|
* Extract action parts from a message
|
|
*/
|
|
export function getMessageActionParts(message: {
|
|
parts?: Array<{ type: string; [key: string]: unknown }>;
|
|
}) {
|
|
if (!message.parts || message.parts.length === 0) {
|
|
return [];
|
|
}
|
|
return message.parts.filter((part) => part.type && part.type.startsWith('action-'));
|
|
}
|