@langgraph-js/ui 1.1.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.
@@ -1,93 +1,93 @@
1
- .login-container {
2
- max-width: 600px;
3
- margin: 2rem auto;
4
- padding: 2rem;
5
- background: #fff;
6
- border-radius: 8px;
7
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
8
- }
9
-
10
- .header-group {
11
- display: flex;
12
- gap: 1rem;
13
- align-items: flex-start;
14
- padding: 1rem;
15
- background: #f8f9fa;
16
- border-radius: 4px;
17
- position: relative;
18
- }
19
-
20
- .form-group {
21
- flex: 1;
22
- }
23
-
24
- .form-group label {
25
- display: block;
26
- margin-bottom: 0.5rem;
27
- color: #333;
28
- font-weight: 500;
29
- }
30
-
31
- .form-group input {
32
- width: 100%;
33
- padding: 0.5rem;
34
- border: 1px solid #ddd;
35
- border-radius: 4px;
36
- font-size: 1rem;
37
- }
38
-
39
- .form-group input:focus {
40
- outline: none;
41
- border-color: #007bff;
42
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
43
- }
44
-
45
- .button-group {
46
- display: flex;
47
- gap: 1rem;
48
- margin-top: 1rem;
49
- }
50
-
51
- button {
52
- padding: 0.5rem 1rem;
53
- border: none;
54
- border-radius: 4px;
55
- font-size: 1rem;
56
- cursor: pointer;
57
- transition: background-color 0.2s;
58
- }
59
-
60
- button[type="submit"] {
61
- background-color: #007bff;
62
- color: white;
63
- }
64
-
65
- button[type="submit"]:hover {
66
- background-color: #0056b3;
67
- }
68
-
69
- button[type="button"] {
70
- background-color: #6c757d;
71
- color: white;
72
- }
73
-
74
- button[type="button"]:hover {
75
- background-color: #5a6268;
76
- }
77
-
78
- .remove-header {
79
- background-color: #dc3545;
80
- color: white;
81
- padding: 0.25rem 0.5rem;
82
- font-size: 0.875rem;
83
- }
84
-
85
- .remove-header:hover {
86
- background-color: #c82333;
87
- }
88
-
89
- p {
90
- margin-bottom: 1.5rem;
91
- color: #666;
92
- text-align: center;
93
- }
1
+ .login-container {
2
+ max-width: 600px;
3
+ margin: 2rem auto;
4
+ padding: 2rem;
5
+ background: #fff;
6
+ border-radius: 8px;
7
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
8
+ }
9
+
10
+ .header-group {
11
+ display: flex;
12
+ gap: 1rem;
13
+ align-items: flex-start;
14
+ padding: 1rem;
15
+ background: #f8f9fa;
16
+ border-radius: 4px;
17
+ position: relative;
18
+ }
19
+
20
+ .form-group {
21
+ flex: 1;
22
+ }
23
+
24
+ .form-group label {
25
+ display: block;
26
+ margin-bottom: 0.5rem;
27
+ color: #333;
28
+ font-weight: 500;
29
+ }
30
+
31
+ .form-group input {
32
+ width: 100%;
33
+ padding: 0.5rem;
34
+ border: 1px solid #ddd;
35
+ border-radius: 4px;
36
+ font-size: 1rem;
37
+ }
38
+
39
+ .form-group input:focus {
40
+ outline: none;
41
+ border-color: #007bff;
42
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
43
+ }
44
+
45
+ .button-group {
46
+ display: flex;
47
+ gap: 1rem;
48
+ margin-top: 1rem;
49
+ }
50
+
51
+ button {
52
+ padding: 0.5rem 1rem;
53
+ border: none;
54
+ border-radius: 4px;
55
+ font-size: 1rem;
56
+ cursor: pointer;
57
+ transition: background-color 0.2s;
58
+ }
59
+
60
+ button[type="submit"] {
61
+ background-color: #007bff;
62
+ color: white;
63
+ }
64
+
65
+ button[type="submit"]:hover {
66
+ background-color: #0056b3;
67
+ }
68
+
69
+ button[type="button"] {
70
+ background-color: #6c757d;
71
+ color: white;
72
+ }
73
+
74
+ button[type="button"]:hover {
75
+ background-color: #5a6268;
76
+ }
77
+
78
+ .remove-header {
79
+ background-color: #dc3545;
80
+ color: white;
81
+ padding: 0.25rem 0.5rem;
82
+ font-size: 0.875rem;
83
+ }
84
+
85
+ .remove-header:hover {
86
+ background-color: #c82333;
87
+ }
88
+
89
+ p {
90
+ margin-bottom: 1.5rem;
91
+ color: #666;
92
+ text-align: center;
93
+ }
@@ -1,92 +1,92 @@
1
- import React, { useState } from "react";
2
- import "./Login.css";
3
-
4
- interface HeaderConfig {
5
- key: string;
6
- value: string;
7
- }
8
-
9
- const Login: React.FC = () => {
10
- const [headers, setHeaders] = useState<HeaderConfig[]>([{ key: "authorization", value: "" }]);
11
- const [withCredentials, setWithCredentials] = useState<boolean>(localStorage.getItem("withCredentials") === "true");
12
- const [apiUrl, setApiUrl] = useState<string>(localStorage.getItem("apiUrl") || "");
13
-
14
- const addHeader = () => {
15
- setHeaders([...headers, { key: "", value: "" }]);
16
- };
17
-
18
- const removeHeader = (index: number) => {
19
- setHeaders(headers.filter((_, i) => i !== index));
20
- };
21
-
22
- const updateHeader = (index: number, field: "key" | "value", value: string) => {
23
- const newHeaders = [...headers];
24
- newHeaders[index][field] = value;
25
- setHeaders(newHeaders);
26
- };
27
-
28
- const handleLogin = () => {
29
- const headerObject = Object.fromEntries(headers.map((k) => [k.key, k.value]));
30
-
31
- localStorage.setItem("code", JSON.stringify(headerObject));
32
- localStorage.setItem("withCredentials", JSON.stringify(withCredentials));
33
- localStorage.setItem("apiUrl", apiUrl);
34
- location.reload();
35
- };
36
-
37
- return (
38
- <div className="login-container">
39
- <form
40
- onSubmit={(e) => {
41
- e.preventDefault();
42
- handleLogin();
43
- }}
44
- >
45
- <h2>LangGraph UI</h2>
46
- <p>登录,自定义请求头配置</p>
47
-
48
- <div className="form-group api-url-group">
49
- <label htmlFor="api-url">API URL</label>
50
- <input type="text" id="api-url" value={apiUrl} onChange={(e) => setApiUrl(e.target.value)} placeholder="例如: http://localhost:8123" />
51
- </div>
52
-
53
- {headers.map((header, index) => (
54
- <div key={index} className="header-group">
55
- <div className="form-group">
56
- <input type="text" id={`header-key-${index}`} value={header.key} onChange={(e) => updateHeader(index, "key", e.target.value)} placeholder="例如: authorization" required />
57
- </div>
58
- <div className="form-group">
59
- <input
60
- type="text"
61
- id={`header-value-${index}`}
62
- value={header.value}
63
- onChange={(e) => updateHeader(index, "value", e.target.value)}
64
- placeholder="例如: Bearer token;无则填 1"
65
- required
66
- />
67
- </div>
68
- {index > 0 && (
69
- <button type="button" className="remove-header" onClick={() => removeHeader(index)}>
70
- 删除
71
- </button>
72
- )}
73
- </div>
74
- ))}
75
- <div className="with-credentials-option">
76
- <label>
77
- <input type="checkbox" checked={withCredentials} onChange={(e) => setWithCredentials(e.target.checked)} />
78
- 启用 withCredentials(跨域请求时发送 Cookie)
79
- </label>
80
- </div>
81
- <div className="button-group">
82
- <button type="button" onClick={addHeader}>
83
- 添加请求头
84
- </button>
85
- <button type="submit">保存配置</button>
86
- </div>
87
- </form>
88
- </div>
89
- );
90
- };
91
-
92
- export default Login;
1
+ import React, { useState } from "react";
2
+ import "./Login.css";
3
+
4
+ interface HeaderConfig {
5
+ key: string;
6
+ value: string;
7
+ }
8
+
9
+ const Login: React.FC = () => {
10
+ const [headers, setHeaders] = useState<HeaderConfig[]>([{ key: "authorization", value: "" }]);
11
+ const [withCredentials, setWithCredentials] = useState<boolean>(localStorage.getItem("withCredentials") === "true");
12
+ const [apiUrl, setApiUrl] = useState<string>(localStorage.getItem("apiUrl") || "");
13
+
14
+ const addHeader = () => {
15
+ setHeaders([...headers, { key: "", value: "" }]);
16
+ };
17
+
18
+ const removeHeader = (index: number) => {
19
+ setHeaders(headers.filter((_, i) => i !== index));
20
+ };
21
+
22
+ const updateHeader = (index: number, field: "key" | "value", value: string) => {
23
+ const newHeaders = [...headers];
24
+ newHeaders[index][field] = value;
25
+ setHeaders(newHeaders);
26
+ };
27
+
28
+ const handleLogin = () => {
29
+ const headerObject = Object.fromEntries(headers.map((k) => [k.key, k.value]));
30
+
31
+ localStorage.setItem("code", JSON.stringify(headerObject));
32
+ localStorage.setItem("withCredentials", JSON.stringify(withCredentials));
33
+ localStorage.setItem("apiUrl", apiUrl);
34
+ location.reload();
35
+ };
36
+
37
+ return (
38
+ <div className="login-container">
39
+ <form
40
+ onSubmit={(e) => {
41
+ e.preventDefault();
42
+ handleLogin();
43
+ }}
44
+ >
45
+ <h2>LangGraph UI</h2>
46
+ <p>登录,自定义请求头配置</p>
47
+
48
+ <div className="form-group api-url-group">
49
+ <label htmlFor="api-url">API URL</label>
50
+ <input type="text" id="api-url" value={apiUrl} onChange={(e) => setApiUrl(e.target.value)} placeholder="例如: http://localhost:8123" />
51
+ </div>
52
+
53
+ {headers.map((header, index) => (
54
+ <div key={index} className="header-group">
55
+ <div className="form-group">
56
+ <input type="text" id={`header-key-${index}`} value={header.key} onChange={(e) => updateHeader(index, "key", e.target.value)} placeholder="例如: authorization" required />
57
+ </div>
58
+ <div className="form-group">
59
+ <input
60
+ type="text"
61
+ id={`header-value-${index}`}
62
+ value={header.value}
63
+ onChange={(e) => updateHeader(index, "value", e.target.value)}
64
+ placeholder="例如: Bearer token;无则填 1"
65
+ required
66
+ />
67
+ </div>
68
+ {index > 0 && (
69
+ <button type="button" className="remove-header" onClick={() => removeHeader(index)}>
70
+ 删除
71
+ </button>
72
+ )}
73
+ </div>
74
+ ))}
75
+ <div className="with-credentials-option">
76
+ <label>
77
+ <input type="checkbox" checked={withCredentials} onChange={(e) => setWithCredentials(e.target.checked)} />
78
+ 启用 withCredentials(跨域请求时发送 Cookie)
79
+ </label>
80
+ </div>
81
+ <div className="button-group">
82
+ <button type="button" onClick={addHeader}>
83
+ 添加请求头
84
+ </button>
85
+ <button type="submit">保存配置</button>
86
+ </div>
87
+ </form>
88
+ </div>
89
+ );
90
+ };
91
+
92
+ export default Login;
package/test/App.tsx CHANGED
@@ -1,9 +1,9 @@
1
- import Chat from "../src/chat/Chat";
2
- import Login from "../src/login/Login";
3
- import { useState } from "react";
4
- function App() {
5
- const [isLogin, setIsLogin] = useState(localStorage.getItem("code"));
6
- return <>{isLogin ? <Chat /> : <Login></Login>}</>;
7
- }
8
-
9
- export default App;
1
+ import Chat from "../src/chat/Chat";
2
+ import Login from "../src/login/Login";
3
+ import { useState } from "react";
4
+ function App() {
5
+ const [isLogin, setIsLogin] = useState(localStorage.getItem("code"));
6
+ return <>{isLogin ? <Chat /> : <Login></Login>}</>;
7
+ }
8
+
9
+ export default App;
package/test/main.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import App from "./App";
4
-
5
- ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import App from "./App";
4
+
5
+ ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
package/tsconfig.json CHANGED
@@ -1,21 +1,21 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "useDefineForClassFields": true,
5
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
- "allowJs": false,
7
- "skipLibCheck": true,
8
- "esModuleInterop": false,
9
- "allowSyntheticDefaultImports": true,
10
- "strict": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "module": "ESNext",
13
- "moduleResolution": "Node",
14
- "resolveJsonModule": true,
15
- "isolatedModules": true,
16
- "noEmit": true,
17
- "jsx": "react-jsx"
18
- },
19
- "include": ["test", "test/**/*.tsx"],
20
- "references": [{ "path": "./tsconfig.node.json" }]
21
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": false,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["test", "test/**/*.tsx"],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
@@ -1,9 +1,9 @@
1
- {
2
- "compilerOptions": {
3
- "composite": true,
4
- "module": "esnext",
5
- "moduleResolution": "node",
6
- "allowSyntheticDefaultImports": true
7
- },
8
- "include": ["vite.config.ts"]
9
- }
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }
package/vite.config.ts CHANGED
@@ -1,17 +1,17 @@
1
- import react from "@vitejs/plugin-react";
2
- import { defineConfig } from "vite";
3
- import basicSsl from "@vitejs/plugin-basic-ssl";
4
-
5
- // https://vitejs.dev/config/
6
- export default defineConfig(({ mode }) => {
7
- const isHttps = mode === "https";
8
- return {
9
- plugins: [react(), isHttps ? basicSsl() : undefined],
10
- optimizeDeps: {
11
- exclude: ["@langgraph-js/ui", "@langgraph-js/sdk"],
12
- },
13
- server: {
14
- port: 1111,
15
- },
16
- };
17
- });
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+ import basicSsl from "@vitejs/plugin-basic-ssl";
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig(({ mode }) => {
7
+ const isHttps = mode === "https";
8
+ return {
9
+ plugins: [react(), isHttps ? basicSsl() : undefined],
10
+ optimizeDeps: {
11
+ exclude: ["@langgraph-js/ui", "@langgraph-js/sdk"],
12
+ },
13
+ server: {
14
+ port: 1111,
15
+ },
16
+ };
17
+ });
@@ -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}