@langgraph-js/ui 1.1.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.
@@ -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;
@@ -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
+ };
@@ -21,9 +21,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
21
21
  const store = useUnionStore(globalChatStore, useStore);
22
22
  useEffect(() => {
23
23
  store.initClient().then((res) => {
24
- store.refreshHistoryList();
24
+ if (store.showHistory) {
25
+ store.refreshHistoryList();
26
+ }
25
27
  });
26
- }, [store.currentAgent]);
28
+ }, []);
27
29
 
28
30
  return <ChatContext.Provider value={store}>{children}</ChatContext.Provider>;
29
31
  };
@@ -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
+ };
@@ -6,8 +6,9 @@ const F =
6
6
  return fetch(url, options);
7
7
  }
8
8
  : fetch;
9
+
9
10
  export const globalChatStore = createChatStore(
10
- "agent",
11
+ localStorage.getItem("agent_name") || "",
11
12
  {
12
13
  apiUrl: localStorage.getItem("apiUrl") || "http://localhost:8123",
13
14
  defaultHeaders: JSON.parse(localStorage.getItem("code") || "{}"),
@@ -1 +0,0 @@
1
- @import"https://unpkg.com/github-markdown-css/github-markdown.css";.chat-container{display:flex;height:100vh;width:100%;position:relative}.chat-sidebar{background-color:#f5f5f5;border-right:1px solid #e0e0e0;display:flex;flex-direction:column}.history-header{padding:16px;border-bottom:1px solid #e0e0e0;display:flex;justify-content:space-between;align-items:center}.history-header h3{margin:0;font-size:16px;color:#333}.close-button{background:none;border:none;font-size:20px;cursor:pointer;color:#666}.history-list{flex:none;width:280px;overflow-y:auto;border-right:1px solid #e0e0e0}.history-item{padding:12px;margin-bottom:8px;background-color:#fff;cursor:pointer;transition:background-color .2s}.history-item:hover{background-color:#e8e8e8}.history-item.active{background-color:#e3f2fd;border-left:3px solid #2196f3}.history-title{font-size:14px;color:#333;margin-bottom:4px}.history-time{font-size:12px;color:#666}.chat-main{overflow:hidden;flex:1;display:flex;flex-direction:column}.chat-header{padding:16px;border-bottom:1px solid #e0e0e0;display:flex;justify-content:flex-end;gap:.5rem}.history-button{padding:8px 16px;background-color:#f0f0f0;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer;font-size:14px;color:#333}.history-button:hover{background-color:#e0e0e0}.chat-messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.message{display:flex;max-width:80%}.message.human{margin-left:auto}.message.ai{margin-right:auto}.message-content{padding:.75rem 1rem;border-radius:8px;border:1px solid #e5e7eb;display:flex;flex-direction:column;gap:.5rem;max-width:100%}.message-text{word-break:break-word;line-height:1.5}.message-image{margin:.5rem 0}.message-image img{max-width:100%;border-radius:4px;box-shadow:0 1px 3px #0000001a}.message-audio{margin:.5rem 0}.message-audio audio{width:100%;max-width:300px}.message-meta{display:flex;align-items:center;gap:.75rem;font-size:.75rem;color:#6b7280;margin-top:.25rem}.message-time{font-family:monospace}.token-info{display:flex;gap:.5rem;align-items:center}.token-item{display:flex;align-items:center;gap:.25rem;background-color:#fff;padding:.125rem .375rem;border-radius:4px;font-family:monospace}.token-emoji{font-size:.875rem}.message.human .message-content{background-color:#3b82f6;color:#fff;border-color:#3b82f6}.message.human .message-meta{color:#fffc}.message.ai .message-content{color:#1f2937}.message.tool{width:100%;max-width:100%}.tool-message{width:100%;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden}.tool-header{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;background-color:#f9fafb;border-bottom:1px solid #e5e7eb;cursor:pointer}.tool-header:hover{background-color:#f3f4f6}.tool-title{font-weight:500;color:#374151}.tool-content{padding:1rem}.tool-input{background-color:#f9fafb;padding:.75rem;border-radius:4px;margin-bottom:.5rem;font-family:monospace;white-space:pre-wrap;word-break:break-all}.tool-output{background-color:#fff;padding:.75rem;border-radius:4px;font-family:monospace;border:1px solid #e5e7eb;margin-bottom:.5rem}.chat-input{border-top:1px solid #e5e7eb;padding:0 1rem 1rem;background-color:#fff}.chat-input-header{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem}.input-container{display:flex;gap:.5rem}.input-textarea{flex:1;padding:.75rem;border:1px solid #e5e7eb;border-radius:8px;resize:none;font-size:.875rem;line-height:1.25rem}.input-textarea:focus{outline:none;border-color:#3b82f6}.send-button{padding:.5rem 1rem;background-color:#3b82f6;color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:500;transition:all .2s}.send-button:hover{background-color:#2563eb}.send-button:disabled{background-color:#93c5fd;cursor:not-allowed}.send-button.interrupt{background-color:#ef4444}.send-button.interrupt:hover{background-color:#dc2626}.collapsed .tool-content{display:none}.expand-icon{transition:transform .2s}.collapsed .expand-icon{transform:rotate(-90deg)}.loading-indicator{padding:12px 16px;margin:8px 0;background-color:#f5f5f5;border-radius:8px;color:#666;font-size:14px;text-align:center;animation:pulse 1.5s infinite;display:flex;align-items:center;justify-content:center;gap:12px}.interrupt-button{padding:4px 12px;background-color:#ef4444;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;transition:background-color .2s}.interrupt-button:hover{background-color:#dc2626}@keyframes pulse{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.error-message{padding:12px 16px;margin:8px 0;background-color:#fee2e2;border:1px solid #fecaca;border-radius:8px;color:#dc2626;font-size:14px;text-align:center}.markdown-body p{text-align:left}.file-list{padding:.5rem;background-color:#f9fafb;border-radius:8px;margin-bottom:1rem}.file-list-header{margin-bottom:1rem}.file-upload-button{display:inline-flex;align-items:center;justify-content:center;width:80px;height:80px;background-color:#3b82f6;color:#fff;border-radius:6px;cursor:pointer;font-size:2rem;transition:background-color .2s}.file-upload-button:hover{background-color:#2563eb}.file-list-content{display:flex;flex-wrap:wrap;gap:.5rem}.file-item{position:relative;width:80px;height:80px;border-radius:6px;overflow:hidden}.file-preview{width:100%;height:100%;object-fit:cover}.file-info{padding:.75rem}.file-name{display:block;font-size:.875rem;color:#374151;margin-bottom:.5rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-actions{display:flex;gap:.5rem}.upload-button,.remove-button{flex:1;padding:.375rem .75rem;border:none;border-radius:4px;font-size:.75rem;cursor:pointer;transition:all .2s}.upload-button{background-color:#3b82f6;color:#fff}.upload-button:hover{background-color:#2563eb}.remove-button{position:absolute;top:2px;right:2px;width:20px;height:20px;background-color:#00000080;color:#fff;border:none;border-radius:50%;cursor:pointer;font-size:16px;line-height:1;display:flex;align-items:center;justify-content:center;transition:background-color .2s}.remove-button:hover{background-color:#000000b3}.upload-button:disabled,.remove-button:disabled{opacity:.5;cursor:not-allowed}.login-container{max-width:600px;margin:2rem auto;padding:2rem;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.header-group{display:flex;gap:1rem;align-items:flex-start;padding:1rem;background:#f8f9fa;border-radius:4px;position:relative}.form-group{flex:1}.form-group label{display:block;margin-bottom:.5rem;color:#333;font-weight:500}.form-group input{width:100%;padding:.5rem;border:1px solid #ddd;border-radius:4px;font-size:1rem}.form-group input:focus{outline:none;border-color:#007bff;box-shadow:0 0 0 2px #007bff40}.button-group{display:flex;gap:1rem;margin-top:1rem}button{padding:.5rem 1rem;border:none;border-radius:4px;font-size:1rem;cursor:pointer;transition:background-color .2s}button[type=submit]{background-color:#007bff;color:#fff}button[type=submit]:hover{background-color:#0056b3}button[type=button]{background-color:#6c757d;color:#fff}button[type=button]:hover{background-color:#5a6268}.remove-header{background-color:#dc3545;color:#fff;padding:.25rem .5rem;font-size:.875rem}.remove-header:hover{background-color:#c82333}p{margin-bottom:1.5rem;color:#666;text-align:center}