@neeter/react 0.6.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/LICENSE +21 -0
- package/dist/AgentProvider.d.ts +15 -0
- package/dist/AgentProvider.js +30 -0
- package/dist/ApprovalButtons.d.ts +5 -0
- package/dist/ApprovalButtons.js +30 -0
- package/dist/ChatInput.d.ts +6 -0
- package/dist/ChatInput.js +41 -0
- package/dist/CollapsibleCard.d.ts +9 -0
- package/dist/CollapsibleCard.js +9 -0
- package/dist/MessageList.d.ts +3 -0
- package/dist/MessageList.js +32 -0
- package/dist/PendingPermissions.d.ts +3 -0
- package/dist/PendingPermissions.js +35 -0
- package/dist/StatusDot.d.ts +8 -0
- package/dist/StatusDot.js +15 -0
- package/dist/TextMessage.d.ts +5 -0
- package/dist/TextMessage.js +8 -0
- package/dist/ThinkingBlock.d.ts +5 -0
- package/dist/ThinkingBlock.js +38 -0
- package/dist/ThinkingIndicator.d.ts +3 -0
- package/dist/ThinkingIndicator.js +5 -0
- package/dist/ToolApprovalCard.d.ts +7 -0
- package/dist/ToolApprovalCard.js +11 -0
- package/dist/ToolCallCard.d.ts +5 -0
- package/dist/ToolCallCard.js +59 -0
- package/dist/UserQuestionCard.d.ts +6 -0
- package/dist/UserQuestionCard.js +120 -0
- package/dist/approval-matching.d.ts +13 -0
- package/dist/approval-matching.js +30 -0
- package/dist/cn.d.ts +2 -0
- package/dist/cn.js +5 -0
- package/dist/icons.d.ts +7 -0
- package/dist/icons.js +8 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +28 -0
- package/dist/markdown-overrides.d.ts +2 -0
- package/dist/markdown-overrides.js +8 -0
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +10 -0
- package/dist/store.d.ts +34 -0
- package/dist/store.js +141 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/use-agent.d.ts +12 -0
- package/dist/use-agent.js +119 -0
- package/dist/widgets/AskUserQuestionWidget.d.ts +1 -0
- package/dist/widgets/AskUserQuestionWidget.js +42 -0
- package/dist/widgets/BashWidget.d.ts +1 -0
- package/dist/widgets/BashWidget.js +33 -0
- package/dist/widgets/EditWidget.d.ts +1 -0
- package/dist/widgets/EditWidget.js +36 -0
- package/dist/widgets/GlobWidget.d.ts +1 -0
- package/dist/widgets/GlobWidget.js +31 -0
- package/dist/widgets/GrepWidget.d.ts +1 -0
- package/dist/widgets/GrepWidget.js +36 -0
- package/dist/widgets/NotebookEditWidget.d.ts +1 -0
- package/dist/widgets/NotebookEditWidget.js +47 -0
- package/dist/widgets/ReadWidget.d.ts +1 -0
- package/dist/widgets/ReadWidget.js +46 -0
- package/dist/widgets/TodoWriteWidget.d.ts +1 -0
- package/dist/widgets/TodoWriteWidget.js +40 -0
- package/dist/widgets/WebFetchWidget.d.ts +1 -0
- package/dist/widgets/WebFetchWidget.js +48 -0
- package/dist/widgets/WebSearchWidget.d.ts +1 -0
- package/dist/widgets/WebSearchWidget.js +85 -0
- package/dist/widgets/WriteWidget.d.ts +1 -0
- package/dist/widgets/WriteWidget.js +30 -0
- package/package.json +45 -0
- package/src/theme.css +170 -0
package/dist/cn.d.ts
ADDED
package/dist/cn.js
ADDED
package/dist/icons.d.ts
ADDED
package/dist/icons.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./cn.js";
|
|
3
|
+
export function ChevronIcon({ open, className }) {
|
|
4
|
+
return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: cn("h-3 w-3 transition-transform", open && "rotate-90", className), "aria-hidden": "true", children: _jsx("path", { d: "m9 18 6-6-6-6" }) }));
|
|
5
|
+
}
|
|
6
|
+
export function SendIcon({ className }) {
|
|
7
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: cn("h-4 w-4", className), "aria-hidden": "true", children: [_jsx("path", { d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" }), _jsx("path", { d: "m21.854 2.147-10.94 10.939" })] }));
|
|
8
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./widgets/AskUserQuestionWidget.js";
|
|
2
|
+
import "./widgets/BashWidget.js";
|
|
3
|
+
import "./widgets/EditWidget.js";
|
|
4
|
+
import "./widgets/GlobWidget.js";
|
|
5
|
+
import "./widgets/GrepWidget.js";
|
|
6
|
+
import "./widgets/NotebookEditWidget.js";
|
|
7
|
+
import "./widgets/ReadWidget.js";
|
|
8
|
+
import "./widgets/TodoWriteWidget.js";
|
|
9
|
+
import "./widgets/WebFetchWidget.js";
|
|
10
|
+
import "./widgets/WebSearchWidget.js";
|
|
11
|
+
import "./widgets/WriteWidget.js";
|
|
12
|
+
export type { ChatMessage, CustomEvent, PermissionRequest, PermissionResponse, SSEEvent, ToolApprovalRequest, ToolApprovalResponse, ToolCallInfo, ToolCallPhase, UserQuestion, UserQuestionOption, UserQuestionRequest, UserQuestionResponse, } from "@neeter/types";
|
|
13
|
+
export { AgentProvider, useAgentContext, useChatStore } from "./AgentProvider.js";
|
|
14
|
+
export { ChatInput } from "./ChatInput.js";
|
|
15
|
+
export { CollapsibleCard } from "./CollapsibleCard.js";
|
|
16
|
+
export { cn } from "./cn.js";
|
|
17
|
+
export { MessageList } from "./MessageList.js";
|
|
18
|
+
export { PendingPermissions } from "./PendingPermissions.js";
|
|
19
|
+
export { getWidget, registerWidget, stripMcpPrefix } from "./registry.js";
|
|
20
|
+
export { StatusDot } from "./StatusDot.js";
|
|
21
|
+
export { type ChatStore, type ChatStoreShape, createChatStore } from "./store.js";
|
|
22
|
+
export { TextMessage } from "./TextMessage.js";
|
|
23
|
+
export { ThinkingBlock } from "./ThinkingBlock.js";
|
|
24
|
+
export { ThinkingIndicator } from "./ThinkingIndicator.js";
|
|
25
|
+
export { ToolApprovalCard } from "./ToolApprovalCard.js";
|
|
26
|
+
export { ToolCallCard } from "./ToolCallCard.js";
|
|
27
|
+
export type { WidgetProps, WidgetRegistration } from "./types.js";
|
|
28
|
+
export { UserQuestionCard } from "./UserQuestionCard.js";
|
|
29
|
+
export { type UseAgentConfig, type UseAgentReturn, useAgent } from "./use-agent.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Built-in widgets (side-effect: auto-registers)
|
|
2
|
+
import "./widgets/AskUserQuestionWidget.js";
|
|
3
|
+
import "./widgets/BashWidget.js";
|
|
4
|
+
import "./widgets/EditWidget.js";
|
|
5
|
+
import "./widgets/GlobWidget.js";
|
|
6
|
+
import "./widgets/GrepWidget.js";
|
|
7
|
+
import "./widgets/NotebookEditWidget.js";
|
|
8
|
+
import "./widgets/ReadWidget.js";
|
|
9
|
+
import "./widgets/TodoWriteWidget.js";
|
|
10
|
+
import "./widgets/WebFetchWidget.js";
|
|
11
|
+
import "./widgets/WebSearchWidget.js";
|
|
12
|
+
import "./widgets/WriteWidget.js";
|
|
13
|
+
export { AgentProvider, useAgentContext, useChatStore } from "./AgentProvider.js";
|
|
14
|
+
export { ChatInput } from "./ChatInput.js";
|
|
15
|
+
export { CollapsibleCard } from "./CollapsibleCard.js";
|
|
16
|
+
export { cn } from "./cn.js";
|
|
17
|
+
export { MessageList } from "./MessageList.js";
|
|
18
|
+
export { PendingPermissions } from "./PendingPermissions.js";
|
|
19
|
+
export { getWidget, registerWidget, stripMcpPrefix } from "./registry.js";
|
|
20
|
+
export { StatusDot } from "./StatusDot.js";
|
|
21
|
+
export { createChatStore } from "./store.js";
|
|
22
|
+
export { TextMessage } from "./TextMessage.js";
|
|
23
|
+
export { ThinkingBlock } from "./ThinkingBlock.js";
|
|
24
|
+
export { ThinkingIndicator } from "./ThinkingIndicator.js";
|
|
25
|
+
export { ToolApprovalCard } from "./ToolApprovalCard.js";
|
|
26
|
+
export { ToolCallCard } from "./ToolCallCard.js";
|
|
27
|
+
export { UserQuestionCard } from "./UserQuestionCard.js";
|
|
28
|
+
export { useAgent } from "./use-agent.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export const markdownComponents = {
|
|
3
|
+
p: ({ children }) => _jsx("p", { className: "mb-2 last:mb-0", children: children }),
|
|
4
|
+
ul: ({ children }) => _jsx("ul", { className: "mb-2 ml-4 list-disc last:mb-0", children: children }),
|
|
5
|
+
ol: ({ children }) => _jsx("ol", { className: "mb-2 ml-4 list-decimal last:mb-0", children: children }),
|
|
6
|
+
li: ({ children }) => _jsx("li", { className: "mb-0.5", children: children }),
|
|
7
|
+
strong: ({ children }) => _jsx("strong", { className: "font-semibold", children: children }),
|
|
8
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WidgetRegistration } from "./types.js";
|
|
2
|
+
export declare function registerWidget<TResult>(reg: WidgetRegistration<TResult>): void;
|
|
3
|
+
export declare function getWidget(toolName: string): WidgetRegistration | undefined;
|
|
4
|
+
export declare function stripMcpPrefix(name: string): string;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const widgets = new Map();
|
|
2
|
+
export function registerWidget(reg) {
|
|
3
|
+
widgets.set(reg.toolName, reg);
|
|
4
|
+
}
|
|
5
|
+
export function getWidget(toolName) {
|
|
6
|
+
return widgets.get(toolName);
|
|
7
|
+
}
|
|
8
|
+
export function stripMcpPrefix(name) {
|
|
9
|
+
return name.replace(/^mcp__[^_]+__/, "");
|
|
10
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ChatMessage, PermissionRequest } from "@neeter/types";
|
|
2
|
+
import { type StoreApi } from "zustand/vanilla";
|
|
3
|
+
interface ChatStoreState {
|
|
4
|
+
sessionId: string | null;
|
|
5
|
+
messages: ChatMessage[];
|
|
6
|
+
isStreaming: boolean;
|
|
7
|
+
isThinking: boolean;
|
|
8
|
+
streamingText: string;
|
|
9
|
+
streamingThinking: string;
|
|
10
|
+
pendingPermissions: PermissionRequest[];
|
|
11
|
+
}
|
|
12
|
+
interface ChatStoreActions {
|
|
13
|
+
setSessionId: (id: string) => void;
|
|
14
|
+
addUserMessage: (text: string) => void;
|
|
15
|
+
appendStreamingText: (text: string) => void;
|
|
16
|
+
appendStreamingThinking: (text: string) => void;
|
|
17
|
+
flushStreamingText: () => void;
|
|
18
|
+
flushStreamingThinking: () => void;
|
|
19
|
+
addSystemMessage: (text: string) => void;
|
|
20
|
+
startToolCall: (toolUseId: string, name: string) => void;
|
|
21
|
+
appendToolInput: (toolUseId: string, partialJson: string) => void;
|
|
22
|
+
finalizeToolCall: (toolUseId: string, name: string, input: Record<string, unknown>) => void;
|
|
23
|
+
completeToolCall: (toolUseId: string, result: string) => void;
|
|
24
|
+
errorToolCall: (toolUseId: string, error: string) => void;
|
|
25
|
+
setStreaming: (v: boolean) => void;
|
|
26
|
+
setThinking: (v: boolean) => void;
|
|
27
|
+
addPermissionRequest: (request: PermissionRequest) => void;
|
|
28
|
+
removePermissionRequest: (requestId: string) => void;
|
|
29
|
+
reset: () => void;
|
|
30
|
+
}
|
|
31
|
+
export type ChatStoreShape = ChatStoreState & ChatStoreActions;
|
|
32
|
+
export type ChatStore = StoreApi<ChatStoreShape>;
|
|
33
|
+
export declare function createChatStore(): ChatStore;
|
|
34
|
+
export {};
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { immer } from "zustand/middleware/immer";
|
|
2
|
+
import { createStore } from "zustand/vanilla";
|
|
3
|
+
let nextId = 0;
|
|
4
|
+
function findToolCall(messages, toolUseId) {
|
|
5
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
6
|
+
const tc = messages[i].toolCalls;
|
|
7
|
+
if (tc?.length) {
|
|
8
|
+
const match = tc.find((t) => t.id === toolUseId);
|
|
9
|
+
if (match)
|
|
10
|
+
return match;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export function createChatStore() {
|
|
16
|
+
return createStore()(immer((set) => ({
|
|
17
|
+
sessionId: null,
|
|
18
|
+
messages: [],
|
|
19
|
+
isStreaming: false,
|
|
20
|
+
isThinking: false,
|
|
21
|
+
streamingText: "",
|
|
22
|
+
streamingThinking: "",
|
|
23
|
+
pendingPermissions: [],
|
|
24
|
+
setSessionId: (id) => set((s) => {
|
|
25
|
+
s.sessionId = id;
|
|
26
|
+
}),
|
|
27
|
+
addUserMessage: (text) => set((s) => {
|
|
28
|
+
s.messages.push({
|
|
29
|
+
id: `msg-${++nextId}`,
|
|
30
|
+
role: "user",
|
|
31
|
+
content: text,
|
|
32
|
+
});
|
|
33
|
+
}),
|
|
34
|
+
addSystemMessage: (text) => set((s) => {
|
|
35
|
+
s.messages.push({
|
|
36
|
+
id: `msg-${++nextId}`,
|
|
37
|
+
role: "system",
|
|
38
|
+
content: text,
|
|
39
|
+
});
|
|
40
|
+
}),
|
|
41
|
+
appendStreamingText: (text) => set((s) => {
|
|
42
|
+
s.streamingText += text;
|
|
43
|
+
}),
|
|
44
|
+
appendStreamingThinking: (text) => set((s) => {
|
|
45
|
+
s.streamingThinking += text;
|
|
46
|
+
}),
|
|
47
|
+
flushStreamingThinking: () => set((s) => {
|
|
48
|
+
if (s.streamingThinking) {
|
|
49
|
+
const last = s.messages[s.messages.length - 1];
|
|
50
|
+
if (last?.role === "assistant" && !last.toolCalls?.length) {
|
|
51
|
+
last.thinking = (last.thinking ?? "") + s.streamingThinking;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
s.messages.push({
|
|
55
|
+
id: `msg-${++nextId}`,
|
|
56
|
+
role: "assistant",
|
|
57
|
+
content: "",
|
|
58
|
+
thinking: s.streamingThinking,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
s.streamingThinking = "";
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
flushStreamingText: () => set((s) => {
|
|
65
|
+
if (s.streamingText) {
|
|
66
|
+
const last = s.messages[s.messages.length - 1];
|
|
67
|
+
if (last?.role === "assistant" && !last.toolCalls?.length) {
|
|
68
|
+
last.content += s.streamingText;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
s.messages.push({
|
|
72
|
+
id: `msg-${++nextId}`,
|
|
73
|
+
role: "assistant",
|
|
74
|
+
content: s.streamingText,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
s.streamingText = "";
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
startToolCall: (toolUseId, name) => set((s) => {
|
|
81
|
+
s.messages.push({
|
|
82
|
+
id: `msg-${++nextId}`,
|
|
83
|
+
role: "assistant",
|
|
84
|
+
content: "",
|
|
85
|
+
toolCalls: [{ id: toolUseId, name, input: {}, status: "pending" }],
|
|
86
|
+
});
|
|
87
|
+
}),
|
|
88
|
+
appendToolInput: (toolUseId, partialJson) => set((s) => {
|
|
89
|
+
const tc = findToolCall(s.messages, toolUseId);
|
|
90
|
+
if (tc) {
|
|
91
|
+
tc.partialInput = (tc.partialInput ?? "") + partialJson;
|
|
92
|
+
tc.status = "streaming_input";
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
finalizeToolCall: (toolUseId, name, input) => set((s) => {
|
|
96
|
+
const tc = findToolCall(s.messages, toolUseId);
|
|
97
|
+
if (tc) {
|
|
98
|
+
tc.name = name;
|
|
99
|
+
tc.input = input;
|
|
100
|
+
tc.status = "running";
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
completeToolCall: (toolUseId, result) => set((s) => {
|
|
104
|
+
const tc = findToolCall(s.messages, toolUseId);
|
|
105
|
+
if (tc && tc.status !== "error") {
|
|
106
|
+
tc.result = result;
|
|
107
|
+
tc.status = "complete";
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
errorToolCall: (toolUseId, error) => set((s) => {
|
|
111
|
+
const tc = findToolCall(s.messages, toolUseId);
|
|
112
|
+
if (tc) {
|
|
113
|
+
tc.error = error;
|
|
114
|
+
tc.status = "error";
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
setStreaming: (v) => set((s) => {
|
|
118
|
+
s.isStreaming = v;
|
|
119
|
+
}),
|
|
120
|
+
setThinking: (v) => set((s) => {
|
|
121
|
+
s.isThinking = v;
|
|
122
|
+
}),
|
|
123
|
+
addPermissionRequest: (request) => set((s) => {
|
|
124
|
+
if (!s.pendingPermissions.some((p) => p.requestId === request.requestId)) {
|
|
125
|
+
s.pendingPermissions.push(request);
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
removePermissionRequest: (requestId) => set((s) => {
|
|
129
|
+
s.pendingPermissions = s.pendingPermissions.filter((p) => p.requestId !== requestId);
|
|
130
|
+
}),
|
|
131
|
+
reset: () => set((s) => {
|
|
132
|
+
s.sessionId = null;
|
|
133
|
+
s.messages = [];
|
|
134
|
+
s.isStreaming = false;
|
|
135
|
+
s.isThinking = false;
|
|
136
|
+
s.streamingText = "";
|
|
137
|
+
s.streamingThinking = "";
|
|
138
|
+
s.pendingPermissions = [];
|
|
139
|
+
}),
|
|
140
|
+
})));
|
|
141
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ToolCallPhase } from "@neeter/types";
|
|
2
|
+
import type { ComponentType } from "react";
|
|
3
|
+
export interface WidgetProps<TResult = unknown> {
|
|
4
|
+
phase: ToolCallPhase;
|
|
5
|
+
toolUseId: string;
|
|
6
|
+
input: Record<string, unknown>;
|
|
7
|
+
partialInput?: string;
|
|
8
|
+
result?: TResult;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface WidgetRegistration<TResult = unknown> {
|
|
12
|
+
toolName: string;
|
|
13
|
+
label: string;
|
|
14
|
+
richLabel?: (result: TResult, input: Record<string, unknown>) => string | null;
|
|
15
|
+
inputRenderer?: ComponentType<{
|
|
16
|
+
input: Record<string, unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
component: ComponentType<WidgetProps<TResult>>;
|
|
19
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CustomEvent, PermissionResponse } from "@neeter/types";
|
|
2
|
+
import type { ChatStore } from "./store.js";
|
|
3
|
+
export interface UseAgentConfig {
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
onCustomEvent?: (event: CustomEvent) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface UseAgentReturn {
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
sendMessage: (text: string) => Promise<void>;
|
|
10
|
+
respondToPermission: (response: PermissionResponse) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function useAgent(store: ChatStore, config?: UseAgentConfig): UseAgentReturn;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
export function useAgent(store, config) {
|
|
3
|
+
const endpoint = config?.endpoint ?? "/api";
|
|
4
|
+
const onCustomEvent = config?.onCustomEvent;
|
|
5
|
+
const eventSourceRef = useRef(null);
|
|
6
|
+
const sessionId = useSyncExternalStore(store.subscribe, () => store.getState().sessionId);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
let cancelled = false;
|
|
9
|
+
async function init() {
|
|
10
|
+
const res = await fetch(`${endpoint}/sessions`, { method: "POST" });
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
if (!cancelled)
|
|
13
|
+
store.getState().setSessionId(data.sessionId);
|
|
14
|
+
}
|
|
15
|
+
init();
|
|
16
|
+
return () => {
|
|
17
|
+
cancelled = true;
|
|
18
|
+
};
|
|
19
|
+
}, [endpoint, store]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!sessionId)
|
|
22
|
+
return;
|
|
23
|
+
const es = new EventSource(`${endpoint}/sessions/${sessionId}/events`);
|
|
24
|
+
eventSourceRef.current = es;
|
|
25
|
+
es.addEventListener("message_start", () => {
|
|
26
|
+
store.getState().setThinking(true);
|
|
27
|
+
});
|
|
28
|
+
es.addEventListener("thinking_delta", (e) => {
|
|
29
|
+
const { text } = JSON.parse(e.data);
|
|
30
|
+
store.getState().appendStreamingThinking(text);
|
|
31
|
+
});
|
|
32
|
+
es.addEventListener("text_delta", (e) => {
|
|
33
|
+
store.getState().setThinking(false);
|
|
34
|
+
store.getState().flushStreamingThinking();
|
|
35
|
+
const { text } = JSON.parse(e.data);
|
|
36
|
+
store.getState().appendStreamingText(text);
|
|
37
|
+
});
|
|
38
|
+
es.addEventListener("tool_start", (e) => {
|
|
39
|
+
store.getState().setThinking(false);
|
|
40
|
+
store.getState().flushStreamingThinking();
|
|
41
|
+
store.getState().flushStreamingText();
|
|
42
|
+
const { id, name } = JSON.parse(e.data);
|
|
43
|
+
store.getState().startToolCall(id, name);
|
|
44
|
+
});
|
|
45
|
+
es.addEventListener("tool_input_delta", (e) => {
|
|
46
|
+
const { id, partialJson } = JSON.parse(e.data);
|
|
47
|
+
store.getState().appendToolInput(id, partialJson);
|
|
48
|
+
});
|
|
49
|
+
es.addEventListener("tool_call", (e) => {
|
|
50
|
+
store.getState().flushStreamingText();
|
|
51
|
+
const { id, name, input } = JSON.parse(e.data);
|
|
52
|
+
store.getState().finalizeToolCall(id, name, input);
|
|
53
|
+
});
|
|
54
|
+
es.addEventListener("tool_result", (e) => {
|
|
55
|
+
const { toolUseId, result } = JSON.parse(e.data);
|
|
56
|
+
store.getState().completeToolCall(toolUseId, result);
|
|
57
|
+
if (store.getState().isStreaming) {
|
|
58
|
+
store.getState().setThinking(true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
es.addEventListener("permission_request", (e) => {
|
|
62
|
+
const request = JSON.parse(e.data);
|
|
63
|
+
store.getState().addPermissionRequest(request);
|
|
64
|
+
});
|
|
65
|
+
es.addEventListener("session_error", (e) => {
|
|
66
|
+
store.getState().flushStreamingThinking();
|
|
67
|
+
store.getState().flushStreamingText();
|
|
68
|
+
store.getState().setThinking(false);
|
|
69
|
+
const { subtype } = JSON.parse(e.data);
|
|
70
|
+
store.getState().addSystemMessage(`Session ended: ${subtype}`);
|
|
71
|
+
store.getState().setStreaming(false);
|
|
72
|
+
});
|
|
73
|
+
es.addEventListener("turn_complete", () => {
|
|
74
|
+
store.getState().flushStreamingThinking();
|
|
75
|
+
store.getState().flushStreamingText();
|
|
76
|
+
store.getState().setThinking(false);
|
|
77
|
+
store.getState().setStreaming(false);
|
|
78
|
+
});
|
|
79
|
+
es.addEventListener("error", () => {
|
|
80
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
81
|
+
store.getState().flushStreamingThinking();
|
|
82
|
+
store.getState().flushStreamingText();
|
|
83
|
+
store.getState().setStreaming(false);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (onCustomEvent) {
|
|
87
|
+
es.addEventListener("custom", (e) => {
|
|
88
|
+
onCustomEvent(JSON.parse(e.data));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
es.close();
|
|
93
|
+
eventSourceRef.current = null;
|
|
94
|
+
};
|
|
95
|
+
}, [sessionId, endpoint, store, onCustomEvent]);
|
|
96
|
+
const sendMessage = useCallback(async (text) => {
|
|
97
|
+
if (!sessionId)
|
|
98
|
+
return;
|
|
99
|
+
store.getState().addUserMessage(text);
|
|
100
|
+
store.getState().setStreaming(true);
|
|
101
|
+
store.getState().setThinking(true);
|
|
102
|
+
await fetch(`${endpoint}/sessions/${sessionId}/messages`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ text }),
|
|
106
|
+
});
|
|
107
|
+
}, [sessionId, endpoint, store]);
|
|
108
|
+
const respondToPermission = useCallback(async (response) => {
|
|
109
|
+
if (!sessionId)
|
|
110
|
+
return;
|
|
111
|
+
store.getState().removePermissionRequest(response.requestId);
|
|
112
|
+
await fetch(`${endpoint}/sessions/${sessionId}/permissions`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify(response),
|
|
116
|
+
});
|
|
117
|
+
}, [sessionId, endpoint, store]);
|
|
118
|
+
return { sessionId, sendMessage, respondToPermission };
|
|
119
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { registerWidget } from "../registry.js";
|
|
3
|
+
function parseAnswers(result) {
|
|
4
|
+
if (typeof result !== "string")
|
|
5
|
+
return null;
|
|
6
|
+
const answers = {};
|
|
7
|
+
// Format: "question"="answer"
|
|
8
|
+
const re = /"(.+?)"="(.+?)"/g;
|
|
9
|
+
for (let match = re.exec(result); match !== null; match = re.exec(result)) {
|
|
10
|
+
answers[match[1]] = match[2];
|
|
11
|
+
}
|
|
12
|
+
if (Object.keys(answers).length === 0)
|
|
13
|
+
return null;
|
|
14
|
+
return answers;
|
|
15
|
+
}
|
|
16
|
+
function AskUserQuestionWidget({ input, result }) {
|
|
17
|
+
const questions = (input.questions ?? []);
|
|
18
|
+
const answers = parseAnswers(result);
|
|
19
|
+
if (!questions.length || !answers)
|
|
20
|
+
return null;
|
|
21
|
+
return (_jsx("div", { className: "space-y-1.5 py-1 text-xs", children: questions.map((q) => {
|
|
22
|
+
const answer = answers[q.question];
|
|
23
|
+
return (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: q.question }), answer && _jsx("span", { className: "ml-1.5 font-medium text-foreground", children: answer })] }, q.question));
|
|
24
|
+
}) }));
|
|
25
|
+
}
|
|
26
|
+
registerWidget({
|
|
27
|
+
toolName: "AskUserQuestion",
|
|
28
|
+
label: "Question",
|
|
29
|
+
richLabel: (result, input) => {
|
|
30
|
+
const questions = (input.questions ?? []);
|
|
31
|
+
const answers = parseAnswers(result);
|
|
32
|
+
if (!questions.length || !answers)
|
|
33
|
+
return null;
|
|
34
|
+
const first = questions[0];
|
|
35
|
+
const answer = answers[first.question];
|
|
36
|
+
if (!answer)
|
|
37
|
+
return null;
|
|
38
|
+
const label = first.header ?? "Answer";
|
|
39
|
+
return `${label}: ${answer}`;
|
|
40
|
+
},
|
|
41
|
+
component: AskUserQuestionWidget,
|
|
42
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { registerWidget } from "../registry.js";
|
|
3
|
+
function BashInputRenderer({ input }) {
|
|
4
|
+
const command = typeof input.command === "string" ? input.command : null;
|
|
5
|
+
const description = typeof input.description === "string" ? input.description : null;
|
|
6
|
+
if (!command)
|
|
7
|
+
return null;
|
|
8
|
+
return (_jsxs("div", { className: "mt-1.5 space-y-1", children: [description && _jsx("div", { className: "text-xs text-muted-foreground", children: description }), _jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: command }) })] }));
|
|
9
|
+
}
|
|
10
|
+
function BashWidget({ result, input, phase }) {
|
|
11
|
+
const command = typeof input.command === "string" ? input.command : null;
|
|
12
|
+
if (phase === "running" || phase === "pending") {
|
|
13
|
+
return (_jsxs("div", { className: "py-1 space-y-1 text-xs", children: [command && (_jsx("pre", { className: "leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsxs("code", { children: ["$ ", command] }) })), _jsx("span", { className: "animate-pulse text-muted-foreground", children: "Running\u2026" })] }));
|
|
14
|
+
}
|
|
15
|
+
if (typeof result !== "string" || !result)
|
|
16
|
+
return null;
|
|
17
|
+
return (_jsx("div", { className: "py-1 text-xs", children: _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsxs("code", { children: [command && (_jsxs("span", { className: "text-muted-foreground", children: ["$ ", command, "\n"] })), result] }) }) }));
|
|
18
|
+
}
|
|
19
|
+
registerWidget({
|
|
20
|
+
toolName: "Bash",
|
|
21
|
+
label: "Bash",
|
|
22
|
+
richLabel: (_r, input) => {
|
|
23
|
+
const desc = typeof input.description === "string" ? input.description : null;
|
|
24
|
+
const cmd = typeof input.command === "string" ? input.command : null;
|
|
25
|
+
if (desc)
|
|
26
|
+
return desc;
|
|
27
|
+
if (cmd)
|
|
28
|
+
return cmd.length > 60 ? `${cmd.slice(0, 57)}...` : cmd;
|
|
29
|
+
return null;
|
|
30
|
+
},
|
|
31
|
+
inputRenderer: BashInputRenderer,
|
|
32
|
+
component: BashWidget,
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { registerWidget } from "../registry.js";
|
|
3
|
+
function basename(filePath) {
|
|
4
|
+
return filePath.split("/").pop() ?? filePath;
|
|
5
|
+
}
|
|
6
|
+
function EditInputRenderer({ input }) {
|
|
7
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : null;
|
|
8
|
+
const oldStr = typeof input.old_string === "string" ? input.old_string : null;
|
|
9
|
+
const newStr = typeof input.new_string === "string" ? input.new_string : null;
|
|
10
|
+
if (!filePath || oldStr == null || newStr == null)
|
|
11
|
+
return null;
|
|
12
|
+
return (_jsxs("div", { className: "mt-1.5 space-y-1.5", children: [_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate", children: filePath }), _jsxs("div", { className: "text-[11px] leading-snug rounded overflow-hidden border border-border", children: [oldStr && (_jsx("pre", { className: "bg-red-500/10 text-red-700 dark:text-red-400 px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: oldStr.split("\n").map((line, i) => (
|
|
13
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static split lines
|
|
14
|
+
_jsxs("span", { children: [i > 0 && "\n", "- ", line] }, i))) }) })), newStr && (_jsx("pre", { className: "bg-green-500/10 text-green-700 dark:text-green-400 px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: newStr.split("\n").map((line, i) => (
|
|
15
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static split lines
|
|
16
|
+
_jsxs("span", { children: [i > 0 && "\n", "+ ", line] }, i))) }) }))] })] }));
|
|
17
|
+
}
|
|
18
|
+
function EditWidget({ result, input, phase }) {
|
|
19
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : null;
|
|
20
|
+
if (phase === "running" || phase === "pending") {
|
|
21
|
+
return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Editing ", filePath ? basename(filePath) : "file", "\u2026"] }) }));
|
|
22
|
+
}
|
|
23
|
+
if (typeof result !== "string" || !result)
|
|
24
|
+
return null;
|
|
25
|
+
return _jsx("div", { className: "py-1 text-xs text-muted-foreground", children: result });
|
|
26
|
+
}
|
|
27
|
+
registerWidget({
|
|
28
|
+
toolName: "Edit",
|
|
29
|
+
label: "Edit",
|
|
30
|
+
richLabel: (_r, input) => {
|
|
31
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : null;
|
|
32
|
+
return filePath ? basename(filePath) : null;
|
|
33
|
+
},
|
|
34
|
+
inputRenderer: EditInputRenderer,
|
|
35
|
+
component: EditWidget,
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { registerWidget } from "../registry.js";
|
|
3
|
+
function GlobInputRenderer({ input }) {
|
|
4
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : null;
|
|
5
|
+
const path = typeof input.path === "string" ? input.path : null;
|
|
6
|
+
if (!pattern)
|
|
7
|
+
return null;
|
|
8
|
+
return (_jsx("div", { className: "mt-1.5 space-y-1", children: _jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsxs("code", { children: [pattern, path ? ` in ${path}` : ""] }) }) }));
|
|
9
|
+
}
|
|
10
|
+
function GlobWidget({ result, input, phase }) {
|
|
11
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : null;
|
|
12
|
+
if (phase === "running" || phase === "pending") {
|
|
13
|
+
return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Matching ", pattern ? pattern : "files", "\u2026"] }) }));
|
|
14
|
+
}
|
|
15
|
+
if (typeof result !== "string" || !result)
|
|
16
|
+
return null;
|
|
17
|
+
const lines = result.split("\n").filter(Boolean);
|
|
18
|
+
return (_jsxs("div", { className: "py-1 text-xs", children: [_jsxs("div", { className: "text-muted-foreground mb-1", children: [lines.length, " ", lines.length === 1 ? "match" : "matches"] }), _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsx("code", { children: lines.join("\n") }) })] }));
|
|
19
|
+
}
|
|
20
|
+
registerWidget({
|
|
21
|
+
toolName: "Glob",
|
|
22
|
+
label: "Glob",
|
|
23
|
+
richLabel: (_r, input) => {
|
|
24
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : null;
|
|
25
|
+
if (pattern)
|
|
26
|
+
return pattern.length > 50 ? `${pattern.slice(0, 47)}...` : pattern;
|
|
27
|
+
return null;
|
|
28
|
+
},
|
|
29
|
+
inputRenderer: GlobInputRenderer,
|
|
30
|
+
component: GlobWidget,
|
|
31
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|