@4djs/assistant 0.0.0 → 0.1.0

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.
Files changed (189) hide show
  1. package/README.md +49 -68
  2. package/dist/core/chat-activity.d.ts +19 -0
  3. package/dist/core/chat-activity.d.ts.map +1 -0
  4. package/dist/core/chat-commands.d.ts +33 -0
  5. package/dist/core/chat-commands.d.ts.map +1 -0
  6. package/dist/core/chat-history.d.ts +14 -0
  7. package/dist/core/chat-history.d.ts.map +1 -0
  8. package/dist/core/chat-reply-suggestions-parse.d.ts +20 -0
  9. package/dist/core/chat-reply-suggestions-parse.d.ts.map +1 -0
  10. package/dist/core/code-highlight.d.ts +3 -0
  11. package/dist/core/code-highlight.d.ts.map +1 -0
  12. package/dist/core/create-assistant-store.d.ts +33 -0
  13. package/dist/core/create-assistant-store.d.ts.map +1 -0
  14. package/dist/core/fetch-suggested-prompts.d.ts +11 -0
  15. package/dist/core/fetch-suggested-prompts.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +19 -0
  17. package/dist/core/index.d.ts.map +1 -0
  18. package/dist/core/index.js +2876 -0
  19. package/dist/core/interactive-tools/choices.d.ts +22 -0
  20. package/dist/core/interactive-tools/choices.d.ts.map +1 -0
  21. package/dist/core/interactive-tools/confirmation.d.ts +15 -0
  22. package/dist/core/interactive-tools/confirmation.d.ts.map +1 -0
  23. package/dist/core/interactive-tools/constants.d.ts +6 -0
  24. package/dist/core/interactive-tools/constants.d.ts.map +1 -0
  25. package/dist/core/interactive-tools/execute.d.ts +11 -0
  26. package/dist/core/interactive-tools/execute.d.ts.map +1 -0
  27. package/dist/core/interactive-tools/index.d.ts +7 -0
  28. package/dist/core/interactive-tools/index.d.ts.map +1 -0
  29. package/dist/core/interactive-tools/suggestions.d.ts +13 -0
  30. package/dist/core/interactive-tools/suggestions.d.ts.map +1 -0
  31. package/dist/core/interactive-tools/waiters.d.ts +4 -0
  32. package/dist/core/interactive-tools/waiters.d.ts.map +1 -0
  33. package/dist/core/llm-chat.d.ts +96 -0
  34. package/dist/core/llm-chat.d.ts.map +1 -0
  35. package/dist/core/llm-config.d.ts +24 -0
  36. package/dist/core/llm-config.d.ts.map +1 -0
  37. package/dist/core/llm-models.d.ts +14 -0
  38. package/dist/core/llm-models.d.ts.map +1 -0
  39. package/dist/core/llm-provider.d.ts +13 -0
  40. package/dist/core/llm-provider.d.ts.map +1 -0
  41. package/dist/core/llm-settings-storage.d.ts +47 -0
  42. package/dist/core/llm-settings-storage.d.ts.map +1 -0
  43. package/dist/core/llm-sse.d.ts +13 -0
  44. package/dist/core/llm-sse.d.ts.map +1 -0
  45. package/dist/core/llm-types.d.ts +49 -0
  46. package/dist/core/llm-types.d.ts.map +1 -0
  47. package/dist/core/markdown-utils.d.ts +3 -0
  48. package/dist/core/markdown-utils.d.ts.map +1 -0
  49. package/dist/core/prepare-markdown.d.ts +7 -0
  50. package/dist/core/prepare-markdown.d.ts.map +1 -0
  51. package/dist/core/types.d.ts +74 -0
  52. package/dist/core/types.d.ts.map +1 -0
  53. package/dist/index.css +1195 -0
  54. package/dist/index.js +184948 -0
  55. package/dist/react/Assistant.d.ts +10 -0
  56. package/dist/react/Assistant.d.ts.map +1 -0
  57. package/dist/react/components/HighlightedJsonCode.d.ts +6 -0
  58. package/dist/react/components/HighlightedJsonCode.d.ts.map +1 -0
  59. package/dist/react/components/MarkdownContent.d.ts +10 -0
  60. package/dist/react/components/MarkdownContent.d.ts.map +1 -0
  61. package/dist/react/components/MarkdownEditor.d.ts +11 -0
  62. package/dist/react/components/MarkdownEditor.d.ts.map +1 -0
  63. package/dist/react/components/MermaidDiagram.d.ts +8 -0
  64. package/dist/react/components/MermaidDiagram.d.ts.map +1 -0
  65. package/dist/react/components/ModelSelector.d.ts +8 -0
  66. package/dist/react/components/ModelSelector.d.ts.map +1 -0
  67. package/dist/react/components/chat/AssistantErrorCallout.d.ts +11 -0
  68. package/dist/react/components/chat/AssistantErrorCallout.d.ts.map +1 -0
  69. package/dist/react/components/chat/ChatActivity.d.ts +8 -0
  70. package/dist/react/components/chat/ChatActivity.d.ts.map +1 -0
  71. package/dist/react/components/chat/ChatComposer.d.ts +36 -0
  72. package/dist/react/components/chat/ChatComposer.d.ts.map +1 -0
  73. package/dist/react/components/chat/ChatEmptyState.d.ts +10 -0
  74. package/dist/react/components/chat/ChatEmptyState.d.ts.map +1 -0
  75. package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts +7 -0
  76. package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts.map +1 -0
  77. package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts +7 -0
  78. package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts.map +1 -0
  79. package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts +7 -0
  80. package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts.map +1 -0
  81. package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts +13 -0
  82. package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts.map +1 -0
  83. package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts +4 -0
  84. package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts.map +1 -0
  85. package/dist/react/components/chat/ChatMessage.d.ts +11 -0
  86. package/dist/react/components/chat/ChatMessage.d.ts.map +1 -0
  87. package/dist/react/components/chat/ChatMessageScroll.d.ts +8 -0
  88. package/dist/react/components/chat/ChatMessageScroll.d.ts.map +1 -0
  89. package/dist/react/components/chat/ChatReplySuggestions.d.ts +9 -0
  90. package/dist/react/components/chat/ChatReplySuggestions.d.ts.map +1 -0
  91. package/dist/react/components/chat/ComposerCommandMenu.d.ts +10 -0
  92. package/dist/react/components/chat/ComposerCommandMenu.d.ts.map +1 -0
  93. package/dist/react/components/chat/LlmSettingsStrip.d.ts +7 -0
  94. package/dist/react/components/chat/LlmSettingsStrip.d.ts.map +1 -0
  95. package/dist/react/components/chat/LlmSetupPrompt.d.ts +7 -0
  96. package/dist/react/components/chat/LlmSetupPrompt.d.ts.map +1 -0
  97. package/dist/react/components/chat/LlmUnavailableBanner.d.ts +6 -0
  98. package/dist/react/components/chat/LlmUnavailableBanner.d.ts.map +1 -0
  99. package/dist/react/components/chat/SuggestedPromptsList.d.ts +14 -0
  100. package/dist/react/components/chat/SuggestedPromptsList.d.ts.map +1 -0
  101. package/dist/react/components/chat/SuggestedPromptsStrip.d.ts +11 -0
  102. package/dist/react/components/chat/SuggestedPromptsStrip.d.ts.map +1 -0
  103. package/dist/react/components/chat/SystemPromptField.d.ts +10 -0
  104. package/dist/react/components/chat/SystemPromptField.d.ts.map +1 -0
  105. package/dist/react/components/highlighted-code.d.ts +8 -0
  106. package/dist/react/components/highlighted-code.d.ts.map +1 -0
  107. package/dist/react/context.d.ts +11 -0
  108. package/dist/react/context.d.ts.map +1 -0
  109. package/dist/react/hooks/use-composer-commands.d.ts +21 -0
  110. package/dist/react/hooks/use-composer-commands.d.ts.map +1 -0
  111. package/dist/react/hooks/use-suggested-prompts.d.ts +29 -0
  112. package/dist/react/hooks/use-suggested-prompts.d.ts.map +1 -0
  113. package/dist/react/index.d.ts +17 -0
  114. package/dist/react/index.d.ts.map +1 -0
  115. package/dist/react/lib/parse-assistant-error.d.ts +9 -0
  116. package/dist/react/lib/parse-assistant-error.d.ts.map +1 -0
  117. package/dist/react/lib/prompt-icons.d.ts +5 -0
  118. package/dist/react/lib/prompt-icons.d.ts.map +1 -0
  119. package/dist/react/types.d.ts +69 -0
  120. package/dist/react/types.d.ts.map +1 -0
  121. package/dist/react/utils/cn.d.ts +2 -0
  122. package/dist/react/utils/cn.d.ts.map +1 -0
  123. package/package.json +16 -5
  124. package/src/core/chat-activity.ts +0 -107
  125. package/src/core/chat-commands.ts +0 -173
  126. package/src/core/chat-history.ts +0 -113
  127. package/src/core/chat-reply-suggestions-parse.ts +0 -119
  128. package/src/core/code-highlight.ts +0 -20
  129. package/src/core/create-assistant-store.ts +0 -639
  130. package/src/core/fetch-suggested-prompts.ts +0 -53
  131. package/src/core/index.ts +0 -125
  132. package/src/core/interactive-tools/choices.ts +0 -155
  133. package/src/core/interactive-tools/confirmation.ts +0 -63
  134. package/src/core/interactive-tools/constants.ts +0 -22
  135. package/src/core/interactive-tools/execute.ts +0 -70
  136. package/src/core/interactive-tools/index.ts +0 -41
  137. package/src/core/interactive-tools/suggestions.ts +0 -87
  138. package/src/core/interactive-tools/waiters.ts +0 -55
  139. package/src/core/llm-chat.ts +0 -686
  140. package/src/core/llm-config.ts +0 -101
  141. package/src/core/llm-models.ts +0 -96
  142. package/src/core/llm-provider.ts +0 -99
  143. package/src/core/llm-settings-storage.ts +0 -331
  144. package/src/core/llm-sse.ts +0 -166
  145. package/src/core/llm-types.ts +0 -52
  146. package/src/core/markdown-utils.ts +0 -11
  147. package/src/core/prepare-markdown.ts +0 -38
  148. package/src/core/types.ts +0 -86
  149. package/src/css.d.ts +0 -1
  150. package/src/react/Assistant.tsx +0 -358
  151. package/src/react/components/HighlightedJsonCode.tsx +0 -24
  152. package/src/react/components/MarkdownContent.tsx +0 -98
  153. package/src/react/components/MarkdownEditor.tsx +0 -60
  154. package/src/react/components/MermaidDiagram.tsx +0 -139
  155. package/src/react/components/ModelSelector.tsx +0 -243
  156. package/src/react/components/chat/AssistantErrorCallout.tsx +0 -79
  157. package/src/react/components/chat/ChatActivity.tsx +0 -274
  158. package/src/react/components/chat/ChatComposer.tsx +0 -189
  159. package/src/react/components/chat/ChatEmptyState.tsx +0 -145
  160. package/src/react/components/chat/ChatInteractivePrompt/choices-prompt.tsx +0 -262
  161. package/src/react/components/chat/ChatInteractivePrompt/confirmation-prompt.tsx +0 -97
  162. package/src/react/components/chat/ChatInteractivePrompt/index.tsx +0 -60
  163. package/src/react/components/chat/ChatInteractivePrompt/shell.tsx +0 -60
  164. package/src/react/components/chat/ChatInteractivePrompt/utils.ts +0 -14
  165. package/src/react/components/chat/ChatMessage.tsx +0 -150
  166. package/src/react/components/chat/ChatMessageScroll.tsx +0 -116
  167. package/src/react/components/chat/ChatReplySuggestions.tsx +0 -231
  168. package/src/react/components/chat/ComposerCommandMenu.tsx +0 -69
  169. package/src/react/components/chat/LlmSettingsStrip.tsx +0 -348
  170. package/src/react/components/chat/LlmSetupPrompt.tsx +0 -58
  171. package/src/react/components/chat/LlmUnavailableBanner.tsx +0 -11
  172. package/src/react/components/chat/SuggestedPromptsList.tsx +0 -121
  173. package/src/react/components/chat/SuggestedPromptsStrip.tsx +0 -72
  174. package/src/react/components/chat/SystemPromptField.tsx +0 -107
  175. package/src/react/components/highlighted-code.tsx +0 -107
  176. package/src/react/context.tsx +0 -72
  177. package/src/react/hooks/use-composer-commands.ts +0 -129
  178. package/src/react/hooks/use-suggested-prompts.ts +0 -128
  179. package/src/react/index.ts +0 -39
  180. package/src/react/lib/parse-assistant-error.ts +0 -96
  181. package/src/react/lib/prompt-icons.ts +0 -40
  182. package/src/react/types.ts +0 -83
  183. package/src/react/utils/cn.ts +0 -5
  184. package/test/buildLlmHistory.test.ts +0 -95
  185. package/test/llm-config.test.ts +0 -72
  186. package/test/llmSettingsStorage.test.ts +0 -121
  187. package/test/parse-assistant-error.test.ts +0 -24
  188. package/tsconfig.json +0 -8
  189. /package/{src/styles/assistant.css → dist/styles.css} +0 -0
