@langgraph-js/ui 1.0.0 → 1.2.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.
@@ -0,0 +1,57 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import './JsonEditorPopup.css';
3
+
4
+ interface JsonEditorPopupProps {
5
+ isOpen: boolean;
6
+ initialJson: object;
7
+ onClose: () => void;
8
+ onSave: (jsonData: object) => void;
9
+ }
10
+
11
+ const JsonEditorPopup: React.FC<JsonEditorPopupProps> = ({ isOpen, initialJson, onClose, onSave }) => {
12
+ const [jsonString, setJsonString] = useState('');
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ setJsonString(JSON.stringify(initialJson, null, 2));
17
+ setError(null); // Reset error when initialJson changes or popup opens
18
+ }, [initialJson, isOpen]);
19
+
20
+ if (!isOpen) {
21
+ return null;
22
+ }
23
+
24
+ const handleSave = () => {
25
+ try {
26
+ const parsedJson = JSON.parse(jsonString);
27
+ onSave(parsedJson);
28
+ onClose();
29
+ } catch (e) {
30
+ setError('JSON 格式无效,请检查后重试。');
31
+ console.error("Invalid JSON format:", e);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <div className="json-editor-popup-overlay">
37
+ <div className="json-editor-popup-content">
38
+ <h2>编辑 Extra Parameters</h2>
39
+ <textarea
40
+ value={jsonString}
41
+ onChange={(e) => {
42
+ setJsonString(e.target.value);
43
+ setError(null); // Clear error on edit
44
+ }}
45
+ rows={15}
46
+ />
47
+ {error && <p className="error-message">{error}</p>}
48
+ <div className="popup-actions">
49
+ <button onClick={onClose} className="cancel-button">取消</button>
50
+ <button onClick={handleSave} className="save-button">保存</button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ );
55
+ };
56
+
57
+ export default JsonEditorPopup;
@@ -1,20 +1,24 @@
1
- import React from "react";
2
- import { RenderMessage } from "@langgraph-js/sdk";
3
- import { UsageMetadata } from "./UsageMetadata";
4
- import { getMessageContent } from "@langgraph-js/sdk";
5
- interface MessageAIProps {
6
- message: RenderMessage;
7
- }
8
-
9
- const MessageAI: React.FC<MessageAIProps> = ({ message }) => {
10
- return (
11
- <div className="message ai">
12
- <div className="message-content">
13
- <div className="message-text">{getMessageContent(message.content)}</div>
14
- {message.usage_metadata && <UsageMetadata usage_metadata={message.usage_metadata} spend_time={message.spend_time} />}
15
- </div>
16
- </div>
17
- );
18
- };
19
-
20
- export default MessageAI;
1
+ import React from "react";
2
+ import { RenderMessage } from "@langgraph-js/sdk";
3
+ import { UsageMetadata } from "./UsageMetadata";
4
+ import { getMessageContent } from "@langgraph-js/sdk";
5
+ import Markdown from 'react-markdown'
6
+ import remarkGfm from 'remark-gfm'
7
+ interface MessageAIProps {
8
+ message: RenderMessage;
9
+ }
10
+
11
+ const MessageAI: React.FC<MessageAIProps> = ({ message }) => {
12
+ return (
13
+ <div className="message ai">
14
+ <div className="message-content">
15
+ <div className="message-text markdown-body">
16
+ <Markdown remarkPlugins={[remarkGfm]}>{getMessageContent(message.content)}</Markdown>
17
+ </div>
18
+ <UsageMetadata response_metadata={message.response_metadata as any} usage_metadata={message.usage_metadata||{}} spend_time={message.spend_time} />
19
+ </div>
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export default MessageAI;
@@ -1,15 +1,55 @@
1
- import React from "react";
2
-
3
- interface MessageHumanProps {
4
- content: any;
5
- }
6
-
7
- const MessageHuman: React.FC<MessageHumanProps> = ({ content }) => {
8
- return (
9
- <div className="message human">
10
- <div className="message-content">{typeof content === "string" ? content : JSON.stringify(content)}</div>
11
- </div>
12
- );
13
- };
14
-
15
- export default MessageHuman;
1
+ import React from "react";
2
+ interface MessageHumanProps {
3
+ content: string | any[];
4
+ }
5
+
6
+ const MessageHuman: React.FC<MessageHumanProps> = ({ content }) => {
7
+ const renderContent = () => {
8
+ if (typeof content === "string") {
9
+ return <div className="message-text">{content}</div>;
10
+ }
11
+
12
+ if (Array.isArray(content)) {
13
+ return content.map((item, index) => {
14
+ switch (item.type) {
15
+ case "text":
16
+ return (
17
+ <div key={index} className="message-text">
18
+ {item.text}
19
+ </div>
20
+ );
21
+ case "image_url":
22
+ return (
23
+ <div key={index} className="message-image">
24
+ <img src={item.image_url.url} alt="用户上传的图片" style={{ maxWidth: "200px", borderRadius: "4px" }} />
25
+ </div>
26
+ );
27
+ case "audio":
28
+ return (
29
+ <div key={index} className="message-audio">
30
+ <audio controls src={item.audio_url}>
31
+ 您的浏览器不支持音频播放
32
+ </audio>
33
+ </div>
34
+ );
35
+ default:
36
+ return (
37
+ <div key={index} className="message-text">
38
+ {JSON.stringify(item)}
39
+ </div>
40
+ );
41
+ }
42
+ });
43
+ }
44
+
45
+ return <div className="message-text">{JSON.stringify(content)}</div>;
46
+ };
47
+
48
+ return (
49
+ <div className="message human">
50
+ <div className="message-content">{renderContent()}</div>
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export default MessageHuman;
@@ -1,47 +1,46 @@
1
- import React from "react";
2
- import { LangGraphClient, RenderMessage, ToolMessage } from "@langgraph-js/sdk";
3
- import { UsageMetadata } from "./UsageMetadata";
4
- interface MessageToolProps {
5
- message: ToolMessage & RenderMessage;
6
- client: LangGraphClient;
7
- getMessageContent: (content: any) => string;
8
- formatTokens: (tokens: number) => string;
9
- isCollapsed: boolean;
10
- onToggleCollapse: () => void;
11
- }
12
-
13
- const MessageTool: React.FC<MessageToolProps> = ({ message, client, getMessageContent, formatTokens, isCollapsed, onToggleCollapse }) => {
14
- console.log(message)
15
- return (
16
- <div className="message tool">
17
- {message.name === "ask_user" && !message.additional_kwargs?.done && (
18
- <div>
19
- <div>询问 {message.tool_input}</div>
20
- <input
21
- type="text"
22
- onKeyDown={(e) => {
23
- if (e.key === "Enter") {
24
- client.doneFEToolWaiting(message.id!, (e.target as any).value);
25
- }
26
- }}
27
- />
28
- </div>
29
- )}
30
- <div className="tool-message">
31
- <div className="tool-header" onClick={onToggleCollapse}>
32
- <div className="tool-title">{message.name}</div>
33
- </div>
34
-
35
- {!isCollapsed && (
36
- <div className="tool-content">
37
- <div className="tool-input">{message.tool_input}</div>
38
- <div className="tool-output">{getMessageContent(message.content)}</div>
39
- {message.usage_metadata && <UsageMetadata usage_metadata={message.usage_metadata} spend_time={message.spend_time} />}
40
- </div>
41
- )}
42
- </div>
43
- </div>
44
- );
45
- };
46
-
47
- export default MessageTool;
1
+ import React from "react";
2
+ import { LangGraphClient, RenderMessage, ToolMessage } from "@langgraph-js/sdk";
3
+ import { UsageMetadata } from "./UsageMetadata";
4
+ interface MessageToolProps {
5
+ message: ToolMessage & RenderMessage;
6
+ client: LangGraphClient;
7
+ getMessageContent: (content: any) => string;
8
+ formatTokens: (tokens: number) => string;
9
+ isCollapsed: boolean;
10
+ onToggleCollapse: () => void;
11
+ }
12
+
13
+ const MessageTool: React.FC<MessageToolProps> = ({ message, client, getMessageContent, formatTokens, isCollapsed, onToggleCollapse }) => {
14
+ return (
15
+ <div className="message tool">
16
+ {message.name === "ask_user" && !message.additional_kwargs?.done && (
17
+ <div>
18
+ <div>询问 {message.tool_input}</div>
19
+ <input
20
+ type="text"
21
+ onKeyDown={(e) => {
22
+ if (e.key === "Enter") {
23
+ client.doneFEToolWaiting(message.id!, (e.target as any).value);
24
+ }
25
+ }}
26
+ />
27
+ </div>
28
+ )}
29
+ <div className="tool-message">
30
+ <div className="tool-header" onClick={onToggleCollapse}>
31
+ <div className="tool-title">{message.name}</div>
32
+ </div>
33
+
34
+ {!isCollapsed && (
35
+ <div className="tool-content">
36
+ <div className="tool-input">{message.tool_input}</div>
37
+ <div className="tool-output">{getMessageContent(message.content)}</div>
38
+ <UsageMetadata response_metadata={message.response_metadata as any} usage_metadata={message.usage_metadata || {}} spend_time={message.spend_time} />
39
+ </div>
40
+ )}
41
+ </div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default MessageTool;
@@ -1,34 +1,40 @@
1
- interface UsageMetadataProps {
2
- usage_metadata: Partial<{
3
- input_tokens: number;
4
- output_tokens: number;
5
- total_tokens: number;
6
- }>;
7
- spend_time?: number;
8
- }
9
-
10
- export const UsageMetadata: React.FC<UsageMetadataProps> = ({ usage_metadata, spend_time }) => {
11
- const formatTokens = (tokens: number) => {
12
- return tokens.toString();
13
- };
14
-
15
- return (
16
- <div className="message-meta">
17
- <div className="token-info">
18
- <span className="token-item">
19
- <span className="token-emoji">📥</span>
20
- {formatTokens(usage_metadata.input_tokens || 0)}
21
- </span>
22
- <span className="token-item">
23
- <span className="token-emoji">📤</span>
24
- {formatTokens(usage_metadata.output_tokens || 0)}
25
- </span>
26
- <span className="token-item">
27
- <span className="token-emoji">📊</span>
28
- {formatTokens(usage_metadata.total_tokens || 0)}
29
- </span>
30
- </div>
31
- <span className="message-time">{spend_time ? `${(spend_time / 1000).toFixed(2)}s` : ""}</span>
32
- </div>
33
- );
34
- };
1
+ interface UsageMetadataProps {
2
+ usage_metadata: Partial<{
3
+ input_tokens: number;
4
+ output_tokens: number;
5
+ total_tokens: number;
6
+ }>;
7
+ response_metadata?:{
8
+ model_name?: string;
9
+ }
10
+ spend_time?: number;
11
+ }
12
+
13
+ export const UsageMetadata: React.FC<UsageMetadataProps> = ({ usage_metadata, spend_time ,response_metadata}) => {
14
+ const formatTokens = (tokens: number) => {
15
+ return tokens.toString();
16
+ };
17
+
18
+ return (
19
+ <div className="message-meta">
20
+ <div className="token-info">
21
+ <span className="token-item">
22
+ <span className="token-emoji">📥</span>
23
+ {formatTokens(usage_metadata.input_tokens || 0)}
24
+ </span>
25
+ <span className="token-item">
26
+ <span className="token-emoji">📤</span>
27
+ {formatTokens(usage_metadata.output_tokens || 0)}
28
+ </span>
29
+ <span className="token-item">
30
+ <span className="token-emoji">📊</span>
31
+ {formatTokens(usage_metadata.total_tokens || 0)}
32
+ </span>
33
+ </div>
34
+ <div>
35
+ {response_metadata?.model_name}
36
+ </div>
37
+ <span className="message-time">{spend_time ? `${(spend_time / 1000).toFixed(2)}s` : ""}</span>
38
+ </div>
39
+ );
40
+ };
@@ -1,29 +1,29 @@
1
- import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from "react";
2
- type ChatContextType = UnionStore<typeof globalChatStore>;
3
-
4
- const ChatContext = createContext<ChatContextType | undefined>(undefined);
5
-
6
- export const useChat = () => {
7
- const context = useContext(ChatContext);
8
- if (!context) {
9
- throw new Error("useChat must be used within a ChatProvider");
10
- }
11
- return context;
12
- };
13
-
14
- interface ChatProviderProps {
15
- children: ReactNode;
16
- }
17
- import { globalChatStore } from "../store";
18
- import { UnionStore, useUnionStore } from "@langgraph-js/sdk";
19
- import { useStore } from "@nanostores/react";
20
- export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
21
- const store = useUnionStore(globalChatStore, useStore);
22
- useEffect(() => {
23
- store.initClient().then((res) => {
24
- store.refreshHistoryList();
25
- });
26
- }, [store.currentAgent]);
27
-
28
- return <ChatContext.Provider value={store}>{children}</ChatContext.Provider>;
29
- };
1
+ import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from "react";
2
+ type ChatContextType = UnionStore<typeof globalChatStore>;
3
+
4
+ const ChatContext = createContext<ChatContextType | undefined>(undefined);
5
+
6
+ export const useChat = () => {
7
+ const context = useContext(ChatContext);
8
+ if (!context) {
9
+ throw new Error("useChat must be used within a ChatProvider");
10
+ }
11
+ return context;
12
+ };
13
+
14
+ interface ChatProviderProps {
15
+ children: ReactNode;
16
+ }
17
+ import { globalChatStore } from "../store";
18
+ import { UnionStore, useUnionStore } from "@langgraph-js/sdk";
19
+ import { useStore } from "@nanostores/react";
20
+ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
21
+ const store = useUnionStore(globalChatStore, useStore);
22
+ useEffect(() => {
23
+ store.initClient().then((res) => {
24
+ store.refreshHistoryList();
25
+ });
26
+ }, [store.currentAgent]);
27
+
28
+ return <ChatContext.Provider value={store}>{children}</ChatContext.Provider>;
29
+ };
@@ -0,0 +1,42 @@
1
+ import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
2
+
3
+ interface ExtraParamsContextType {
4
+ extraParams: object;
5
+ setExtraParams: (params: object) => void;
6
+ }
7
+
8
+ const ExtraParamsContext = createContext<ExtraParamsContextType | undefined>(undefined);
9
+
10
+ export const ExtraParamsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
11
+ const [extraParams, setExtraParamsState] = useState<object>(() => {
12
+ const savedParams = localStorage.getItem("extraParams");
13
+ try {
14
+ return savedParams ? JSON.parse(savedParams) : {};
15
+ } catch (e) {
16
+ console.error("Failed to parse extraParams from localStorage", e);
17
+ return {};
18
+ }
19
+ });
20
+
21
+ useEffect(() => {
22
+ localStorage.setItem("extraParams", JSON.stringify(extraParams));
23
+ }, [extraParams]);
24
+
25
+ const setExtraParams = (params: object) => {
26
+ setExtraParamsState(params);
27
+ };
28
+
29
+ return (
30
+ <ExtraParamsContext.Provider value={{ extraParams, setExtraParams }}>
31
+ {children}
32
+ </ExtraParamsContext.Provider>
33
+ );
34
+ };
35
+
36
+ export const useExtraParams = (): ExtraParamsContextType => {
37
+ const context = useContext(ExtraParamsContext);
38
+ if (context === undefined) {
39
+ throw new Error('useExtraParams must be used within an ExtraParamsProvider');
40
+ }
41
+ return context;
42
+ };
@@ -1,24 +1,24 @@
1
- import { createChatStore } from "@langgraph-js/sdk";
2
- const F =
3
- localStorage.getItem("withCredentials") === "true"
4
- ? (url: string, options: RequestInit) => {
5
- options.credentials = "include";
6
- return fetch(url, options);
7
- }
8
- : fetch;
9
- export const globalChatStore = createChatStore(
10
- "agent",
11
- {
12
- apiUrl: localStorage.getItem("apiUrl") || "http://localhost:8123",
13
- defaultHeaders: JSON.parse(localStorage.getItem("code") || "{}"),
14
- callerOptions: {
15
- // 携带 cookie 的写法
16
- fetch: F,
17
- },
18
- },
19
- {
20
- onInit(client) {
21
- client.tools.bindTools([]);
22
- },
23
- }
24
- );
1
+ import { createChatStore } from "@langgraph-js/sdk";
2
+ const F =
3
+ localStorage.getItem("withCredentials") === "true"
4
+ ? (url: string, options: RequestInit) => {
5
+ options.credentials = "include";
6
+ return fetch(url, options);
7
+ }
8
+ : fetch;
9
+ export const globalChatStore = createChatStore(
10
+ "agent",
11
+ {
12
+ apiUrl: localStorage.getItem("apiUrl") || "http://localhost:8123",
13
+ defaultHeaders: JSON.parse(localStorage.getItem("code") || "{}"),
14
+ callerOptions: {
15
+ // 携带 cookie 的写法
16
+ fetch: F,
17
+ },
18
+ },
19
+ {
20
+ onInit(client) {
21
+ client.tools.bindTools([]);
22
+ },
23
+ }
24
+ );
package/src/chat/tools.ts CHANGED
@@ -1,33 +1,33 @@
1
- import { createFETool, ToolManager } from "@langgraph-js/sdk";
2
-
3
- // 文件操作工具
4
- export const fileTool = createFETool({
5
- name: "file_operation",
6
- description: "执行文件操作,包括读取和写入",
7
- parameters: [
8
- {
9
- name: "filePath",
10
- type: "string",
11
- description: "文件的完整路径",
12
- },
13
- ],
14
- returnDirect: true,
15
- callbackMessage: () => [{ type: "ai", content: "工作完成" }],
16
- async handler(args) {
17
- await new Promise((resolve) => setTimeout(resolve, 3000));
18
- return [{ type: "text", text: "执行文件操作 " + args.filePath }];
19
- },
20
- });
21
-
22
- export const askUserTool = createFETool({
23
- name: "ask_user",
24
- description: "询问用户",
25
- parameters: [
26
- {
27
- name: "question",
28
- type: "string",
29
- description: "问题",
30
- },
31
- ],
32
- handler: ToolManager.waitForUIDone,
33
- });
1
+ import { createFETool, ToolManager } from "@langgraph-js/sdk";
2
+
3
+ // 文件操作工具
4
+ export const fileTool = createFETool({
5
+ name: "file_operation",
6
+ description: "执行文件操作,包括读取和写入",
7
+ parameters: [
8
+ {
9
+ name: "filePath",
10
+ type: "string",
11
+ description: "文件的完整路径",
12
+ },
13
+ ],
14
+ returnDirect: true,
15
+ callbackMessage: () => [{ type: "ai", content: "工作完成" }],
16
+ async handler(args) {
17
+ await new Promise((resolve) => setTimeout(resolve, 3000));
18
+ return [{ type: "text", text: "执行文件操作 " + args.filePath }];
19
+ },
20
+ });
21
+
22
+ export const askUserTool = createFETool({
23
+ name: "ask_user",
24
+ description: "询问用户",
25
+ parameters: [
26
+ {
27
+ name: "question",
28
+ type: "string",
29
+ description: "问题",
30
+ },
31
+ ],
32
+ handler: ToolManager.waitForUIDone,
33
+ });
package/src/chat/types.ts CHANGED
@@ -1,16 +1,16 @@
1
- export interface Message {
2
- content: string;
3
- role: string;
4
- name?: string;
5
- metadata?: {
6
- graph_id?: string;
7
- };
8
- thread_id?: string;
9
- usage_metadata?: {
10
- input_tokens: number;
11
- output_tokens: number;
12
- total_tokens: number;
13
- };
14
- spend_time?: number;
15
- tool_input?: string;
16
- }
1
+ export interface Message {
2
+ content: string;
3
+ role: string;
4
+ name?: string;
5
+ metadata?: {
6
+ graph_id?: string;
7
+ };
8
+ thread_id?: string;
9
+ usage_metadata?: {
10
+ input_tokens: number;
11
+ output_tokens: number;
12
+ total_tokens: number;
13
+ };
14
+ spend_time?: number;
15
+ tool_input?: string;
16
+ }
@@ -1,27 +1,27 @@
1
- import { useState, useEffect } from "react";
2
-
3
- function useLocalStorage<T>(key: string, initialValue: T) {
4
- // 从 localStorage 获取初始值
5
- const [storedValue, setStoredValue] = useState<T>(() => {
6
- try {
7
- const item = window.localStorage.getItem(key);
8
- return item ? JSON.parse(item) : initialValue;
9
- } catch (error) {
10
- console.error("Error reading from localStorage:", error);
11
- return initialValue;
12
- }
13
- });
14
-
15
- // 当值改变时,自动同步到 localStorage
16
- useEffect(() => {
17
- try {
18
- window.localStorage.setItem(key, JSON.stringify(storedValue));
19
- } catch (error) {
20
- console.error("Error writing to localStorage:", error);
21
- }
22
- }, [key, storedValue]);
23
-
24
- return [storedValue, setStoredValue] as const;
25
- }
26
-
27
- export default useLocalStorage;
1
+ import { useState, useEffect } from "react";
2
+
3
+ function useLocalStorage<T>(key: string, initialValue: T) {
4
+ // 从 localStorage 获取初始值
5
+ const [storedValue, setStoredValue] = useState<T>(() => {
6
+ try {
7
+ const item = window.localStorage.getItem(key);
8
+ return item ? JSON.parse(item) : initialValue;
9
+ } catch (error) {
10
+ console.error("Error reading from localStorage:", error);
11
+ return initialValue;
12
+ }
13
+ });
14
+
15
+ // 当值改变时,自动同步到 localStorage
16
+ useEffect(() => {
17
+ try {
18
+ window.localStorage.setItem(key, JSON.stringify(storedValue));
19
+ } catch (error) {
20
+ console.error("Error writing to localStorage:", error);
21
+ }
22
+ }, [key, storedValue]);
23
+
24
+ return [storedValue, setStoredValue] as const;
25
+ }
26
+
27
+ export default useLocalStorage;
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { default as Chat } from "./chat/Chat";
1
+ export { default as Chat } from "./chat/Chat";