@townco/ui 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/use-chat-messages.d.ts +50 -11
- package/dist/core/hooks/use-chat-session.d.ts +5 -5
- package/dist/core/hooks/use-tool-calls.d.ts +52 -0
- package/dist/core/hooks/use-tool-calls.js +61 -0
- package/dist/core/schemas/chat.d.ts +166 -83
- package/dist/core/schemas/chat.js +27 -27
- package/dist/core/schemas/index.d.ts +1 -0
- package/dist/core/schemas/index.js +1 -0
- package/dist/core/schemas/tool-call.d.ts +174 -0
- package/dist/core/schemas/tool-call.js +130 -0
- package/dist/core/store/chat-store.d.ts +28 -28
- package/dist/core/store/chat-store.js +123 -59
- package/dist/gui/components/ChatLayout.js +11 -10
- package/dist/gui/components/MessageContent.js +4 -1
- package/dist/gui/components/ToolCall.d.ts +8 -0
- package/dist/gui/components/ToolCall.js +100 -0
- package/dist/gui/components/ToolCallList.d.ts +9 -0
- package/dist/gui/components/ToolCallList.js +22 -0
- package/dist/gui/components/index.d.ts +2 -0
- package/dist/gui/components/index.js +2 -0
- package/dist/gui/components/resizable.d.ts +7 -0
- package/dist/gui/components/resizable.js +7 -0
- package/dist/sdk/schemas/session.d.ts +390 -220
- package/dist/sdk/schemas/session.js +74 -29
- package/dist/sdk/transports/http.js +705 -472
- package/dist/sdk/transports/stdio.js +187 -32
- package/dist/tui/components/ChatView.js +19 -51
- package/dist/tui/components/MessageList.d.ts +2 -4
- package/dist/tui/components/MessageList.js +13 -37
- package/dist/tui/components/ToolCall.d.ts +9 -0
- package/dist/tui/components/ToolCall.js +41 -0
- package/dist/tui/components/ToolCallList.d.ts +8 -0
- package/dist/tui/components/ToolCallList.js +17 -0
- package/dist/tui/components/index.d.ts +2 -0
- package/dist/tui/components/index.js +2 -0
- package/package.json +4 -2
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Tool call status lifecycle
|
|
4
|
+
*/
|
|
5
|
+
export declare const ToolCallStatusSchema: z.ZodEnum<{
|
|
6
|
+
pending: "pending";
|
|
7
|
+
in_progress: "in_progress";
|
|
8
|
+
completed: "completed";
|
|
9
|
+
failed: "failed";
|
|
10
|
+
}>;
|
|
11
|
+
export type ToolCallStatus = z.infer<typeof ToolCallStatusSchema>;
|
|
12
|
+
/**
|
|
13
|
+
* Tool call categories for UI presentation
|
|
14
|
+
*/
|
|
15
|
+
export declare const ToolCallKindSchema: z.ZodEnum<{
|
|
16
|
+
read: "read";
|
|
17
|
+
edit: "edit";
|
|
18
|
+
delete: "delete";
|
|
19
|
+
move: "move";
|
|
20
|
+
search: "search";
|
|
21
|
+
execute: "execute";
|
|
22
|
+
think: "think";
|
|
23
|
+
fetch: "fetch";
|
|
24
|
+
switch_mode: "switch_mode";
|
|
25
|
+
other: "other";
|
|
26
|
+
}>;
|
|
27
|
+
export type ToolCallKind = z.infer<typeof ToolCallKindSchema>;
|
|
28
|
+
/**
|
|
29
|
+
* File location with optional line number
|
|
30
|
+
*/
|
|
31
|
+
export declare const FileLocationSchema: z.ZodObject<{
|
|
32
|
+
path: z.ZodString;
|
|
33
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export type FileLocation = z.infer<typeof FileLocationSchema>;
|
|
36
|
+
/**
|
|
37
|
+
* Token usage metadata for tracking LLM consumption
|
|
38
|
+
*/
|
|
39
|
+
export declare const TokenUsageSchema: z.ZodObject<{
|
|
40
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type TokenUsage = z.infer<typeof TokenUsageSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Content block types for tool call results
|
|
47
|
+
*/
|
|
48
|
+
export declare const ToolCallContentBlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
49
|
+
type: z.ZodLiteral<"content">;
|
|
50
|
+
content: z.ZodObject<{
|
|
51
|
+
type: z.ZodLiteral<"text">;
|
|
52
|
+
text: z.ZodString;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
55
|
+
type: z.ZodLiteral<"text">;
|
|
56
|
+
text: z.ZodString;
|
|
57
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
58
|
+
type: z.ZodLiteral<"diff">;
|
|
59
|
+
path: z.ZodString;
|
|
60
|
+
oldText: z.ZodString;
|
|
61
|
+
newText: z.ZodString;
|
|
62
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
63
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
64
|
+
type: z.ZodLiteral<"terminal">;
|
|
65
|
+
terminalId: z.ZodString;
|
|
66
|
+
}, z.core.$strip>], "type">;
|
|
67
|
+
export type ToolCallContentBlock = z.infer<typeof ToolCallContentBlockSchema>;
|
|
68
|
+
/**
|
|
69
|
+
* Complete tool call state as displayed in the UI
|
|
70
|
+
*/
|
|
71
|
+
export declare const ToolCallSchema: z.ZodObject<{
|
|
72
|
+
id: z.ZodString;
|
|
73
|
+
title: z.ZodString;
|
|
74
|
+
kind: z.ZodEnum<{
|
|
75
|
+
read: "read";
|
|
76
|
+
edit: "edit";
|
|
77
|
+
delete: "delete";
|
|
78
|
+
move: "move";
|
|
79
|
+
search: "search";
|
|
80
|
+
execute: "execute";
|
|
81
|
+
think: "think";
|
|
82
|
+
fetch: "fetch";
|
|
83
|
+
switch_mode: "switch_mode";
|
|
84
|
+
other: "other";
|
|
85
|
+
}>;
|
|
86
|
+
status: z.ZodEnum<{
|
|
87
|
+
pending: "pending";
|
|
88
|
+
in_progress: "in_progress";
|
|
89
|
+
completed: "completed";
|
|
90
|
+
failed: "failed";
|
|
91
|
+
}>;
|
|
92
|
+
locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
93
|
+
path: z.ZodString;
|
|
94
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
95
|
+
}, z.core.$strip>>>;
|
|
96
|
+
rawInput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
97
|
+
rawOutput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
98
|
+
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
99
|
+
type: z.ZodLiteral<"content">;
|
|
100
|
+
content: z.ZodObject<{
|
|
101
|
+
type: z.ZodLiteral<"text">;
|
|
102
|
+
text: z.ZodString;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
105
|
+
type: z.ZodLiteral<"text">;
|
|
106
|
+
text: z.ZodString;
|
|
107
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
108
|
+
type: z.ZodLiteral<"diff">;
|
|
109
|
+
path: z.ZodString;
|
|
110
|
+
oldText: z.ZodString;
|
|
111
|
+
newText: z.ZodString;
|
|
112
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
113
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
114
|
+
type: z.ZodLiteral<"terminal">;
|
|
115
|
+
terminalId: z.ZodString;
|
|
116
|
+
}, z.core.$strip>], "type">>>;
|
|
117
|
+
error: z.ZodOptional<z.ZodString>;
|
|
118
|
+
startedAt: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
completedAt: z.ZodOptional<z.ZodNumber>;
|
|
120
|
+
tokenUsage: z.ZodOptional<z.ZodObject<{
|
|
121
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
122
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
123
|
+
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
124
|
+
}, z.core.$strip>>;
|
|
125
|
+
}, z.core.$strip>;
|
|
126
|
+
export type ToolCall = z.infer<typeof ToolCallSchema>;
|
|
127
|
+
/**
|
|
128
|
+
* Partial update for an existing tool call
|
|
129
|
+
*/
|
|
130
|
+
export declare const ToolCallUpdateSchema: z.ZodObject<{
|
|
131
|
+
id: z.ZodString;
|
|
132
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
133
|
+
pending: "pending";
|
|
134
|
+
in_progress: "in_progress";
|
|
135
|
+
completed: "completed";
|
|
136
|
+
failed: "failed";
|
|
137
|
+
}>>;
|
|
138
|
+
locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
139
|
+
path: z.ZodString;
|
|
140
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
141
|
+
}, z.core.$strip>>>;
|
|
142
|
+
rawOutput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
143
|
+
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
144
|
+
type: z.ZodLiteral<"content">;
|
|
145
|
+
content: z.ZodObject<{
|
|
146
|
+
type: z.ZodLiteral<"text">;
|
|
147
|
+
text: z.ZodString;
|
|
148
|
+
}, z.core.$strip>;
|
|
149
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
150
|
+
type: z.ZodLiteral<"text">;
|
|
151
|
+
text: z.ZodString;
|
|
152
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
153
|
+
type: z.ZodLiteral<"diff">;
|
|
154
|
+
path: z.ZodString;
|
|
155
|
+
oldText: z.ZodString;
|
|
156
|
+
newText: z.ZodString;
|
|
157
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
158
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
159
|
+
type: z.ZodLiteral<"terminal">;
|
|
160
|
+
terminalId: z.ZodString;
|
|
161
|
+
}, z.core.$strip>], "type">>>;
|
|
162
|
+
error: z.ZodOptional<z.ZodString>;
|
|
163
|
+
completedAt: z.ZodOptional<z.ZodNumber>;
|
|
164
|
+
tokenUsage: z.ZodOptional<z.ZodObject<{
|
|
165
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
167
|
+
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
168
|
+
}, z.core.$strip>>;
|
|
169
|
+
}, z.core.$strip>;
|
|
170
|
+
export type ToolCallUpdate = z.infer<typeof ToolCallUpdateSchema>;
|
|
171
|
+
/**
|
|
172
|
+
* Helper to merge a tool call update into an existing tool call
|
|
173
|
+
*/
|
|
174
|
+
export declare function mergeToolCallUpdate(existing: ToolCall, update: ToolCallUpdate): ToolCall;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Tool call status lifecycle
|
|
4
|
+
*/
|
|
5
|
+
export const ToolCallStatusSchema = z.enum([
|
|
6
|
+
"pending",
|
|
7
|
+
"in_progress",
|
|
8
|
+
"completed",
|
|
9
|
+
"failed",
|
|
10
|
+
]);
|
|
11
|
+
/**
|
|
12
|
+
* Tool call categories for UI presentation
|
|
13
|
+
*/
|
|
14
|
+
export const ToolCallKindSchema = z.enum([
|
|
15
|
+
"read",
|
|
16
|
+
"edit",
|
|
17
|
+
"delete",
|
|
18
|
+
"move",
|
|
19
|
+
"search",
|
|
20
|
+
"execute",
|
|
21
|
+
"think",
|
|
22
|
+
"fetch",
|
|
23
|
+
"switch_mode",
|
|
24
|
+
"other",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* File location with optional line number
|
|
28
|
+
*/
|
|
29
|
+
export const FileLocationSchema = z.object({
|
|
30
|
+
path: z.string(),
|
|
31
|
+
line: z.number().nullable().optional(),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Token usage metadata for tracking LLM consumption
|
|
35
|
+
*/
|
|
36
|
+
export const TokenUsageSchema = z.object({
|
|
37
|
+
inputTokens: z.number().optional(),
|
|
38
|
+
outputTokens: z.number().optional(),
|
|
39
|
+
totalTokens: z.number().optional(),
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Content block types for tool call results
|
|
43
|
+
*/
|
|
44
|
+
export const ToolCallContentBlockSchema = z.discriminatedUnion("type", [
|
|
45
|
+
// ACP nested content format
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("content"),
|
|
48
|
+
content: z.object({
|
|
49
|
+
type: z.literal("text"),
|
|
50
|
+
text: z.string(),
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
// Direct text block (legacy)
|
|
54
|
+
z.object({
|
|
55
|
+
type: z.literal("text"),
|
|
56
|
+
text: z.string(),
|
|
57
|
+
}),
|
|
58
|
+
z.object({
|
|
59
|
+
type: z.literal("diff"),
|
|
60
|
+
path: z.string(),
|
|
61
|
+
oldText: z.string(),
|
|
62
|
+
newText: z.string(),
|
|
63
|
+
line: z.number().nullable().optional(),
|
|
64
|
+
}),
|
|
65
|
+
z.object({
|
|
66
|
+
type: z.literal("terminal"),
|
|
67
|
+
terminalId: z.string(),
|
|
68
|
+
}),
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* Complete tool call state as displayed in the UI
|
|
72
|
+
*/
|
|
73
|
+
export const ToolCallSchema = z.object({
|
|
74
|
+
/** Unique identifier within the session */
|
|
75
|
+
id: z.string(),
|
|
76
|
+
/** Human-readable description of the operation */
|
|
77
|
+
title: z.string(),
|
|
78
|
+
/** Category for UI presentation */
|
|
79
|
+
kind: ToolCallKindSchema,
|
|
80
|
+
/** Current execution status */
|
|
81
|
+
status: ToolCallStatusSchema,
|
|
82
|
+
/** Affected file paths with optional line numbers */
|
|
83
|
+
locations: z.array(FileLocationSchema).optional(),
|
|
84
|
+
/** Raw parameters passed to the tool */
|
|
85
|
+
rawInput: z.record(z.string(), z.unknown()).optional(),
|
|
86
|
+
/** Raw response from the tool */
|
|
87
|
+
rawOutput: z.record(z.string(), z.unknown()).optional(),
|
|
88
|
+
/** Produced results (content blocks, diffs, terminal output) */
|
|
89
|
+
content: z.array(ToolCallContentBlockSchema).optional(),
|
|
90
|
+
/** Error message if status is 'failed' */
|
|
91
|
+
error: z.string().optional(),
|
|
92
|
+
/** Timestamp when the tool call started */
|
|
93
|
+
startedAt: z.number().optional(),
|
|
94
|
+
/** Timestamp when the tool call completed/failed */
|
|
95
|
+
completedAt: z.number().optional(),
|
|
96
|
+
/** Token usage metadata for this tool call */
|
|
97
|
+
tokenUsage: TokenUsageSchema.optional(),
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Partial update for an existing tool call
|
|
101
|
+
*/
|
|
102
|
+
export const ToolCallUpdateSchema = z.object({
|
|
103
|
+
id: z.string(),
|
|
104
|
+
status: ToolCallStatusSchema.optional(),
|
|
105
|
+
locations: z.array(FileLocationSchema).optional(),
|
|
106
|
+
rawOutput: z.record(z.string(), z.unknown()).optional(),
|
|
107
|
+
content: z.array(ToolCallContentBlockSchema).optional(),
|
|
108
|
+
error: z.string().optional(),
|
|
109
|
+
completedAt: z.number().optional(),
|
|
110
|
+
tokenUsage: TokenUsageSchema.optional(),
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* Helper to merge a tool call update into an existing tool call
|
|
114
|
+
*/
|
|
115
|
+
export function mergeToolCallUpdate(existing, update) {
|
|
116
|
+
const merged = {
|
|
117
|
+
...existing,
|
|
118
|
+
// Only update fields that are defined in the update
|
|
119
|
+
status: update.status ?? existing.status,
|
|
120
|
+
locations: update.locations ?? existing.locations,
|
|
121
|
+
rawOutput: update.rawOutput ?? existing.rawOutput,
|
|
122
|
+
content: update.content
|
|
123
|
+
? [...(existing.content ?? []), ...update.content]
|
|
124
|
+
: existing.content,
|
|
125
|
+
error: update.error ?? existing.error,
|
|
126
|
+
completedAt: update.completedAt ?? existing.completedAt,
|
|
127
|
+
tokenUsage: update.tokenUsage ?? existing.tokenUsage,
|
|
128
|
+
};
|
|
129
|
+
return merged;
|
|
130
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
DisplayMessage,
|
|
4
|
-
InputState,
|
|
5
|
-
} from "../schemas/index.js";
|
|
1
|
+
import type { ConnectionStatus, DisplayMessage, InputState } from "../schemas/index.js";
|
|
2
|
+
import type { ToolCall, ToolCallUpdate } from "../schemas/tool-call.js";
|
|
6
3
|
/**
|
|
7
4
|
* Chat store state
|
|
8
5
|
*/
|
|
9
6
|
export interface ChatStore {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
7
|
+
connectionStatus: ConnectionStatus;
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
error: string | null;
|
|
10
|
+
messages: DisplayMessage[];
|
|
11
|
+
isStreaming: boolean;
|
|
12
|
+
streamingStartTime: number | null;
|
|
13
|
+
toolCalls: Record<string, ToolCall[]>;
|
|
14
|
+
input: InputState;
|
|
15
|
+
setConnectionStatus: (status: ConnectionStatus) => void;
|
|
16
|
+
setSessionId: (id: string | null) => void;
|
|
17
|
+
setError: (error: string | null) => void;
|
|
18
|
+
addMessage: (message: DisplayMessage) => void;
|
|
19
|
+
updateMessage: (id: string, updates: Partial<DisplayMessage>) => void;
|
|
20
|
+
clearMessages: () => void;
|
|
21
|
+
setIsStreaming: (streaming: boolean) => void;
|
|
22
|
+
setStreamingStartTime: (time: number | null) => void;
|
|
23
|
+
addToolCall: (sessionId: string, toolCall: ToolCall) => void;
|
|
24
|
+
updateToolCall: (sessionId: string, update: ToolCallUpdate) => void;
|
|
25
|
+
addToolCallToCurrentMessage: (toolCall: ToolCall) => void;
|
|
26
|
+
updateToolCallInCurrentMessage: (update: ToolCallUpdate) => void;
|
|
27
|
+
setInputValue: (value: string) => void;
|
|
28
|
+
setInputSubmitting: (submitting: boolean) => void;
|
|
29
|
+
addFileAttachment: (file: InputState["attachedFiles"][number]) => void;
|
|
30
|
+
removeFileAttachment: (index: number) => void;
|
|
31
|
+
clearInput: () => void;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Create chat store
|
|
33
35
|
*/
|
|
34
|
-
export declare const useChatStore: import("zustand").UseBoundStore<
|
|
35
|
-
import("zustand").StoreApi<ChatStore>
|
|
36
|
-
>;
|
|
36
|
+
export declare const useChatStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatStore>>;
|
|
@@ -1,65 +1,129 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
import { mergeToolCallUpdate } from "../schemas/tool-call.js";
|
|
2
3
|
/**
|
|
3
4
|
* Create chat store
|
|
4
5
|
*/
|
|
5
6
|
export const useChatStore = create((set) => ({
|
|
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
|
-
|
|
7
|
+
// Initial state
|
|
8
|
+
connectionStatus: "disconnected",
|
|
9
|
+
sessionId: null,
|
|
10
|
+
error: null,
|
|
11
|
+
messages: [],
|
|
12
|
+
isStreaming: false,
|
|
13
|
+
streamingStartTime: null,
|
|
14
|
+
toolCalls: {},
|
|
15
|
+
input: {
|
|
16
|
+
value: "",
|
|
17
|
+
isSubmitting: false,
|
|
18
|
+
attachedFiles: [],
|
|
19
|
+
},
|
|
20
|
+
// Actions
|
|
21
|
+
setConnectionStatus: (status) => set({ connectionStatus: status }),
|
|
22
|
+
setSessionId: (id) => set({ sessionId: id }),
|
|
23
|
+
setError: (error) => set({ error }),
|
|
24
|
+
addMessage: (message) => set((state) => ({
|
|
25
|
+
messages: [...state.messages, message],
|
|
26
|
+
})),
|
|
27
|
+
updateMessage: (id, updates) => set((state) => ({
|
|
28
|
+
messages: state.messages.map((msg) => msg.id === id ? { ...msg, ...updates } : msg),
|
|
29
|
+
})),
|
|
30
|
+
clearMessages: () => set({ messages: [] }),
|
|
31
|
+
setIsStreaming: (streaming) => set({ isStreaming: streaming }),
|
|
32
|
+
setStreamingStartTime: (time) => set({ streamingStartTime: time }),
|
|
33
|
+
addToolCall: (sessionId, toolCall) => set((state) => ({
|
|
34
|
+
toolCalls: {
|
|
35
|
+
...state.toolCalls,
|
|
36
|
+
[sessionId]: [...(state.toolCalls[sessionId] || []), toolCall],
|
|
37
|
+
},
|
|
38
|
+
})),
|
|
39
|
+
addToolCallToCurrentMessage: (toolCall) => set((state) => {
|
|
40
|
+
// Find the most recent assistant message (which should be streaming)
|
|
41
|
+
const lastAssistantIndex = state.messages.findLastIndex((msg) => msg.role === "assistant");
|
|
42
|
+
if (lastAssistantIndex === -1) {
|
|
43
|
+
console.warn("No assistant message found to add tool call to");
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
const messages = [...state.messages];
|
|
47
|
+
const lastAssistantMsg = messages[lastAssistantIndex];
|
|
48
|
+
if (!lastAssistantMsg)
|
|
49
|
+
return state;
|
|
50
|
+
messages[lastAssistantIndex] = {
|
|
51
|
+
...lastAssistantMsg,
|
|
52
|
+
toolCalls: [...(lastAssistantMsg.toolCalls || []), toolCall],
|
|
53
|
+
};
|
|
54
|
+
return { messages };
|
|
55
|
+
}),
|
|
56
|
+
updateToolCallInCurrentMessage: (update) => set((state) => {
|
|
57
|
+
// Find the most recent assistant message
|
|
58
|
+
const lastAssistantIndex = state.messages.findLastIndex((msg) => msg.role === "assistant");
|
|
59
|
+
if (lastAssistantIndex === -1) {
|
|
60
|
+
console.warn("No assistant message found to update tool call in");
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
const messages = [...state.messages];
|
|
64
|
+
const lastAssistantMsg = messages[lastAssistantIndex];
|
|
65
|
+
if (!lastAssistantMsg)
|
|
66
|
+
return state;
|
|
67
|
+
const toolCalls = lastAssistantMsg.toolCalls || [];
|
|
68
|
+
const existingIndex = toolCalls.findIndex((tc) => tc.id === update.id);
|
|
69
|
+
if (existingIndex === -1) {
|
|
70
|
+
console.warn(`Tool call ${update.id} not found in message`);
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
const existing = toolCalls[existingIndex];
|
|
74
|
+
if (!existing)
|
|
75
|
+
return state;
|
|
76
|
+
const updatedToolCalls = [...toolCalls];
|
|
77
|
+
updatedToolCalls[existingIndex] = mergeToolCallUpdate(existing, update);
|
|
78
|
+
messages[lastAssistantIndex] = {
|
|
79
|
+
...lastAssistantMsg,
|
|
80
|
+
toolCalls: updatedToolCalls,
|
|
81
|
+
};
|
|
82
|
+
return { messages };
|
|
83
|
+
}),
|
|
84
|
+
updateToolCall: (sessionId, update) => set((state) => {
|
|
85
|
+
const sessionToolCalls = state.toolCalls[sessionId] || [];
|
|
86
|
+
const existingIndex = sessionToolCalls.findIndex((tc) => tc.id === update.id);
|
|
87
|
+
if (existingIndex === -1) {
|
|
88
|
+
// Tool call not found, ignore update
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
const existing = sessionToolCalls[existingIndex];
|
|
92
|
+
if (!existing) {
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
const updatedToolCalls = [...sessionToolCalls];
|
|
96
|
+
updatedToolCalls[existingIndex] = mergeToolCallUpdate(existing, update);
|
|
97
|
+
return {
|
|
98
|
+
toolCalls: {
|
|
99
|
+
...state.toolCalls,
|
|
100
|
+
[sessionId]: updatedToolCalls,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
setInputValue: (value) => set((state) => ({
|
|
105
|
+
input: { ...state.input, value },
|
|
106
|
+
})),
|
|
107
|
+
setInputSubmitting: (submitting) => set((state) => ({
|
|
108
|
+
input: { ...state.input, isSubmitting: submitting },
|
|
109
|
+
})),
|
|
110
|
+
addFileAttachment: (file) => set((state) => ({
|
|
111
|
+
input: {
|
|
112
|
+
...state.input,
|
|
113
|
+
attachedFiles: [...state.input.attachedFiles, file],
|
|
114
|
+
},
|
|
115
|
+
})),
|
|
116
|
+
removeFileAttachment: (index) => set((state) => ({
|
|
117
|
+
input: {
|
|
118
|
+
...state.input,
|
|
119
|
+
attachedFiles: state.input.attachedFiles.filter((_, i) => i !== index),
|
|
120
|
+
},
|
|
121
|
+
})),
|
|
122
|
+
clearInput: () => set((_state) => ({
|
|
123
|
+
input: {
|
|
124
|
+
value: "",
|
|
125
|
+
isSubmitting: false,
|
|
126
|
+
attachedFiles: [],
|
|
127
|
+
},
|
|
128
|
+
})),
|
|
65
129
|
}));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDown } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "./resizable.js";
|
|
5
6
|
import { Toaster } from "./Sonner.js";
|
|
6
7
|
const ChatLayoutContext = React.createContext(undefined);
|
|
7
8
|
const useChatLayoutContext = () => {
|
|
@@ -22,7 +23,7 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
22
23
|
setPanelSize,
|
|
23
24
|
activeTab,
|
|
24
25
|
setActiveTab,
|
|
25
|
-
}, children: _jsx("div", { ref: ref, className: cn("flex h-screen flex-row bg-background text-foreground", className), ...props, children: children }) }));
|
|
26
|
+
}, children: _jsx("div", { ref: ref, className: cn("flex h-screen flex-row bg-background text-foreground", className), ...props, children: _jsx(ResizablePanelGroup, { direction: "horizontal", className: "flex-1", children: children }) }) }));
|
|
26
27
|
});
|
|
27
28
|
ChatLayoutRoot.displayName = "ChatLayout.Root";
|
|
28
29
|
const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
@@ -30,7 +31,7 @@ const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, re
|
|
|
30
31
|
});
|
|
31
32
|
ChatLayoutHeader.displayName = "ChatLayout.Header";
|
|
32
33
|
const ChatLayoutMain = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
33
|
-
return (_jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden", className), ...props, children: children }));
|
|
34
|
+
return (_jsx(ResizablePanel, { defaultSize: 75, minSize: 50, children: _jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden h-full", className), ...props, children: children }) }));
|
|
34
35
|
});
|
|
35
36
|
ChatLayoutMain.displayName = "ChatLayout.Main";
|
|
36
37
|
const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, children, ...props }, ref) => {
|
|
@@ -90,13 +91,13 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, childr
|
|
|
90
91
|
// Hidden state - don't render
|
|
91
92
|
if (panelSize === "hidden")
|
|
92
93
|
return null;
|
|
93
|
-
return (_jsx("div", { ref: ref, className: cn(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
return (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: 25, minSize: 15, maxSize: 50, children: _jsx("div", { ref: ref, className: cn(
|
|
95
|
+
// Hidden by default, visible at breakpoint
|
|
96
|
+
"hidden h-full border-l border-border bg-card overflow-y-auto transition-all duration-300",
|
|
97
|
+
// Breakpoint visibility
|
|
98
|
+
breakpoint === "md" && "md:block", breakpoint === "lg" && "lg:block", breakpoint === "xl" && "xl:block", breakpoint === "2xl" && "2xl:block",
|
|
99
|
+
// Size variants - width is now controlled by ResizablePanel
|
|
100
|
+
className), ...props, children: children }) })] }));
|
|
100
101
|
});
|
|
101
102
|
ChatLayoutAside.displayName = "ChatLayout.Aside";
|
|
102
103
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -6,6 +6,7 @@ import { useChatStore } from "../../core/store/chat-store.js";
|
|
|
6
6
|
import { cn } from "../lib/utils.js";
|
|
7
7
|
import { Reasoning } from "./Reasoning.js";
|
|
8
8
|
import { Response } from "./Response.js";
|
|
9
|
+
import { ToolCall } from "./ToolCall.js";
|
|
9
10
|
/**
|
|
10
11
|
* MessageContent component inspired by shadcn.io/ai
|
|
11
12
|
* Provides the content container with role-based styling
|
|
@@ -97,7 +98,9 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
97
98
|
const hasThinking = !!thinking;
|
|
98
99
|
// Check if waiting (streaming but no content yet)
|
|
99
100
|
const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
|
|
100
|
-
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsxs("div", { className: "flex items-center gap-2 opacity-50", children: [_jsx(Loader2Icon, { className: "size-4 animate-spin text-muted-foreground" }), _jsx(WaitingElapsedTime, { startTime: streamingStartTime })] })), message.role === "
|
|
101
|
+
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsxs("div", { className: "flex items-center gap-2 opacity-50", children: [_jsx(Loader2Icon, { className: "size-4 animate-spin text-muted-foreground" }), _jsx(WaitingElapsedTime, { startTime: streamingStartTime })] })), message.role === "assistant" &&
|
|
102
|
+
message.toolCalls &&
|
|
103
|
+
message.toolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-3", children: message.toolCalls.map((toolCall) => (_jsx(ToolCall, { toolCall: toolCall }, toolCall.id))) })), message.role === "user" ? (_jsx("div", { className: "whitespace-pre-wrap", children: message.content })) : (_jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false }))] }));
|
|
101
104
|
}
|
|
102
105
|
return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
|
|
103
106
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
|
|
2
|
+
export interface ToolCallProps {
|
|
3
|
+
toolCall: ToolCallType;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* ToolCall component - displays a single tool call with collapsible details
|
|
7
|
+
*/
|
|
8
|
+
export declare function ToolCall({ toolCall }: ToolCallProps): import("react/jsx-runtime").JSX.Element;
|