File size: 3,276 Bytes
0b9dc2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/* eslint-disable react-refresh/only-export-components -- renderer constant is co-located with its inline component by design */
import { useState } from 'react';

import { CornerLine, ToolStateIcon } from './_shared';
import type { TFunction, ToolCallWithResult, ToolRenderer } from './types';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';

function parseInput(input: string): Record<string, unknown> {
	try {
		return JSON.parse(input);
	} catch {
		return {};
	}
}

/**

 * Collapsed: "TaskCreate  Created 3 tasks ..."

 * Expanded:

 *   TaskCreate  Created 3 tasks

 *     Research duck facts

 *       Find five interesting facts about ducks

 *     Write summary

 *       Compile the research into a brief summary

 *     Review code

 *       Check for bugs and style issues

 */
function TaskCreateGroup({ calls, t }: { calls: ToolCallWithResult[]; t: TFunction }) {
	const [open, setOpen] = useState(false);

	return (
		<Collapsible open={open} onOpenChange={setOpen} className="flex flex-col w-full">

			<CollapsibleTrigger className="flex flex-row gap-x-2 w-full max-w-full items-center cursor-pointer text-left">

				<ToolStateIcon states={calls.map((c) => c.result?.state)} />

				<span className="text-sm flex-1 min-w-0 truncate">

					<strong className="text-primary">{t('tool.taskCreate.label')}</strong>{' '}

					{t('tool.taskCreate.count', { count: calls.length })}

					{!open && <span className="text-muted-foreground"> ...</span>}

				</span>

			</CollapsibleTrigger>

			<CollapsibleContent className="pl-6 pt-2 flex flex-col gap-y-2 text-sm">

				{calls.map(({ call, result }) => {

					const input = parseInput(call.input);

					const subject = (input.subject as string) || '(untitled)';

					const description = (input.description as string) || '';

					// Extract the numeric task id from result text:

					// "Task (id=3) created successfully: ..."

					const resultText =

						typeof result?.output === 'string'

							? result.output

							: Array.isArray(result?.output)

								? result.output.map((b) => ('text' in b ? b.text : '')).join('')

								: '';

					const idMatch = resultText.match(/^Task \(id=(\d+)\)/);

					const taskId = idMatch ? idMatch[1] : null;

					return (

						<div key={call.id} className="flex flex-col min-w-0">

							<span className="text-xs break-all font-medium">

								{taskId && (

									<span className="text-muted-foreground font-mono">

										#{taskId}

									</span>

								)}

								{taskId && ' '}

								{subject}

							</span>

							{description && (

								<div className="flex flex-row gap-x-2 items-start pl-2 text-xs">

									<CornerLine />

									<span className="text-muted-foreground break-all">

										{description}

									</span>

								</div>

							)}

						</div>

					);

				})}

			</CollapsibleContent>

		</Collapsible>
	);
}

export const TaskCreateRenderer: ToolRenderer = {
	getDisplayName: (_call, t) => t('tool.taskCreate.name'),

	renderCallArgs: (call) => {
		const input = parseInput(call.input);
		return (input.subject as string) || '';
	},

	renderGroup: (calls, t) => <TaskCreateGroup calls={calls} t={t} />,
};