File size: 2,919 Bytes
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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-'));
}