@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.
- package/README.md +49 -68
- package/dist/core/chat-activity.d.ts +19 -0
- package/dist/core/chat-activity.d.ts.map +1 -0
- package/dist/core/chat-commands.d.ts +33 -0
- package/dist/core/chat-commands.d.ts.map +1 -0
- package/dist/core/chat-history.d.ts +14 -0
- package/dist/core/chat-history.d.ts.map +1 -0
- package/dist/core/chat-reply-suggestions-parse.d.ts +20 -0
- package/dist/core/chat-reply-suggestions-parse.d.ts.map +1 -0
- package/dist/core/code-highlight.d.ts +3 -0
- package/dist/core/code-highlight.d.ts.map +1 -0
- package/dist/core/create-assistant-store.d.ts +33 -0
- package/dist/core/create-assistant-store.d.ts.map +1 -0
- package/dist/core/fetch-suggested-prompts.d.ts +11 -0
- package/dist/core/fetch-suggested-prompts.d.ts.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2876 -0
- package/dist/core/interactive-tools/choices.d.ts +22 -0
- package/dist/core/interactive-tools/choices.d.ts.map +1 -0
- package/dist/core/interactive-tools/confirmation.d.ts +15 -0
- package/dist/core/interactive-tools/confirmation.d.ts.map +1 -0
- package/dist/core/interactive-tools/constants.d.ts +6 -0
- package/dist/core/interactive-tools/constants.d.ts.map +1 -0
- package/dist/core/interactive-tools/execute.d.ts +11 -0
- package/dist/core/interactive-tools/execute.d.ts.map +1 -0
- package/dist/core/interactive-tools/index.d.ts +7 -0
- package/dist/core/interactive-tools/index.d.ts.map +1 -0
- package/dist/core/interactive-tools/suggestions.d.ts +13 -0
- package/dist/core/interactive-tools/suggestions.d.ts.map +1 -0
- package/dist/core/interactive-tools/waiters.d.ts +4 -0
- package/dist/core/interactive-tools/waiters.d.ts.map +1 -0
- package/dist/core/llm-chat.d.ts +96 -0
- package/dist/core/llm-chat.d.ts.map +1 -0
- package/dist/core/llm-config.d.ts +24 -0
- package/dist/core/llm-config.d.ts.map +1 -0
- package/dist/core/llm-models.d.ts +14 -0
- package/dist/core/llm-models.d.ts.map +1 -0
- package/dist/core/llm-provider.d.ts +13 -0
- package/dist/core/llm-provider.d.ts.map +1 -0
- package/dist/core/llm-settings-storage.d.ts +47 -0
- package/dist/core/llm-settings-storage.d.ts.map +1 -0
- package/dist/core/llm-sse.d.ts +13 -0
- package/dist/core/llm-sse.d.ts.map +1 -0
- package/dist/core/llm-types.d.ts +49 -0
- package/dist/core/llm-types.d.ts.map +1 -0
- package/dist/core/markdown-utils.d.ts +3 -0
- package/dist/core/markdown-utils.d.ts.map +1 -0
- package/dist/core/prepare-markdown.d.ts +7 -0
- package/dist/core/prepare-markdown.d.ts.map +1 -0
- package/dist/core/types.d.ts +74 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.css +1195 -0
- package/dist/index.js +184948 -0
- package/dist/react/Assistant.d.ts +10 -0
- package/dist/react/Assistant.d.ts.map +1 -0
- package/dist/react/components/HighlightedJsonCode.d.ts +6 -0
- package/dist/react/components/HighlightedJsonCode.d.ts.map +1 -0
- package/dist/react/components/MarkdownContent.d.ts +10 -0
- package/dist/react/components/MarkdownContent.d.ts.map +1 -0
- package/dist/react/components/MarkdownEditor.d.ts +11 -0
- package/dist/react/components/MarkdownEditor.d.ts.map +1 -0
- package/dist/react/components/MermaidDiagram.d.ts +8 -0
- package/dist/react/components/MermaidDiagram.d.ts.map +1 -0
- package/dist/react/components/ModelSelector.d.ts +8 -0
- package/dist/react/components/ModelSelector.d.ts.map +1 -0
- package/dist/react/components/chat/AssistantErrorCallout.d.ts +11 -0
- package/dist/react/components/chat/AssistantErrorCallout.d.ts.map +1 -0
- package/dist/react/components/chat/ChatActivity.d.ts +8 -0
- package/dist/react/components/chat/ChatActivity.d.ts.map +1 -0
- package/dist/react/components/chat/ChatComposer.d.ts +36 -0
- package/dist/react/components/chat/ChatComposer.d.ts.map +1 -0
- package/dist/react/components/chat/ChatEmptyState.d.ts +10 -0
- package/dist/react/components/chat/ChatEmptyState.d.ts.map +1 -0
- package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts +7 -0
- package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts.map +1 -0
- package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts +7 -0
- package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts.map +1 -0
- package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts +7 -0
- package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts.map +1 -0
- package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts +13 -0
- package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts.map +1 -0
- package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts +4 -0
- package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts.map +1 -0
- package/dist/react/components/chat/ChatMessage.d.ts +11 -0
- package/dist/react/components/chat/ChatMessage.d.ts.map +1 -0
- package/dist/react/components/chat/ChatMessageScroll.d.ts +8 -0
- package/dist/react/components/chat/ChatMessageScroll.d.ts.map +1 -0
- package/dist/react/components/chat/ChatReplySuggestions.d.ts +9 -0
- package/dist/react/components/chat/ChatReplySuggestions.d.ts.map +1 -0
- package/dist/react/components/chat/ComposerCommandMenu.d.ts +10 -0
- package/dist/react/components/chat/ComposerCommandMenu.d.ts.map +1 -0
- package/dist/react/components/chat/LlmSettingsStrip.d.ts +7 -0
- package/dist/react/components/chat/LlmSettingsStrip.d.ts.map +1 -0
- package/dist/react/components/chat/LlmSetupPrompt.d.ts +7 -0
- package/dist/react/components/chat/LlmSetupPrompt.d.ts.map +1 -0
- package/dist/react/components/chat/LlmUnavailableBanner.d.ts +6 -0
- package/dist/react/components/chat/LlmUnavailableBanner.d.ts.map +1 -0
- package/dist/react/components/chat/SuggestedPromptsList.d.ts +14 -0
- package/dist/react/components/chat/SuggestedPromptsList.d.ts.map +1 -0
- package/dist/react/components/chat/SuggestedPromptsStrip.d.ts +11 -0
- package/dist/react/components/chat/SuggestedPromptsStrip.d.ts.map +1 -0
- package/dist/react/components/chat/SystemPromptField.d.ts +10 -0
- package/dist/react/components/chat/SystemPromptField.d.ts.map +1 -0
- package/dist/react/components/highlighted-code.d.ts +8 -0
- package/dist/react/components/highlighted-code.d.ts.map +1 -0
- package/dist/react/context.d.ts +11 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/hooks/use-composer-commands.d.ts +21 -0
- package/dist/react/hooks/use-composer-commands.d.ts.map +1 -0
- package/dist/react/hooks/use-suggested-prompts.d.ts +29 -0
- package/dist/react/hooks/use-suggested-prompts.d.ts.map +1 -0
- package/dist/react/index.d.ts +17 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/lib/parse-assistant-error.d.ts +9 -0
- package/dist/react/lib/parse-assistant-error.d.ts.map +1 -0
- package/dist/react/lib/prompt-icons.d.ts +5 -0
- package/dist/react/lib/prompt-icons.d.ts.map +1 -0
- package/dist/react/types.d.ts +69 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/utils/cn.d.ts +2 -0
- package/dist/react/utils/cn.d.ts.map +1 -0
- package/package.json +16 -5
- package/src/core/chat-activity.ts +0 -107
- package/src/core/chat-commands.ts +0 -173
- package/src/core/chat-history.ts +0 -113
- package/src/core/chat-reply-suggestions-parse.ts +0 -119
- package/src/core/code-highlight.ts +0 -20
- package/src/core/create-assistant-store.ts +0 -639
- package/src/core/fetch-suggested-prompts.ts +0 -53
- package/src/core/index.ts +0 -125
- package/src/core/interactive-tools/choices.ts +0 -155
- package/src/core/interactive-tools/confirmation.ts +0 -63
- package/src/core/interactive-tools/constants.ts +0 -22
- package/src/core/interactive-tools/execute.ts +0 -70
- package/src/core/interactive-tools/index.ts +0 -41
- package/src/core/interactive-tools/suggestions.ts +0 -87
- package/src/core/interactive-tools/waiters.ts +0 -55
- package/src/core/llm-chat.ts +0 -686
- package/src/core/llm-config.ts +0 -101
- package/src/core/llm-models.ts +0 -96
- package/src/core/llm-provider.ts +0 -99
- package/src/core/llm-settings-storage.ts +0 -331
- package/src/core/llm-sse.ts +0 -166
- package/src/core/llm-types.ts +0 -52
- package/src/core/markdown-utils.ts +0 -11
- package/src/core/prepare-markdown.ts +0 -38
- package/src/core/types.ts +0 -86
- package/src/css.d.ts +0 -1
- package/src/react/Assistant.tsx +0 -358
- package/src/react/components/HighlightedJsonCode.tsx +0 -24
- package/src/react/components/MarkdownContent.tsx +0 -98
- package/src/react/components/MarkdownEditor.tsx +0 -60
- package/src/react/components/MermaidDiagram.tsx +0 -139
- package/src/react/components/ModelSelector.tsx +0 -243
- package/src/react/components/chat/AssistantErrorCallout.tsx +0 -79
- package/src/react/components/chat/ChatActivity.tsx +0 -274
- package/src/react/components/chat/ChatComposer.tsx +0 -189
- package/src/react/components/chat/ChatEmptyState.tsx +0 -145
- package/src/react/components/chat/ChatInteractivePrompt/choices-prompt.tsx +0 -262
- package/src/react/components/chat/ChatInteractivePrompt/confirmation-prompt.tsx +0 -97
- package/src/react/components/chat/ChatInteractivePrompt/index.tsx +0 -60
- package/src/react/components/chat/ChatInteractivePrompt/shell.tsx +0 -60
- package/src/react/components/chat/ChatInteractivePrompt/utils.ts +0 -14
- package/src/react/components/chat/ChatMessage.tsx +0 -150
- package/src/react/components/chat/ChatMessageScroll.tsx +0 -116
- package/src/react/components/chat/ChatReplySuggestions.tsx +0 -231
- package/src/react/components/chat/ComposerCommandMenu.tsx +0 -69
- package/src/react/components/chat/LlmSettingsStrip.tsx +0 -348
- package/src/react/components/chat/LlmSetupPrompt.tsx +0 -58
- package/src/react/components/chat/LlmUnavailableBanner.tsx +0 -11
- package/src/react/components/chat/SuggestedPromptsList.tsx +0 -121
- package/src/react/components/chat/SuggestedPromptsStrip.tsx +0 -72
- package/src/react/components/chat/SystemPromptField.tsx +0 -107
- package/src/react/components/highlighted-code.tsx +0 -107
- package/src/react/context.tsx +0 -72
- package/src/react/hooks/use-composer-commands.ts +0 -129
- package/src/react/hooks/use-suggested-prompts.ts +0 -128
- package/src/react/index.ts +0 -39
- package/src/react/lib/parse-assistant-error.ts +0 -96
- package/src/react/lib/prompt-icons.ts +0 -40
- package/src/react/types.ts +0 -83
- package/src/react/utils/cn.ts +0 -5
- package/test/buildLlmHistory.test.ts +0 -95
- package/test/llm-config.test.ts +0 -72
- package/test/llmSettingsStorage.test.ts +0 -121
- package/test/parse-assistant-error.test.ts +0 -24
- package/tsconfig.json +0 -8
- /package/{src/styles/assistant.css → dist/styles.css} +0 -0
package/src/core/llm-sse.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/llm-types.ts
DELETED
|
@@ -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";
|
package/src/react/Assistant.tsx
DELETED
|
@@ -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
|
-
}
|