@langgraph-js/ui 1.0.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 @@
1
+ .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}.message-text{word-break:break-word}.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{background-color:#f3f4f6;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}.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}
@@ -0,0 +1,23 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Vite App</title>
7
+ <style>
8
+ body,
9
+ #root,
10
+ html {
11
+ margin: 0;
12
+ padding: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ }
16
+ </style>
17
+ <script type="module" crossorigin src="/assets/index-B6G4BLix.js"></script>
18
+ <link rel="stylesheet" crossorigin href="/assets/index-BAcH-2-3.css">
19
+ </head>
20
+ <body>
21
+ <div id="root"></div>
22
+ </body>
23
+ </html>
package/index.html ADDED
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Vite App</title>
7
+ <style>
8
+ body,
9
+ #root,
10
+ html {
11
+ margin: 0;
12
+ padding: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <div id="root"></div>
20
+ <script type="module" src="./test/main.tsx"></script>
21
+ </body>
22
+ </html>
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@langgraph-js/ui",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/",
7
+ "access": "public"
8
+ },
9
+ "bin": {
10
+ "langgraph-ui": "./cli.mjs"
11
+ },
12
+ "main": "./dist/react/src/index.js",
13
+ "type": "module",
14
+ "dependencies": {
15
+ "@nanostores/react": "^1.0.0",
16
+ "@vitejs/plugin-basic-ssl": "^2.0.0",
17
+ "@vitejs/plugin-react": "^4.3.4",
18
+ "nanostores": "^1.0.1",
19
+ "react": "^19.0.0",
20
+ "react-dom": "^19.0.0",
21
+ "vite": "^6.2.0",
22
+ "@langgraph-js/sdk": "1.1.5"
23
+ },
24
+ "devDependencies": {
25
+ "@types/react": "^19.0.10",
26
+ "@types/react-dom": "^19.0.4"
27
+ },
28
+ "scripts": {
29
+ "dev": "vite",
30
+ "build": "vite build",
31
+ "preview": "vite preview",
32
+ "prepublish": "pnpm build"
33
+ }
34
+ }
@@ -0,0 +1,112 @@
1
+ import React from "react";
2
+ import "./chat.css";
3
+ import MessageHuman from "./components/MessageHuman";
4
+ import MessageAI from "./components/MessageAI";
5
+ import MessageTool from "./components/MessageTool";
6
+ import HistoryList from "./components/HistoryList";
7
+ import { ChatProvider, useChat } from "./context/ChatContext";
8
+ import { UsageMetadata } from "./components/UsageMetadata";
9
+ import { formatTime, formatTokens, getMessageContent } from "@langgraph-js/sdk";
10
+
11
+ const ChatMessages: React.FC = () => {
12
+ const { renderMessages, loading, inChatError, client, collapsedTools, toggleToolCollapse } = useChat();
13
+
14
+ return (
15
+ <div className="chat-messages">
16
+ {renderMessages.map((message) =>
17
+ message.type === "human" ? (
18
+ <MessageHuman content={message.content} key={message.unique_id} />
19
+ ) : message.type === "tool" ? (
20
+ <MessageTool
21
+ key={message.unique_id}
22
+ message={message}
23
+ client={client!}
24
+ getMessageContent={getMessageContent}
25
+ formatTokens={formatTokens}
26
+ isCollapsed={collapsedTools.includes(message.id!)}
27
+ onToggleCollapse={() => toggleToolCollapse(message.id!)}
28
+ />
29
+ ) : (
30
+ <MessageAI key={message.unique_id} message={message} />
31
+ )
32
+ )}
33
+ {loading && <div className="loading-indicator">正在思考中...</div>}
34
+ {inChatError && <div className="error-message">{inChatError}</div>}
35
+ </div>
36
+ );
37
+ };
38
+
39
+ const ChatInput: React.FC = () => {
40
+ const { userInput, setUserInput, loading, sendMessage, stopGeneration, currentAgent, setCurrentAgent, client } = useChat();
41
+ const handleKeyPress = (event: React.KeyboardEvent) => {
42
+ if (event.key === "Enter" && !event.shiftKey) {
43
+ event.preventDefault();
44
+ sendMessage();
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className="chat-input">
50
+ <div className="chat-input-header">
51
+ <select value={currentAgent} onChange={(e) => setCurrentAgent(e.target.value)}>
52
+ {client?.availableAssistants.map((i) => {
53
+ return <option value={i.graph_id}>{i.name}</option>;
54
+ })}
55
+ </select>
56
+ <UsageMetadata usage_metadata={client?.tokenCounter || {}} />
57
+ </div>
58
+ <div className="input-container">
59
+ <textarea
60
+ className="input-textarea"
61
+ rows={2}
62
+ value={userInput}
63
+ onChange={(e) => setUserInput(e.target.value)}
64
+ onKeyDown={handleKeyPress}
65
+ placeholder="输入消息..."
66
+ disabled={loading}
67
+ />
68
+ <button className={`send-button ${loading ? "interrupt" : ""}`} onClick={() => (loading ? stopGeneration() : sendMessage())} disabled={!loading && !userInput.trim()}>
69
+ {loading ? "中断" : "发送"}
70
+ </button>
71
+ </div>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ const Chat: React.FC = () => {
77
+ const { showHistory, toggleHistoryVisible } = useChat();
78
+
79
+ return (
80
+ <div className="chat-container">
81
+ {showHistory && <HistoryList onClose={() => toggleHistoryVisible()} formatTime={formatTime} />}
82
+ <div className="chat-main">
83
+ <div className="chat-header">
84
+ <button className="history-button" onClick={() => toggleHistoryVisible()}>
85
+ 历史记录
86
+ </button>
87
+ <button
88
+ className="history-button"
89
+ onClick={() => {
90
+ localStorage.setItem("code", "");
91
+ location.reload();
92
+ }}
93
+ >
94
+ 退出登陆
95
+ </button>
96
+ </div>
97
+ <ChatMessages />
98
+ <ChatInput />
99
+ </div>
100
+ </div>
101
+ );
102
+ };
103
+
104
+ const ChatWrapper: React.FC = () => {
105
+ return (
106
+ <ChatProvider>
107
+ <Chat />
108
+ </ChatProvider>
109
+ );
110
+ };
111
+
112
+ export default ChatWrapper;
@@ -0,0 +1,361 @@
1
+ .chat-container {
2
+ display: flex;
3
+ height: 100vh;
4
+ width: 100%;
5
+ position: relative;
6
+ }
7
+
8
+ .chat-sidebar {
9
+ background-color: #f5f5f5;
10
+ border-right: 1px solid #e0e0e0;
11
+ display: flex;
12
+ flex-direction: column;
13
+ }
14
+
15
+ .history-header {
16
+ padding: 16px;
17
+ border-bottom: 1px solid #e0e0e0;
18
+ display: flex;
19
+ justify-content: space-between;
20
+ align-items: center;
21
+ }
22
+
23
+ .history-header h3 {
24
+ margin: 0;
25
+ font-size: 16px;
26
+ color: #333;
27
+ }
28
+
29
+ .close-button {
30
+ background: none;
31
+ border: none;
32
+ font-size: 20px;
33
+ cursor: pointer;
34
+ color: #666;
35
+ }
36
+
37
+ .history-list {
38
+ flex: none;
39
+ width: 280px;
40
+ overflow-y: auto;
41
+ /* padding: 8px; */
42
+ border-right: 1px solid #e0e0e0;
43
+ }
44
+
45
+ .history-item {
46
+ padding: 12px;
47
+ margin-bottom: 8px;
48
+ background-color: white;
49
+ cursor: pointer;
50
+ transition: background-color 0.2s;
51
+ }
52
+
53
+ .history-item:hover {
54
+ background-color: #e8e8e8;
55
+ }
56
+
57
+ .history-item.active {
58
+ background-color: #e3f2fd;
59
+ border-left: 3px solid #2196f3;
60
+ }
61
+
62
+ .history-title {
63
+ font-size: 14px;
64
+ color: #333;
65
+ margin-bottom: 4px;
66
+ }
67
+
68
+ .history-time {
69
+ font-size: 12px;
70
+ color: #666;
71
+ }
72
+
73
+ .chat-main {
74
+ overflow: hidden;
75
+ flex: 1;
76
+ display: flex;
77
+ flex-direction: column;
78
+ }
79
+
80
+ .chat-header {
81
+ padding: 16px;
82
+ border-bottom: 1px solid #e0e0e0;
83
+ display: flex;
84
+ justify-content: flex-end;
85
+ gap: 0.5rem;
86
+ }
87
+
88
+ .history-button {
89
+ padding: 8px 16px;
90
+ background-color: #f0f0f0;
91
+ border: 1px solid #e0e0e0;
92
+ border-radius: 4px;
93
+ cursor: pointer;
94
+ font-size: 14px;
95
+ color: #333;
96
+ }
97
+
98
+ .history-button:hover {
99
+ background-color: #e0e0e0;
100
+ }
101
+
102
+ .chat-messages {
103
+ flex: 1;
104
+ overflow-y: auto;
105
+ padding: 1rem;
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 1rem;
109
+ }
110
+
111
+ .message {
112
+ display: flex;
113
+ max-width: 80%;
114
+ }
115
+
116
+ .message.human {
117
+ margin-left: auto;
118
+ }
119
+
120
+ .message.ai {
121
+ margin-right: auto;
122
+ }
123
+
124
+ .message-content {
125
+ padding: 0.75rem 1rem;
126
+ border-radius: 8px;
127
+ border: 1px solid #e5e7eb;
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: 0.5rem;
131
+ }
132
+
133
+ .message-text {
134
+ word-break: break-word;
135
+ }
136
+
137
+ .message-meta {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 0.75rem;
141
+ font-size: 0.75rem;
142
+ color: #6b7280;
143
+ margin-top: 0.25rem;
144
+ }
145
+
146
+ .message-time {
147
+ font-family: monospace;
148
+ }
149
+
150
+ .token-info {
151
+ display: flex;
152
+ gap: 0.5rem;
153
+ align-items: center;
154
+ }
155
+
156
+ .token-item {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 0.25rem;
160
+ background-color: #ffffff;
161
+ padding: 0.125rem 0.375rem;
162
+ border-radius: 4px;
163
+ font-family: monospace;
164
+ }
165
+
166
+ .token-emoji {
167
+ font-size: 0.875rem;
168
+ }
169
+
170
+ .message.human .message-content {
171
+ background-color: #3b82f6;
172
+ color: white;
173
+ border-color: #3b82f6;
174
+ }
175
+
176
+ .message.human .message-meta {
177
+ color: rgba(255, 255, 255, 0.8);
178
+ }
179
+
180
+ .message.ai .message-content {
181
+ background-color: #f3f4f6;
182
+ color: #1f2937;
183
+ }
184
+
185
+ .message.tool {
186
+ width: 100%;
187
+ max-width: 100%;
188
+ }
189
+
190
+ .tool-message {
191
+ width: 100%;
192
+ border: 1px solid #e5e7eb;
193
+ border-radius: 8px;
194
+ overflow: hidden;
195
+ }
196
+
197
+ .tool-header {
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: space-between;
201
+ padding: 0.5rem 1rem;
202
+ background-color: #f9fafb;
203
+ border-bottom: 1px solid #e5e7eb;
204
+ cursor: pointer;
205
+ }
206
+
207
+ .tool-header:hover {
208
+ background-color: #f3f4f6;
209
+ }
210
+
211
+ .tool-title {
212
+ font-weight: 500;
213
+ color: #374151;
214
+ }
215
+
216
+ .tool-content {
217
+ padding: 1rem;
218
+ }
219
+
220
+ .tool-input {
221
+ background-color: #f9fafb;
222
+ padding: 0.75rem;
223
+ border-radius: 4px;
224
+ margin-bottom: 0.5rem;
225
+ font-family: monospace;
226
+ white-space: pre-wrap;
227
+ word-break: break-all;
228
+ }
229
+
230
+ .tool-output {
231
+ background-color: #ffffff;
232
+ padding: 0.75rem;
233
+ border-radius: 4px;
234
+ font-family: monospace;
235
+ border: 1px solid #e5e7eb;
236
+ margin-bottom: 0.5rem;
237
+ }
238
+
239
+ .chat-input {
240
+ border-top: 1px solid #e5e7eb;
241
+ padding: 0 1rem 1rem 1rem;
242
+ background-color: #ffffff;
243
+ }
244
+ .chat-input-header {
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: space-between;
248
+ padding: 0.5rem 1rem;
249
+ }
250
+ .input-container {
251
+ display: flex;
252
+ gap: 0.5rem;
253
+ }
254
+
255
+ .input-textarea {
256
+ flex: 1;
257
+ padding: 0.75rem;
258
+ border: 1px solid #e5e7eb;
259
+ border-radius: 8px;
260
+ resize: none;
261
+ font-size: 0.875rem;
262
+ line-height: 1.25rem;
263
+ }
264
+
265
+ .input-textarea:focus {
266
+ outline: none;
267
+ border-color: #3b82f6;
268
+ }
269
+
270
+ .send-button {
271
+ padding: 0.5rem 1rem;
272
+ background-color: #3b82f6;
273
+ color: white;
274
+ border: none;
275
+ border-radius: 8px;
276
+ cursor: pointer;
277
+ font-weight: 500;
278
+ transition: all 0.2s;
279
+ }
280
+
281
+ .send-button:hover {
282
+ background-color: #2563eb;
283
+ }
284
+
285
+ .send-button:disabled {
286
+ background-color: #93c5fd;
287
+ cursor: not-allowed;
288
+ }
289
+
290
+ .send-button.interrupt {
291
+ background-color: #ef4444;
292
+ }
293
+
294
+ .send-button.interrupt:hover {
295
+ background-color: #dc2626;
296
+ }
297
+
298
+ .collapsed .tool-content {
299
+ display: none;
300
+ }
301
+
302
+ .expand-icon {
303
+ transition: transform 0.2s;
304
+ }
305
+
306
+ .collapsed .expand-icon {
307
+ transform: rotate(-90deg);
308
+ }
309
+
310
+ .loading-indicator {
311
+ padding: 12px 16px;
312
+ margin: 8px 0;
313
+ background-color: #f5f5f5;
314
+ border-radius: 8px;
315
+ color: #666;
316
+ font-size: 14px;
317
+ text-align: center;
318
+ animation: pulse 1.5s infinite;
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ gap: 12px;
323
+ }
324
+
325
+ .interrupt-button {
326
+ padding: 4px 12px;
327
+ background-color: #ef4444;
328
+ color: white;
329
+ border: none;
330
+ border-radius: 4px;
331
+ cursor: pointer;
332
+ font-size: 12px;
333
+ transition: background-color 0.2s;
334
+ }
335
+
336
+ .interrupt-button:hover {
337
+ background-color: #dc2626;
338
+ }
339
+
340
+ @keyframes pulse {
341
+ 0% {
342
+ opacity: 0.6;
343
+ }
344
+ 50% {
345
+ opacity: 1;
346
+ }
347
+ 100% {
348
+ opacity: 0.6;
349
+ }
350
+ }
351
+
352
+ .error-message {
353
+ padding: 12px 16px;
354
+ margin: 8px 0;
355
+ background-color: #fee2e2;
356
+ border: 1px solid #fecaca;
357
+ border-radius: 8px;
358
+ color: #dc2626;
359
+ font-size: 14px;
360
+ text-align: center;
361
+ }