Sign In

@neeter/react

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@neeter/react - npm Package Compare versions

Comparing version
0.7.1
to
0.8.0
+3
-1
dist/ChatInput.d.ts

@@ -1,3 +0,5 @@

export declare function ChatInput({ onSend, placeholder, disabled, className, }: {
export declare function ChatInput({ onSend, onStop, isStreaming, placeholder, disabled, className, }: {
onSend: (text: string) => void;
onStop?: () => void;
isStreaming?: boolean;
placeholder?: string;

@@ -4,0 +6,0 @@ disabled?: boolean;

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useRef, useState } from "react";
import { cn } from "./cn.js";
import { SendIcon } from "./icons.js";
export function ChatInput({ onSend, placeholder = "Type a message...", disabled, className, }) {
import { SendIcon, StopIcon } from "./icons.js";
export function ChatInput({ onSend, onStop, isStreaming, placeholder = "Type a message...", disabled, className, }) {
const [text, setText] = useState("");

@@ -40,3 +40,3 @@ const textareaRef = useRef(null);

autoResize();
}, onKeyDown: handleKeyDown, placeholder: placeholder, rows: 1, className: "flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring max-h-40 overflow-y-hidden" }), _jsx("button", { type: "button", onClick: handleSend, disabled: !text.trim() || isDisabled, className: "inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:pointer-events-none", children: _jsx(SendIcon, {}) })] }));
}, onKeyDown: handleKeyDown, placeholder: placeholder, rows: 1, className: "flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring max-h-40 overflow-y-hidden" }), isStreaming && onStop ? (_jsx("button", { type: "button", onClick: onStop, className: "inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90", children: _jsx(StopIcon, {}) })) : (_jsx("button", { type: "button", onClick: handleSend, disabled: !text.trim() || isDisabled, className: "inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:pointer-events-none", children: _jsx(SendIcon, {}) }))] }));
}

@@ -5,4 +5,7 @@ export declare function ChevronIcon({ open, className }: {

}): import("react/jsx-runtime").JSX.Element;
export declare function StopIcon({ className }: {
className?: string;
}): import("react/jsx-runtime").JSX.Element;
export declare function SendIcon({ className }: {
className?: string;
}): import("react/jsx-runtime").JSX.Element;

@@ -6,4 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";

}
export function StopIcon({ className }) {
return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: cn("h-4 w-4", className), "aria-hidden": "true", children: _jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1" }) }));
}
export function SendIcon({ className }) {
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" })] }));
}

@@ -11,2 +11,4 @@ import type { ChatMessage, PermissionRequest } from "@neeter/types";

pendingPermissions: PermissionRequest[];
totalCost: number;
totalTurns: number;
}

@@ -30,2 +32,4 @@ interface ChatStoreActions {

removePermissionRequest: (requestId: string) => void;
addCost: (cost: number, turns: number) => void;
cancelInflightToolCalls: () => void;
reset: () => void;

@@ -32,0 +36,0 @@ }

@@ -24,2 +24,4 @@ import { immer } from "zustand/middleware/immer";

pendingPermissions: [],
totalCost: 0,
totalTurns: 0,
setSessionId: (id) => set((s) => {

@@ -132,2 +134,20 @@ s.sessionId = id;

}),
addCost: (cost, turns) => set((s) => {
s.totalCost += cost;
s.totalTurns += turns;
}),
cancelInflightToolCalls: () => set((s) => {
for (const msg of s.messages) {
if (msg.toolCalls) {
for (const tc of msg.toolCalls) {
if (tc.status === "pending" ||
tc.status === "streaming_input" ||
tc.status === "running") {
tc.status = "error";
tc.error = "Interrupted";
}
}
}
}
}),
reset: () => set((s) => {

@@ -141,4 +161,6 @@ s.sessionId = null;

s.pendingPermissions = [];
s.totalCost = 0;
s.totalTurns = 0;
}),
})));
}

@@ -10,4 +10,5 @@ import type { CustomEvent, PermissionResponse } from "@neeter/types";

sendMessage: (text: string) => Promise<void>;
stopSession: () => Promise<void>;
respondToPermission: (response: PermissionResponse) => Promise<void>;
}
export declare function useAgent(store: ChatStore, config?: UseAgentConfig): UseAgentReturn;

@@ -6,2 +6,3 @@ import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";

const eventSourceRef = useRef(null);
const abortedRef = useRef(false);
const sessionId = useSyncExternalStore(store.subscribe, () => store.getState().sessionId);

@@ -26,2 +27,3 @@ useEffect(() => {

eventSourceRef.current = es;
abortedRef.current = false;
es.addEventListener("message_start", () => {

@@ -71,7 +73,15 @@ store.getState().setThinking(true);

store.getState().setThinking(false);
const { subtype } = JSON.parse(e.data);
store.getState().addSystemMessage(`Session ended: ${subtype}`);
store.getState().setStreaming(false);
if (abortedRef.current) {
store.getState().cancelInflightToolCalls();
store.getState().addSystemMessage("Interrupted");
abortedRef.current = false;
}
else {
const { subtype } = JSON.parse(e.data);
store.getState().addSystemMessage(`Session ended: ${subtype}`);
}
});
es.addEventListener("turn_complete", () => {
es.addEventListener("turn_complete", (e) => {
const { cost, numTurns } = JSON.parse(e.data);
store.getState().flushStreamingThinking();

@@ -81,9 +91,26 @@ store.getState().flushStreamingText();

store.getState().setStreaming(false);
store.getState().addCost(cost ?? 0, numTurns ?? 0);
if (abortedRef.current) {
store.getState().cancelInflightToolCalls();
store.getState().addSystemMessage("Interrupted");
abortedRef.current = false;
}
});
es.addEventListener("error", () => {
if (es.readyState === EventSource.CLOSED) {
if (abortedRef.current) {
store.getState().flushStreamingThinking();
store.getState().flushStreamingText();
store.getState().cancelInflightToolCalls();
store.getState().setThinking(false);
store.getState().setStreaming(false);
store.getState().addSystemMessage("Interrupted");
abortedRef.current = false;
es.close();
}
else if (es.readyState === EventSource.CLOSED) {
store.getState().flushStreamingThinking();
store.getState().flushStreamingText();
store.getState().setThinking(false);
store.getState().setStreaming(false);
}
});

@@ -112,2 +139,10 @@ if (onCustomEvent) {

}, [sessionId, endpoint, store]);
const stopSession = useCallback(async () => {
if (!sessionId)
return;
abortedRef.current = true;
store.getState().setThinking(false);
store.getState().setStreaming(false);
await fetch(`${endpoint}/sessions/${sessionId}/abort`, { method: "POST" });
}, [sessionId, endpoint, store]);
const respondToPermission = useCallback(async (response) => {

@@ -123,3 +158,3 @@ if (!sessionId)

}, [sessionId, endpoint, store]);
return { sessionId, sendMessage, respondToPermission };
return { sessionId, sendMessage, stopSession, respondToPermission };
}
{
"name": "@neeter/react",
"version": "0.7.1",
"version": "0.8.0",
"description": "React components and hooks for building chat UIs on top of the Claude Agent SDK",

@@ -28,3 +28,3 @@ "license": "MIT",

"tailwind-merge": "^3.4.0",
"@neeter/types": "0.7.1"
"@neeter/types": "0.8.0"
},

@@ -31,0 +31,0 @@ "peerDependencies": {