@@ -1,166 +0,0 @@
1
- import type { OpenAiToolCall } from "./llm-types.ts";
2
-
3
- interface ToolCallAccumulator {
4
- id: string;
5
- type: "function";
6
- function: { name: string; arguments: string };
7
- }
8
-
9
- export interface ParsedStreamChunk {
10
- contentDelta: string;
11
- content: string;
12
- toolCalls: OpenAiToolCall[];
13
- model: string | null;
14
- done: boolean;
15
- }
16
-
17
- export function createStreamParser() {
18
- let content = "";
19
- let model: string | null = null;
20
- const toolAcc = new Map<number, ToolCallAccumulator>();
21
-
22
- function toolCallAt(index: number): ToolCallAccumulator {
23
- const existing = toolAcc.get(index);
24
- if (existing) return existing;
25
- const created: ToolCallAccumulator = {
26
- id: "",
27
- type: "function",
28
- function: { name: "", arguments: "" },
29
- };
30
- toolAcc.set(index, created);
31
- return created;
32
- }
33
-
34
- function ingestLine(line: string): ParsedStreamChunk | null {
35
- const trimmed = line.trim();
36
- if (!trimmed.startsWith("data:")) return null;
37
-
38
- const data = trimmed.slice(5).trim();
39
- if (data === "[DONE]") {
40
- return {
41
- contentDelta: "",
42
- content,
43
- toolCalls: finalizeToolCalls(),
44
- model,
45
- done: true,
46
- };
47
- }
48
-
49
- let json: Record<string, unknown>;
50
- try {
51
- json = JSON.parse(data) as Record<string, unknown>;
52
- } catch {
53
- return null;
54
- }
55
-
56
- if (typeof json.model === "string") model = json.model;
57
-
58
- const choice = (
59
- json.choices as Array<Record<string, unknown>> | undefined
60
- )?.[0];
61
- const delta = choice?.delta as Record<string, unknown> | undefined;
62
- if (!delta) return null;
63
-
64
- let contentDelta = "";
65
- if (typeof delta.content === "string" && delta.content.length > 0) {
66
- contentDelta = delta.content;
67
- content += contentDelta;
68
- }
69
-
70
- const toolDeltas = delta.tool_calls as
71
- | Array<Record<string, unknown>>
72
- | undefined;
73
- if (toolDeltas) {
74
- for (const toolDelta of toolDeltas) {
75
- const index = (toolDelta.index as number | undefined) ?? 0;
76
- const current = toolCallAt(index);
77
- if (typeof toolDelta.id === "string") current.id = toolDelta.id;
78
- const fn = toolDelta.function as Record<string, unknown> | undefined;
79
- if (fn && typeof fn.name === "string") current.function.name = fn.name;
80
- if (fn && typeof fn.arguments === "string") {
81
- current.function.arguments += fn.arguments;
82
- }
83
- }
84
- }
85
-
86
- return {
87
- contentDelta,
88
- content,
89
- toolCalls: finalizeToolCalls(),
90
- model,
91
- done: false,
92
- };
93
- }
94
-
95
- function finalizeToolCalls(): OpenAiToolCall[] {
96
- return [...toolAcc.entries()]
97
- .sort(([a], [b]) => a - b)
98
- .map(([, value]) => value)
99
- .filter((value): value is ToolCallAccumulator =>
100
- Boolean(value.id && value.function.name),
101
- );
102
- }
103
-
104
- return { ingestLine };
105
- }
106
-
107
- export async function readSseStream(
108
- body: ReadableStream<Uint8Array>,
109
- onChunk: (chunk: ParsedStreamChunk) => void,
110
- signal?: AbortSignal,
111
- ): Promise<ParsedStreamChunk> {
112
- const reader = body.getReader();
113
- const decoder = new TextDecoder();
114
- const parser = createStreamParser();
115
- let buffer = "";
116
- let last: ParsedStreamChunk = {
117
- contentDelta: "",
118
- content: "",
119
- toolCalls: [],
120
- model: null,
121
- done: false,
122
- };
123
-
124
- const abort = () => {
125
- void reader.cancel();
126
- };
127
-
128
- if (signal?.aborted) {
129
- abort();
130
- throw new DOMException("Aborted", "AbortError");
131
- }
132
-
133
- signal?.addEventListener("abort", abort, { once: true });
134
-
135
- try {
136
- while (true) {
137
- if (signal?.aborted) {
138
- throw new DOMException("Aborted", "AbortError");
139
- }
140
- const { done, value } = await reader.read();
141
- if (done) break;
142
- buffer += decoder.decode(value, { stream: true });
143
- const lines = buffer.split("\n");
144
- buffer = lines.pop() ?? "";
145
- for (const line of lines) {
146
- const chunk = parser.ingestLine(line);
147
- if (!chunk) continue;
148
- last = chunk;
149
- onChunk(chunk);
150
- if (chunk.done) return chunk;
151
- }
152
- }
153
- } finally {
154
- signal?.removeEventListener("abort", abort);
155
- }
156
-
157
- if (buffer.trim()) {
158
- const chunk = parser.ingestLine(buffer);
159
- if (chunk) {
160
- last = { ...chunk, done: true };
161
- onChunk(last);
162
- }
163
- }
164
-
165
- return { ...last, done: true };
166
- }
@@ -1,52 +0,0 @@
1
- import type { JsonSchema } from "./types.ts";
2
-
3
- export interface OpenAiToolCall {
4
- id: string;
5
- type: "function";
6
- function: {
7
- name: string;
8
- arguments: string;
9
- };
10
- }
11
-
12
- export interface LlmChatMessage {
13
- role: "system" | "user" | "assistant" | "tool";
14
- content?: string | null;
15
- tool_call_id?: string;
16
- tool_calls?: OpenAiToolCall[];
17
- }
18
-
19
- export interface ClientToolDefinition {
20
- name: string;
21
- description: string;
22
- inputSchema: JsonSchema;
23
- }
24
-
25
- export interface ChatCompletionRequest {
26
- messages: LlmChatMessage[];
27
- tools?: ClientToolDefinition[];
28
- stream?: boolean;
29
- model?: string;
30
- systemPrompt?: string;
31
- responseFormat?: { type: "json_object" };
32
- }
33
-
34
- export interface ChatCompletionResult {
35
- message: {
36
- role: "assistant";
37
- content: string | null;
38
- tool_calls?: OpenAiToolCall[];
39
- };
40
- model: string;
41
- usage?: {
42
- prompt_tokens?: number;
43
- completion_tokens?: number;
44
- total_tokens?: number;
45
- };
46
- }
47
-
48
- export interface LlmStatusResponse {
49
- enabled: boolean;
50
- model: string | null;
51
- models: string[];
52
- }
@@ -1,11 +0,0 @@
1
- import { isValidElement, type ReactNode } from "react";
2
-
3
- export function childrenToText(children: ReactNode): string {
4
- if (typeof children === "string") return children;
5
- if (typeof children === "number") return String(children);
6
- if (Array.isArray(children)) return children.map(childrenToText).join("");
7
- if (isValidElement<{ children?: ReactNode }>(children)) {
8
- return childrenToText(children.props.children);
9
- }
10
- return "";
11
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * Normalize LaTeX for remark-math + KaTeX.
3
- * - `\[...\]` → `$$...$$` (display)
4
- * - `\(...\)` → `$...$` (inline)
5
- */
6
- export function prepareMarkdown(message: string): string {
7
- let text = convertLatexDisplayMath(message.trimStart());
8
- text = convertLatexInlineMath(text);
9
- return text.replace(/\n{3,}/g, "\n\n").trimEnd();
10
- }
11
-
12
- function convertLatexInlineMath(message: string): string {
13
- return message.replace(
14
- /\\\(([\s\S]*?)\\\)/g,
15
- (_, math) => `$${math.trim()}$`,
16
- );
17
- }
18
-
19
- function convertLatexDisplayMath(message: string): string {
20
- return message.replace(
21
- /(^|\n)([ \t]*?)\\\[([\s\S]*?)\\\]/g,
22
- (_full, lineStart, indent, rawMath) => {
23
- const mathLines = rawMath
24
- .split("\n")
25
- .map((line: string) => line.trim())
26
- .filter((line: string) => line.length > 0);
27
-
28
- if (mathLines.length === 0) {
29
- return _full;
30
- }
31
-
32
- const pad = indent.length > 0 ? " " : "";
33
- const body = mathLines.map((line: string) => `${pad}${line}`).join("\n");
34
-
35
- return `${lineStart}${pad}$$\n${body}\n${pad}$$`;
36
- },
37
- );
38
- }
package/src/core/types.ts DELETED
@@ -1,86 +0,0 @@
1
- import type { ChatActivityStep } from "./chat-activity.ts";
2
- import type { ReplySuggestions } from "./interactive-tools/index.ts";
3
-
4
- export type JsonSchema = Record<string, unknown>;
5
-
6
- export interface AssistantToolDefinition {
7
- name: string;
8
- description: string;
9
- inputSchema: JsonSchema;
10
- }
11
-
12
- export interface AssistantToolContent {
13
- type: string;
14
- text?: string;
15
- }
16
-
17
- export interface AssistantToolResult {
18
- isError?: boolean;
19
- content: AssistantToolContent[];
20
- structuredContent?: unknown;
21
- }
22
-
23
- export interface AssistantMessage {
24
- id: string;
25
- role: "user" | "assistant" | "system";
26
- content: string;
27
- activity?: ChatActivityStep[];
28
- toolCall?: { name: string; args: Record<string, unknown> };
29
- replySuggestions?: ReplySuggestions;
30
- isError?: boolean;
31
- llmSetupRequired?: boolean;
32
- streaming?: boolean;
33
- timestamp: number;
34
- }
35
-
36
- export interface AssistantSuggestedPrompt {
37
- id: string;
38
- label: string;
39
- description?: string;
40
- prompt: string;
41
- /** Icon name for dynamic prompts (e.g. "database", "search") */
42
- icon?: string;
43
- }
44
-
45
- export interface AssistantLlmSettings {
46
- enabled: boolean;
47
- baseUrl: string;
48
- apiKey: string | null;
49
- model: string;
50
- /** Optional static model list; fetched from the provider when omitted */
51
- models?: string[];
52
- /** Default system prompt prepended to each completion request */
53
- systemPrompt?: string;
54
- }
55
-
56
- export type AssistantLlmConfig =
57
- | AssistantLlmSettings
58
- | (() => AssistantLlmSettings | Promise<AssistantLlmSettings>);
59
-
60
- export interface AssistantStorageKeys {
61
- history?: string;
62
- /** @deprecated Legacy model key migrated into llmSettings on load */
63
- model?: string;
64
- llmSettings?: string;
65
- }
66
-
67
- export interface AssistantWelcomeContext {
68
- llmEnabled: boolean;
69
- model: string | null;
70
- }
71
-
72
- export interface AssistantStoreDependencies {
73
- llm?: AssistantLlmConfig;
74
- storageKeys?: AssistantStorageKeys;
75
- welcomeMessage: (ctx: AssistantWelcomeContext) => AssistantMessage;
76
- listTools: () => Promise<AssistantToolDefinition[]>;
77
- invokeTool: (
78
- name: string,
79
- args: Record<string, unknown>,
80
- ) => Promise<AssistantToolResult>;
81
- onToolInvoked?: (result: AssistantToolResult) => void;
82
- fallbackHandler?: (input: {
83
- message: string;
84
- tools: AssistantToolDefinition[];
85
- }) => Promise<AssistantMessage | null>;
86
- }
package/src/css.d.ts DELETED
@@ -1 +0,0 @@
1
- declare module "*.css";
@@ -1,358 +0,0 @@
1
- import { Sparkles, X } from "lucide-react";
2
- import {
3
- type KeyboardEvent,
4
- type ReactNode,
5
- useEffect,
6
- useMemo,
7
- useRef,
8
- useState,
9
- } from "react";
10
- import { runAssistantChatCommand } from "../core/chat-commands.ts";
11
- import { isLlmUnavailableMessage } from "../core/llm-config.ts";
12
- import { ChatComposer } from "./components/chat/ChatComposer.tsx";
13
- import { ChatEmptyState } from "./components/chat/ChatEmptyState.tsx";
14
- import { ChatMessageView } from "./components/chat/ChatMessage.tsx";
15
- import { ChatMessageScroll } from "./components/chat/ChatMessageScroll.tsx";
16
- import { LlmSettingsStrip } from "./components/chat/LlmSettingsStrip.tsx";
17
- import { LlmUnavailableBanner } from "./components/chat/LlmUnavailableBanner.tsx";
18
- import { SuggestedPromptsStrip } from "./components/chat/SuggestedPromptsStrip.tsx";
19
- import {
20
- AssistantBootstrap,
21
- AssistantProvider,
22
- useAssistant,
23
- useAssistantActions,
24
- useAssistantContext,
25
- } from "./context.tsx";
26
- import { useComposerCommands } from "./hooks/use-composer-commands.ts";
27
- import { useSuggestedPrompts } from "./hooks/use-suggested-prompts.ts";
28
- import type { AssistantConfig, AssistantProps } from "./types.ts";
29
-
30
- function AssistantPanel({
31
- className,
32
- header: headerOverride,
33
- emptyState: emptyStateOverride,
34
- ui: uiOverride,
35
- }: AssistantProps) {
36
- const { config } = useAssistantContext();
37
- const messages = useAssistant((s) => s.messages);
38
- const chatLoading = useAssistant((s) => s.chatLoading);
39
- const llmEnabled = useAssistant((s) => s.llmEnabled);
40
- const selectedModel = useAssistant((s) => s.selectedModel);
41
- const { sendChat, stopChat, clearChatHistory, retryLastChat } =
42
- useAssistantActions();
43
-
44
- const header = { ...config.header, ...headerOverride };
45
- const emptyState = { ...config.emptyState, ...emptyStateOverride };
46
- const ui = { ...config.ui, ...uiOverride };
47
-
48
- const HeaderIcon = header.icon ?? Sparkles;
49
-
50
- const [input, setInput] = useState("");
51
- const [commandError, setCommandError] = useState<string | null>(null);
52
- const [suggestionsOpen, setSuggestionsOpen] = useState(false);
53
- const [llmSettingsOpen, setLlmSettingsOpen] = useState(false);
54
- const textareaRef = useRef<HTMLTextAreaElement>(null);
55
-
56
- function focusComposer() {
57
- requestAnimationFrame(() => {
58
- textareaRef.current?.focus({ preventScroll: true });
59
- });
60
- }
61
-
62
- function handleClearChat() {
63
- clearChatHistory();
64
- focusComposer();
65
- }
66
-
67
- function handleCloseLlmSettings() {
68
- setLlmSettingsOpen(false);
69
- focusComposer();
70
- }
71
-
72
- const suggestions = useSuggestedPrompts({
73
- staticPrompts: emptyState?.suggestedPrompts,
74
- llmEnabled,
75
- model: selectedModel,
76
- enabled: true,
77
- dynamicSuggestedPrompts: emptyState?.dynamicSuggestedPrompts,
78
- listTools: config.listTools,
79
- fetchSuggestedPrompts: config.fetchSuggestedPrompts,
80
- });
81
-
82
- const visibleMessages = useMemo(
83
- () =>
84
- messages.filter(
85
- (message) =>
86
- message.id !== "welcome" &&
87
- (message.role === "user" || message.role === "assistant"),
88
- ),
89
- [messages],
90
- );
91
-
92
- const lastErrorMessageId = useMemo(() => {
93
- for (let i = visibleMessages.length - 1; i >= 0; i--) {
94
- const message = visibleMessages[i];
95
- if (message?.isError) return message.id;
96
- }
97
- return null;
98
- }, [visibleMessages]);
99
-
100
- const isStreaming = messages.some((message) => message.streaming);
101
- const streaming = chatLoading || isStreaming;
102
- const scrollKey = visibleMessages
103
- .map((message) => {
104
- const activityKey = (message.activity ?? [])
105
- .map((step) => `${step.id}:${step.status}`)
106
- .join(",");
107
- return `${message.id}:${message.content.length}:${message.streaming}:${activityKey}`;
108
- })
109
- .join("|");
110
-
111
- useEffect(() => {
112
- if (!chatLoading && !isStreaming) {
113
- textareaRef.current?.focus({ preventScroll: true });
114
- }
115
- }, [chatLoading, isStreaming]);
116
-
117
- function openLlmSettings() {
118
- setSuggestionsOpen(false);
119
- setLlmSettingsOpen(true);
120
- }
121
-
122
- async function handleSubmit(textOverride?: string) {
123
- const text = (textOverride ?? input).trim();
124
- if (!text) return;
125
-
126
- const commandResult = await runAssistantChatCommand(text, {
127
- clearMessages: handleClearChat,
128
- setError: setCommandError,
129
- streaming,
130
- });
131
-
132
- if (commandResult.handled) {
133
- if (commandResult.clearInput) {
134
- setInput("");
135
- }
136
- if (commandResult.error) {
137
- setCommandError(commandResult.error);
138
- }
139
- return;
140
- }
141
-
142
- setCommandError(null);
143
- setInput("");
144
- setSuggestionsOpen(false);
145
- await sendChat(text);
146
- }
147
-
148
- const {
149
- suggestions: commandSuggestions,
150
- menuOpen: commandMenuOpen,
151
- selectedIndex: selectedCommandIndex,
152
- completeCommand,
153
- handleKeyDown: handleCommandKeyDown,
154
- handleInputChange: handleCommandInputChange,
155
- } = useComposerCommands({
156
- value: input,
157
- onChange: (value) => {
158
- setCommandError(null);
159
- setInput(value);
160
- },
161
- surface: "assistant",
162
- disabled: streaming,
163
- onExecute: async (commandText) => {
164
- await handleSubmit(commandText);
165
- },
166
- });
167
-
168
- function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {
169
- if (handleCommandKeyDown(event)) {
170
- return;
171
- }
172
-
173
- if (event.key === "Enter" && !event.shiftKey) {
174
- event.preventDefault();
175
- void handleSubmit();
176
- }
177
- }
178
-
179
- const showEmptyState =
180
- visibleMessages.length === 0 && !chatLoading && !isStreaming;
181
- const hasLlmSetupInChat = visibleMessages.some((message) =>
182
- isLlmUnavailableMessage(message),
183
- );
184
- const showClearButton = header.showClearButton !== false;
185
- const showModelSelector = ui.showModelSelector !== false;
186
- const showLlmSettings = ui.showLlmSettings !== false;
187
- const canSuggest =
188
- llmEnabled &&
189
- Boolean(config.fetchSuggestedPrompts) &&
190
- emptyState?.dynamicSuggestedPrompts !== false;
191
- const showSuggestionsButton =
192
- canSuggest && header.showSuggestionsButton !== false;
193
-
194
- function handleSuggestionsClick() {
195
- setLlmSettingsOpen(false);
196
- if (showEmptyState) {
197
- void suggestions.refresh();
198
- return;
199
- }
200
- setSuggestionsOpen(true);
201
- void suggestions.refresh();
202
- }
203
-
204
- function handleRegenerateSuggestions() {
205
- void suggestions.refresh();
206
- }
207
-
208
- useEffect(() => {
209
- if (showEmptyState) {
210
- setSuggestionsOpen(false);
211
- }
212
- }, [showEmptyState]);
213
-
214
- return (
215
- <section
216
- className={`assistant-panel ${className ?? ""} ${ui.className ?? ""}`.trim()}
217
- style={ui.maxWidth ? { maxWidth: ui.maxWidth } : undefined}
218
- >
219
- <div className="assistant-panel__header">
220
- <div className="assistant-panel__header-main">
221
- <div className="assistant-panel__header-icon" aria-hidden>
222
- <HeaderIcon size={16} strokeWidth={2} />
223
- </div>
224
- <div className="assistant-panel__header-text">
225
- <h2 className="assistant-panel__title">
226
- {header.title ?? "Assistant"}
227
- </h2>
228
- {header.subtitle ? (
229
- <p className="assistant-panel__subtitle">{header.subtitle}</p>
230
- ) : null}
231
- </div>
232
- </div>
233
- </div>
234
-
235
- <ChatMessageScroll dependencyKey={scrollKey}>
236
- {showEmptyState && emptyState?.title && emptyState.description ? (
237
- <ChatEmptyState
238
- config={{
239
- title: emptyState.title,
240
- description: emptyState.description,
241
- suggestedPrompts: emptyState.suggestedPrompts,
242
- dynamicSuggestedPrompts: emptyState.dynamicSuggestedPrompts,
243
- }}
244
- suggestions={suggestions}
245
- onSelect={(prompt) => void handleSubmit(prompt)}
246
- />
247
- ) : null}
248
- {visibleMessages.map((message) => (
249
- <ChatMessageView
250
- key={message.id}
251
- message={message}
252
- onSuggestionSelect={(reply) => void handleSubmit(reply)}
253
- onOpenLlmSettings={showLlmSettings ? openLlmSettings : undefined}
254
- onRetryError={
255
- message.isError && message.id === lastErrorMessageId && llmEnabled
256
- ? () => void retryLastChat()
257
- : undefined
258
- }
259
- retryErrorLoading={chatLoading && message.id === lastErrorMessageId}
260
- />
261
- ))}
262
- </ChatMessageScroll>
263
-
264
- <footer className="assistant-panel__footer">
265
- {!llmEnabled && !hasLlmSetupInChat ? (
266
- <LlmUnavailableBanner onConfigure={openLlmSettings} />
267
- ) : null}
268
- {llmSettingsOpen ? (
269
- <LlmSettingsStrip
270
- open={llmSettingsOpen}
271
- onClose={handleCloseLlmSettings}
272
- />
273
- ) : null}
274
- {!showEmptyState && suggestionsOpen ? (
275
- <SuggestedPromptsStrip
276
- open={suggestionsOpen}
277
- onClose={() => setSuggestionsOpen(false)}
278
- onRefresh={handleRegenerateSuggestions}
279
- onSelect={(prompt) => void handleSubmit(prompt)}
280
- suggestions={suggestions}
281
- />
282
- ) : null}
283
- {commandError ? (
284
- <div className="assistant-command-error" role="alert">
285
- <p className="assistant-command-error__text">{commandError}</p>
286
- <button
287
- type="button"
288
- className="assistant-command-error__dismiss"
289
- aria-label="Dismiss"
290
- onClick={() => setCommandError(null)}
291
- >
292
- <X size={14} aria-hidden />
293
- </button>
294
- </div>
295
- ) : null}
296
- <ChatComposer
297
- input={input}
298
- onInputChange={handleCommandInputChange}
299
- onSubmit={() => void handleSubmit()}
300
- onKeyDown={handleKeyDown}
301
- streaming={streaming}
302
- onStop={stopChat}
303
- textareaRef={textareaRef}
304
- llmEnabled={llmEnabled && showModelSelector}
305
- inputDisabled={!llmEnabled}
306
- placeholder={
307
- llmEnabled
308
- ? ui.composerPlaceholder
309
- : "LLM not configured — chat is disabled"
310
- }
311
- toolbar={{
312
- showLlmSettings,
313
- onOpenLlmSettings: openLlmSettings,
314
- showGenerateSuggestions: showSuggestionsButton,
315
- onGenerateSuggestions: handleSuggestionsClick,
316
- suggestionsLoading: suggestions.loading,
317
- showClear: showClearButton,
318
- onClear: handleClearChat,
319
- clearDisabled: visibleMessages.length === 0,
320
- }}
321
- commandMenu={{
322
- open: commandMenuOpen,
323
- commands: commandSuggestions,
324
- selectedIndex: selectedCommandIndex,
325
- onSelect: completeCommand,
326
- }}
327
- />
328
- </footer>
329
- </section>
330
- );
331
- }
332
-
333
- /** Full assistant UI — requires `AssistantProvider` ancestor. */
334
- export function Assistant(props: AssistantProps) {
335
- return (
336
- <AssistantBootstrap>
337
- <AssistantPanel {...props} />
338
- </AssistantBootstrap>
339
- );
340
- }
341
-
342
- /** Provider + bootstrap + default UI in one component. */
343
- export function AssistantRoot({
344
- config,
345
- children,
346
- ...props
347
- }: AssistantProps & {
348
- config: AssistantConfig;
349
- children?: ReactNode;
350
- }) {
351
- return (
352
- <AssistantProvider config={config}>
353
- <AssistantBootstrap>
354
- {children ?? <AssistantPanel {...props} />}
355
- </AssistantBootstrap>
356
- </AssistantProvider>
357
- );
358
- }