@worktango/ai-assistant 0.0.26 → 0.0.28
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/package.json +3 -7
- package/types.d.ts +18 -2
- package/src/backend/express.test.ts +0 -208
- package/src/backend/express.ts +0 -105
- package/src/frontend/AiAssistant.test.tsx +0 -19
- package/src/frontend/AiAssistant.tsx +0 -26
- package/src/frontend/components/AiAssistantComponent.scss +0 -24
- package/src/frontend/components/AiAssistantComponent.test.tsx +0 -270
- package/src/frontend/components/AiAssistantComponent.tsx +0 -246
- package/src/frontend/components/messageRenderers/AssistantMessage.test.tsx +0 -154
- package/src/frontend/components/messageRenderers/AssistantMessage.tsx +0 -104
- package/src/frontend/components/messageRenderers/UserMessage.test.tsx +0 -103
- package/src/frontend/components/messageRenderers/UserMessage.tsx +0 -43
- package/src/frontend/config.ts +0 -54
- package/src/frontend/reactComponent.ts +0 -6
- package/src/frontend/tools/generatedSdks.ts +0 -44
- package/src/frontend/tools/llm-tools/getCurrentUser.test.ts +0 -100
- package/src/frontend/tools/llm-tools/getCurrentUser.ts +0 -158
- package/src/frontend/tools/llm-tools/index.ts +0 -6
- package/src/frontend/tools/llm-tools/kazooPlatform/getRewardsAndRecognitionCurrentUser.test.ts +0 -76
- package/src/frontend/tools/llm-tools/kazooPlatform/getRewardsAndRecognitionCurrentUser.ts +0 -36
- package/src/frontend/tools/llm-tools/pulsePlatform/.gitkeep +0 -0
- package/src/frontend/tools/llm-tools/shared/.gitkeep +0 -0
- package/src/frontend/tools/useAiAssistantSession.test.ts +0 -768
- package/src/frontend/tools/useAiAssistantSession.ts +0 -374
- package/src/frontend/useStyles.ts +0 -36
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { coalesce } from "@kazoohr/helpers";
|
|
4
|
-
import {
|
|
5
|
-
type ToolDeclaration,
|
|
6
|
-
type ChatItem,
|
|
7
|
-
type SupportedTextModel,
|
|
8
|
-
ChatData,
|
|
9
|
-
ChatDataText,
|
|
10
|
-
ChatDataToolResult,
|
|
11
|
-
ChatDataToolCall,
|
|
12
|
-
} from "@kazoohr/llm/types";
|
|
13
|
-
|
|
14
|
-
import { getLlmCallUrl } from "../config";
|
|
15
|
-
|
|
16
|
-
export type ChatItemWithTimestamp = ChatItem & { timestamp?: Date };
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* This hook is used to manage the state of the AI assistant session.
|
|
20
|
-
* It handles as much as possible of the LLM session, so that the dev
|
|
21
|
-
* can focus on efficiently rendering the UI.
|
|
22
|
-
*/
|
|
23
|
-
// eslint-disable-next-line max-lines-per-function, complexity
|
|
24
|
-
export function useAiAssistantSession({
|
|
25
|
-
model,
|
|
26
|
-
initialMessages,
|
|
27
|
-
temperature,
|
|
28
|
-
onAfterLlmCall,
|
|
29
|
-
onBeforeLlmCall,
|
|
30
|
-
onError,
|
|
31
|
-
tools,
|
|
32
|
-
onChunkAdded,
|
|
33
|
-
}: {
|
|
34
|
-
model?: SupportedTextModel;
|
|
35
|
-
initialMessages?: ChatItemWithTimestamp[];
|
|
36
|
-
temperature?: number;
|
|
37
|
-
onAfterLlmCall?: (messages: ChatItem[]) => void;
|
|
38
|
-
onBeforeLlmCall?: () => void;
|
|
39
|
-
onError?: (error: Error) => void;
|
|
40
|
-
tools?: ToolDeclaration[];
|
|
41
|
-
onChunkAdded?: () => void;
|
|
42
|
-
} = {}) {
|
|
43
|
-
const [isLoading, setIsLoading] = React.useState(false);
|
|
44
|
-
const [messages, setMessages] = React.useState<ChatItemWithTimestamp[]>(
|
|
45
|
-
() =>
|
|
46
|
-
initialMessages?.map((msg) => ({
|
|
47
|
-
...msg,
|
|
48
|
-
timestamp: coalesce(msg.timestamp, new Date()), // Adds timestamp to each message, even if not provided
|
|
49
|
-
})) ?? []
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* We use a ref to store the debug info. Debug info shouldn't trigger a re-
|
|
54
|
-
* render.
|
|
55
|
-
*/
|
|
56
|
-
const debugInfo = React.useRef<DebugInfo>([]);
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Sometimes, props change, but we don't want to recreate the callback functions
|
|
60
|
-
* so we use a ref to store them, so that our callbacks always grab the latest
|
|
61
|
-
* value.
|
|
62
|
-
*/
|
|
63
|
-
const propRefs = React.useRef<PropRefs>({
|
|
64
|
-
onAfterLlmCall,
|
|
65
|
-
onBeforeLlmCall,
|
|
66
|
-
onError,
|
|
67
|
-
tools,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const sendMessages = React.useCallback(
|
|
71
|
-
// eslint-disable-next-line max-lines-per-function, complexity
|
|
72
|
-
async (messages: ChatItem[]) => {
|
|
73
|
-
try {
|
|
74
|
-
if (propRefs.current.onBeforeLlmCall) {
|
|
75
|
-
debugInfo.current.push({
|
|
76
|
-
time: new Date(),
|
|
77
|
-
message: "Calling onBeforeLlmCall",
|
|
78
|
-
level: "info",
|
|
79
|
-
});
|
|
80
|
-
propRefs.current.onBeforeLlmCall();
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
debugInfo.current.push({
|
|
84
|
-
time: new Date(),
|
|
85
|
-
message: "Error calling onBeforeLlmCall",
|
|
86
|
-
data: error,
|
|
87
|
-
level: "error",
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const requestBody = {
|
|
92
|
-
model,
|
|
93
|
-
temperature,
|
|
94
|
-
stream: true,
|
|
95
|
-
messages,
|
|
96
|
-
tools,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
setIsLoading(true);
|
|
100
|
-
|
|
101
|
-
debugInfo.current.push({
|
|
102
|
-
time: new Date(),
|
|
103
|
-
message: "Calling LLM API",
|
|
104
|
-
data: requestBody,
|
|
105
|
-
level: "info",
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const response = await fetch(getLlmCallUrl(), {
|
|
109
|
-
method: "POST",
|
|
110
|
-
body: JSON.stringify(requestBody),
|
|
111
|
-
credentials: "include",
|
|
112
|
-
headers: {
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
Accept: "text/event-stream",
|
|
115
|
-
},
|
|
116
|
-
}).catch((error) => {
|
|
117
|
-
debugInfo.current.push({
|
|
118
|
-
time: new Date(),
|
|
119
|
-
message: "Error calling LLM API",
|
|
120
|
-
data: error,
|
|
121
|
-
level: "error",
|
|
122
|
-
});
|
|
123
|
-
propRefs.current.onError?.(
|
|
124
|
-
new Error("Unexpected error calling LLM API. Please try again.")
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
setIsLoading(false);
|
|
129
|
-
|
|
130
|
-
if (!response || !response.ok) {
|
|
131
|
-
propRefs.current.onError?.(
|
|
132
|
-
new Error("Unable to connect to LLM API. Please try again.")
|
|
133
|
-
);
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const reader = response.body?.getReader();
|
|
139
|
-
if (!reader) {
|
|
140
|
-
propRefs.current.onError?.(
|
|
141
|
-
new Error(
|
|
142
|
-
"Unexpected client error. Response body reader not available."
|
|
143
|
-
)
|
|
144
|
-
);
|
|
145
|
-
debugInfo.current.push({
|
|
146
|
-
time: new Date(),
|
|
147
|
-
message: "No reader available",
|
|
148
|
-
level: "error",
|
|
149
|
-
});
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const decoder = new TextDecoder();
|
|
154
|
-
const chatDataText: ChatDataText = {
|
|
155
|
-
type: "text",
|
|
156
|
-
text: "",
|
|
157
|
-
};
|
|
158
|
-
const toolCalls: ChatDataToolCall[] = [];
|
|
159
|
-
|
|
160
|
-
// Add the user's message immediately
|
|
161
|
-
const newMessages: ChatItem[] = [...messages];
|
|
162
|
-
setMessages(newMessages);
|
|
163
|
-
|
|
164
|
-
let i = 1_000_000; // Bounding our loop, just in case
|
|
165
|
-
while (i--) {
|
|
166
|
-
const { done, value } = await reader.read();
|
|
167
|
-
if (done) {
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const chunk = decoder.decode(value);
|
|
172
|
-
debugInfo.current.push({
|
|
173
|
-
time: new Date(),
|
|
174
|
-
message: "Received chunk",
|
|
175
|
-
data: chunk,
|
|
176
|
-
level: "info",
|
|
177
|
-
});
|
|
178
|
-
const parsedChunk: ChatData = JSON.parse(chunk);
|
|
179
|
-
|
|
180
|
-
if (parsedChunk.type === "text") {
|
|
181
|
-
chatDataText.text += parsedChunk.text;
|
|
182
|
-
} else if (parsedChunk.type === "toolCall") {
|
|
183
|
-
toolCalls.push(parsedChunk);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Update the last message's text content
|
|
187
|
-
setMessages((currentMessages) => {
|
|
188
|
-
const updatedMessages = [...currentMessages];
|
|
189
|
-
|
|
190
|
-
if (updatedMessages[updatedMessages.length - 1]?.role !== "model") {
|
|
191
|
-
updatedMessages.push({
|
|
192
|
-
role: "model",
|
|
193
|
-
parts: [],
|
|
194
|
-
timestamp: new Date(),
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
updatedMessages[updatedMessages.length - 1].parts = [
|
|
199
|
-
...toolCalls,
|
|
200
|
-
...(chatDataText.text ? [chatDataText] : []),
|
|
201
|
-
];
|
|
202
|
-
|
|
203
|
-
if (onChunkAdded) {
|
|
204
|
-
onChunkAdded();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return updatedMessages;
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
debugInfo.current.push({
|
|
212
|
-
time: new Date(),
|
|
213
|
-
message: "Completed streaming response",
|
|
214
|
-
level: "info",
|
|
215
|
-
data: {
|
|
216
|
-
chatDataText,
|
|
217
|
-
toolCalls,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
if (propRefs.current.onAfterLlmCall) {
|
|
223
|
-
debugInfo.current.push({
|
|
224
|
-
time: new Date(),
|
|
225
|
-
message: "Calling onAfterLlmCall",
|
|
226
|
-
level: "info",
|
|
227
|
-
});
|
|
228
|
-
propRefs.current.onAfterLlmCall(newMessages);
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
debugInfo.current.push({
|
|
232
|
-
time: new Date(),
|
|
233
|
-
message: "Error calling onAfterLlmCall",
|
|
234
|
-
data: error,
|
|
235
|
-
level: "error",
|
|
236
|
-
});
|
|
237
|
-
} finally {
|
|
238
|
-
// This branch re-runs this task if the last message from the LLM was
|
|
239
|
-
// a tool call.
|
|
240
|
-
handlePendingToolCalls({
|
|
241
|
-
toolCalls,
|
|
242
|
-
setMessages,
|
|
243
|
-
sendMessages,
|
|
244
|
-
propRefs,
|
|
245
|
-
debugInfo,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
return true;
|
|
249
|
-
} catch (streamError) {
|
|
250
|
-
debugInfo.current.push({
|
|
251
|
-
time: new Date(),
|
|
252
|
-
message: "Error processing stream",
|
|
253
|
-
data: {
|
|
254
|
-
message: streamError.message,
|
|
255
|
-
stack: streamError.stack,
|
|
256
|
-
},
|
|
257
|
-
level: "error",
|
|
258
|
-
});
|
|
259
|
-
propRefs.current.onError?.(
|
|
260
|
-
new Error("Unexpected error serving the request. Please try again.")
|
|
261
|
-
);
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
[model, temperature]
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
const sendUserMessage = React.useCallback(
|
|
269
|
-
async (message: string) => {
|
|
270
|
-
const originalMessages = [...messages];
|
|
271
|
-
const newMessage: ChatItemWithTimestamp = {
|
|
272
|
-
role: "user",
|
|
273
|
-
parts: [{ type: "text", text: message }],
|
|
274
|
-
timestamp: new Date(),
|
|
275
|
-
};
|
|
276
|
-
addMessage(newMessage);
|
|
277
|
-
const success = await sendMessages([...originalMessages, newMessage]);
|
|
278
|
-
if (!success) {
|
|
279
|
-
debugInfo.current.push({
|
|
280
|
-
time: new Date(),
|
|
281
|
-
message: "Error sending message. Reverting to original messages",
|
|
282
|
-
data: originalMessages,
|
|
283
|
-
level: "error",
|
|
284
|
-
});
|
|
285
|
-
setMessages(originalMessages);
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
[messages]
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
const addMessage = React.useCallback(
|
|
292
|
-
(message: ChatItem) => {
|
|
293
|
-
setMessages([...messages, message]);
|
|
294
|
-
},
|
|
295
|
-
[messages]
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
isLoading,
|
|
300
|
-
messages,
|
|
301
|
-
setMessages,
|
|
302
|
-
debugInfo: debugInfo.current,
|
|
303
|
-
addMessage,
|
|
304
|
-
sendUserMessage,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
type PropRefs = {
|
|
309
|
-
onAfterLlmCall?: (messages: ChatItem[]) => void;
|
|
310
|
-
onBeforeLlmCall?: () => void;
|
|
311
|
-
onError?: (error: Error) => void;
|
|
312
|
-
tools?: ToolDeclaration[];
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
type DebugInfo = {
|
|
316
|
-
time: Date;
|
|
317
|
-
message: string;
|
|
318
|
-
data?: any;
|
|
319
|
-
level: "info" | "error" | "warn";
|
|
320
|
-
}[];
|
|
321
|
-
|
|
322
|
-
async function handlePendingToolCalls({
|
|
323
|
-
toolCalls,
|
|
324
|
-
sendMessages,
|
|
325
|
-
setMessages,
|
|
326
|
-
propRefs,
|
|
327
|
-
debugInfo,
|
|
328
|
-
}: {
|
|
329
|
-
toolCalls: ChatDataToolCall[];
|
|
330
|
-
setMessages: React.Dispatch<React.SetStateAction<ChatItem[]>>;
|
|
331
|
-
sendMessages: (messages: ChatItem[]) => Promise<boolean>;
|
|
332
|
-
propRefs: React.MutableRefObject<PropRefs>;
|
|
333
|
-
debugInfo: React.MutableRefObject<DebugInfo>;
|
|
334
|
-
}): Promise<void> {
|
|
335
|
-
if (toolCalls.length === 0) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const toolResults: ChatDataToolResult[] = [];
|
|
340
|
-
|
|
341
|
-
for (const toolCall of toolCalls) {
|
|
342
|
-
const tool = propRefs.current.tools?.find(
|
|
343
|
-
(tool) => tool.name === toolCall.toolName
|
|
344
|
-
);
|
|
345
|
-
if (!tool) {
|
|
346
|
-
propRefs.current.onError?.(
|
|
347
|
-
new Error(`Tool ${toolCall.toolName} not found`)
|
|
348
|
-
);
|
|
349
|
-
debugInfo.current.push({
|
|
350
|
-
time: new Date(),
|
|
351
|
-
message: `Tool ${toolCall.toolName} not found`,
|
|
352
|
-
data: {
|
|
353
|
-
toolCall,
|
|
354
|
-
tools: propRefs.current.tools,
|
|
355
|
-
},
|
|
356
|
-
level: "error",
|
|
357
|
-
});
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const result = await tool.fulfill(toolCall.args);
|
|
362
|
-
toolResults.push({
|
|
363
|
-
type: "toolResult" as const,
|
|
364
|
-
toolName: toolCall.toolName,
|
|
365
|
-
result,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
const newMessage = { role: "tool" as const, parts: toolResults };
|
|
369
|
-
setMessages((currentMessages) => {
|
|
370
|
-
const newMessages = [...currentMessages, newMessage];
|
|
371
|
-
sendMessages(newMessages);
|
|
372
|
-
return newMessages;
|
|
373
|
-
});
|
|
374
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { setStateValue } from "@kazoohr/helpers";
|
|
4
|
-
|
|
5
|
-
const CONFETTI_CSS = "$$CONFETTI_CSS_REPLACEME$$";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This hook injects the Confetti CSS into the page and sets a flag in the
|
|
9
|
-
* window object to indicate that the CSS has been injected. We do this so
|
|
10
|
-
* that we can avoid a flash of unstyled content (FOUC); and to make the
|
|
11
|
-
* process of shipping this component far simpler.
|
|
12
|
-
*/
|
|
13
|
-
export function useStyles() {
|
|
14
|
-
const [isLoaded, setIsLoaded] = React.useState(false);
|
|
15
|
-
React.useEffect(() => {
|
|
16
|
-
// I could avoid this by updating our root `types`; but that would leave
|
|
17
|
-
// evidence of this ugly hack elsewhere in a codebase that it's not even
|
|
18
|
-
// needed for. So we'll just live with a lint rule ignore.
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
20
|
-
const windowAsAny = window as any;
|
|
21
|
-
// istanbul ignore if - noop if the styles have already been injected
|
|
22
|
-
if (windowAsAny._confettiStylesHaveBeenInjected) {
|
|
23
|
-
setTimeout(setStateValue(setIsLoaded, true), 50);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const styles = document.createElement("style");
|
|
27
|
-
styles.innerHTML = CONFETTI_CSS;
|
|
28
|
-
document.head.appendChild(styles);
|
|
29
|
-
windowAsAny._confettiStylesHaveBeenInjected = true;
|
|
30
|
-
setTimeout(setStateValue(setIsLoaded, true), 50);
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
isLoaded,
|
|
35
|
-
};
|
|
36
|
-
}
|