@langgraph-js/ui 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +6 -6
  3. package/cli.mjs +36 -36
  4. package/dist/assets/{index-Du4LMUX2.css → index-BlHtM5cu.css} +1 -1
  5. package/dist/assets/index-C7SfDwhG.js +192 -0
  6. package/dist/index.html +14 -14
  7. package/index.html +22 -22
  8. package/package.json +8 -9
  9. package/src/chat/Chat.tsx +151 -164
  10. package/src/chat/FileUpload/index.ts +105 -105
  11. package/src/chat/chat.css +403 -403
  12. package/src/chat/components/FileList.css +128 -128
  13. package/src/chat/components/FileList.tsx +73 -73
  14. package/src/chat/components/HistoryList.tsx +192 -192
  15. package/src/chat/components/JsonEditorPopup.css +80 -80
  16. package/src/chat/components/JsonEditorPopup.tsx +56 -56
  17. package/src/chat/components/JsonToMessage/JsonToMessage.css +104 -0
  18. package/src/chat/components/JsonToMessage/JsonToMessage.tsx +114 -0
  19. package/src/chat/components/JsonToMessage/JsonToMessageButton.tsx +27 -0
  20. package/src/chat/components/JsonToMessage/index.tsx +5 -0
  21. package/src/chat/components/MessageAI.tsx +24 -24
  22. package/src/chat/components/MessageBox.tsx +39 -0
  23. package/src/chat/components/MessageHuman.tsx +55 -55
  24. package/src/chat/components/MessageTool.tsx +46 -46
  25. package/src/chat/components/UsageMetadata.tsx +40 -40
  26. package/src/chat/context/ChatContext.tsx +31 -29
  27. package/src/chat/context/ExtraParamsContext.tsx +41 -41
  28. package/src/chat/store/index.ts +25 -24
  29. package/src/chat/tools.ts +33 -33
  30. package/src/chat/types.ts +16 -16
  31. package/src/hooks/useLocalStorage.ts +27 -27
  32. package/src/index.ts +1 -1
  33. package/src/login/Login.css +93 -93
  34. package/src/login/Login.tsx +92 -92
  35. package/test/App.tsx +9 -9
  36. package/test/main.tsx +5 -5
  37. package/test/vite-env.d.ts +1 -1
  38. package/tsconfig.json +21 -21
  39. package/tsconfig.node.json +9 -9
  40. package/vite.config.ts +17 -17
  41. package/dist/assets/index-BHPbGlnP.js +0 -192
@@ -0,0 +1,104 @@
1
+ .json-to-message-overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ right: 0;
6
+ bottom: 0;
7
+ background-color: rgba(0, 0, 0, 0.5);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 1000;
12
+ }
13
+
14
+ .json-to-message-content {
15
+ background-color: white;
16
+ border-radius: 8px;
17
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
18
+ width: 90%;
19
+ max-width: 1200px;
20
+ height: 80vh;
21
+ max-height: 800px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .json-to-message-header {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ padding: 15px 20px;
32
+ border-bottom: 1px solid #eaeaea;
33
+ }
34
+
35
+ .json-to-message-header h2 {
36
+ margin: 0;
37
+ font-size: 1.5rem;
38
+ color: #333;
39
+ }
40
+
41
+ .close-button {
42
+ background: none;
43
+ border: none;
44
+ font-size: 1.5rem;
45
+ cursor: pointer;
46
+ color: #666;
47
+ }
48
+
49
+ .close-button:hover {
50
+ color: #000;
51
+ }
52
+
53
+ .json-to-message-body {
54
+ display: flex;
55
+ flex: 1;
56
+ overflow: hidden;
57
+ }
58
+
59
+ .json-editor-pane {
60
+ flex: 1;
61
+ padding: 15px;
62
+ display: flex;
63
+ flex-direction: column;
64
+ border-right: 1px solid #eaeaea;
65
+ }
66
+
67
+ .json-editor-pane textarea {
68
+ flex: 1;
69
+ font-family: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", monospace;
70
+ font-size: 0.9rem;
71
+ padding: 10px;
72
+ border: 1px solid #ddd;
73
+ border-radius: 4px;
74
+ resize: none;
75
+ }
76
+
77
+ .error-message {
78
+ color: #e53e3e;
79
+ margin-top: 10px;
80
+ font-size: 0.9rem;
81
+ }
82
+
83
+ .message-preview-pane {
84
+ flex: 1;
85
+ padding: 15px;
86
+ overflow-y: auto;
87
+ background-color: #f9f9f9;
88
+ }
89
+
90
+ .preview-container {
91
+ padding: 10px;
92
+ background-color: white;
93
+ border-radius: 4px;
94
+ min-height: 100%;
95
+ }
96
+
97
+ .no-preview {
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ height: 100%;
102
+ color: #666;
103
+ font-style: italic;
104
+ }
@@ -0,0 +1,114 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { MessagesBox } from "../MessageBox";
3
+ import "./JsonToMessage.css";
4
+ import { RenderMessage } from "@langgraph-js/sdk";
5
+
6
+ interface JsonToMessageProps {
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ initialJson?: string | object;
10
+ }
11
+
12
+ const JsonToMessage: React.FC<JsonToMessageProps> = ({ isOpen, onClose, initialJson }) => {
13
+ const [jsonString, setJsonString] = useState<string>("");
14
+ const [error, setError] = useState<string | null>(null);
15
+ const [previewMessages, setPreviewMessages] = useState<RenderMessage[]>([]);
16
+
17
+ useEffect(() => {
18
+ // 重置状态当弹窗打开时
19
+ if (isOpen) {
20
+ let initialJsonContent = "";
21
+
22
+ if (initialJson) {
23
+ if (typeof initialJson === "string") {
24
+ initialJsonContent = initialJson;
25
+ } else {
26
+ try {
27
+ initialJsonContent = JSON.stringify(initialJson, null, 2);
28
+ } catch (e) {
29
+ console.error("Failed to stringify initial JSON", e);
30
+ initialJsonContent = "";
31
+ }
32
+ }
33
+ } else {
34
+ // 默认示例消息
35
+ initialJsonContent = JSON.stringify([], null, 2);
36
+ }
37
+
38
+ setJsonString(initialJsonContent);
39
+ setError(null);
40
+
41
+ // 初始化时尝试解析
42
+ try {
43
+ updatePreview(initialJsonContent);
44
+ } catch (e) {
45
+ console.error("Failed to parse initial JSON", e);
46
+ }
47
+ }
48
+ }, [isOpen, initialJson]);
49
+
50
+ // 解析JSON并更新预览
51
+ const updatePreview = (jsonInput: string) => {
52
+ try {
53
+ const parsedJson = JSON.parse(jsonInput);
54
+ let message: RenderMessage[] = [];
55
+ if (Array.isArray(parsedJson)) {
56
+ message = parsedJson;
57
+ } else {
58
+ message = parsedJson.messages;
59
+ }
60
+ // 确保消息有必要的字段
61
+ if (!Array.isArray(message)) {
62
+ throw new Error("JSON 格式无效,请检查后重试");
63
+ }
64
+ setPreviewMessages(message);
65
+ setError(null);
66
+ } catch (e) {
67
+ setError("JSON 格式无效,请检查后重试");
68
+ console.error("Invalid JSON format:", e);
69
+ }
70
+ };
71
+
72
+ // 当JSON输入变化时更新预览
73
+ useEffect(() => {
74
+ if (jsonString) {
75
+ updatePreview(jsonString);
76
+ }
77
+ }, [jsonString]);
78
+
79
+ if (!isOpen) {
80
+ return null;
81
+ }
82
+
83
+ return (
84
+ <div className="json-to-message-overlay">
85
+ <div className="json-to-message-content">
86
+ <div className="json-to-message-header">
87
+ <h2>JSON 消息预览</h2>
88
+ <button onClick={onClose} className="close-button">
89
+ ×
90
+ </button>
91
+ </div>
92
+
93
+ <div className="json-to-message-body">
94
+ <div className="json-editor-pane">
95
+ <textarea value={jsonString} onChange={(e) => setJsonString(e.target.value)} rows={20} placeholder="输入JSON消息格式..." />
96
+ {error && <p className="error-message">{error}</p>}
97
+ </div>
98
+
99
+ <div className="message-preview-pane">
100
+ <div className="preview-container chat-messages">
101
+ {previewMessages?.length > 0 ? (
102
+ <MessagesBox renderMessages={previewMessages} collapsedTools={[]} toggleToolCollapse={() => {}} client={null as any} />
103
+ ) : (
104
+ <div className="no-preview">请输入有效的JSON以查看消息预览</div>
105
+ )}
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default JsonToMessage;
@@ -0,0 +1,27 @@
1
+ import React, { useState } from "react";
2
+ import JsonToMessage from "./JsonToMessage";
3
+
4
+ interface JsonToMessageButtonProps {
5
+ buttonText?: string;
6
+ initialJson?: string | object;
7
+ className?: string;
8
+ }
9
+
10
+ const JsonToMessageButton: React.FC<JsonToMessageButtonProps> = ({ buttonText = "JSON 预览", initialJson, className = "" }) => {
11
+ const [isOpen, setIsOpen] = useState(false);
12
+
13
+ const openModal = () => setIsOpen(true);
14
+ const closeModal = () => setIsOpen(false);
15
+
16
+ return (
17
+ <>
18
+ <button className={`history-button ${className}`} onClick={openModal}>
19
+ {buttonText}
20
+ </button>
21
+
22
+ <JsonToMessage isOpen={isOpen} onClose={closeModal} initialJson={initialJson} />
23
+ </>
24
+ );
25
+ };
26
+
27
+ export default JsonToMessageButton;
@@ -0,0 +1,5 @@
1
+ import JsonToMessage from "./JsonToMessage";
2
+ import JsonToMessageButton from "./JsonToMessageButton";
3
+
4
+ export { JsonToMessage, JsonToMessageButton };
5
+ export default JsonToMessage;
@@ -1,24 +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
- 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
+ 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;
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import MessageHuman from "./MessageHuman";
3
+ import MessageAI from "./MessageAI";
4
+ import MessageTool from "./MessageTool";
5
+ import { formatTokens, getMessageContent, LangGraphClient, RenderMessage } from "@langgraph-js/sdk";
6
+
7
+ export const MessagesBox = ({
8
+ renderMessages,
9
+ collapsedTools,
10
+ toggleToolCollapse,
11
+ client,
12
+ }: {
13
+ renderMessages: RenderMessage[];
14
+ collapsedTools: string[];
15
+ toggleToolCollapse: (id: string) => void;
16
+ client: LangGraphClient;
17
+ }) => {
18
+ return (
19
+ <>
20
+ {renderMessages.map((message) =>
21
+ message.type === "human" ? (
22
+ <MessageHuman content={message.content} key={message.unique_id} />
23
+ ) : message.type === "tool" ? (
24
+ <MessageTool
25
+ key={message.unique_id}
26
+ message={message}
27
+ client={client!}
28
+ getMessageContent={getMessageContent}
29
+ formatTokens={formatTokens}
30
+ isCollapsed={collapsedTools.includes(message.id!)}
31
+ onToggleCollapse={() => toggleToolCollapse(message.id!)}
32
+ />
33
+ ) : (
34
+ <MessageAI key={message.unique_id} message={message} />
35
+ )
36
+ )}
37
+ </>
38
+ );
39
+ };
@@ -1,55 +1,55 @@
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
+ 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,46 +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
- 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
+ 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,40 +1,40 @@
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
+ 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
+ };