@langgraph-js/ui 1.2.1 → 1.3.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.
- package/dist/assets/index-88Ds6iYK.js +214 -0
- package/dist/assets/index-_qsGvf6N.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +5 -3
- package/src/chat/Chat.tsx +20 -2
- package/src/chat/chat.css +1 -1
- package/src/chat/context/ChatContext.tsx +1 -0
- package/src/chat/store/index.ts +13 -0
- package/src/graph/GraphPanel.tsx +5 -0
- package/src/graph/flattenGraph.ts +45 -0
- package/src/graph/flow.css +176 -0
- package/src/graph/index.tsx +161 -0
- package/vite.config.ts +6 -1
- package/dist/assets/index-BlHtM5cu.css +0 -1
- package/dist/assets/index-C7SfDwhG.js +0 -192
|
@@ -0,0 +1 @@
|
|
|
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;border-right:1px solid #e0e0e0}.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:.5rem 1rem 1rem;background-color:#fff}.chat-input-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.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}.edit-params-button{padding:6px 12px;background-color:#f0f0f0;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer;font-size:.8rem;color:#333;white-space:nowrap}.edit-params-button:hover{background-color:#e0e0e0}.file-list{display:flex;gap:.5rem;border-radius:8px;flex:1}.file-list-header{margin-bottom:1rem}.file-upload-button{display:inline-flex;align-items:center;justify-content:center;width:80px;height:80px;color:#929292;background-color:#ebebeb;border-radius:6px;cursor:pointer;transition:all .2s}.file-upload-button svg{width:32px;height:32px}.file-upload-button.empty{width:32px;height:32px}.file-upload-button.empty svg{width:20px;height:20px}.file-list-content{display:flex;flex-wrap:wrap;gap:.5rem}.file-item{position:relative;width:80px;height:80px;border-radius:6px;overflow:hidden}.file-item img{border:1px solid #e5e7eb;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{color:#000}.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}.json-editor-popup-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.json-editor-popup-content{background-color:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 10px #0000001a;width:80%;max-width:600px;display:flex;flex-direction:column}.json-editor-popup-content h2{margin-top:0;margin-bottom:15px;font-size:1.5rem;color:#333}.json-editor-popup-content textarea{width:100%;padding:10px;border:1px solid #ccc;border-radius:4px;font-family:monospace;font-size:.9rem;resize:vertical;box-sizing:border-box}.json-editor-popup-content .error-message{color:red;font-size:.8rem;margin-top:5px;margin-bottom:10px}.popup-actions{display:flex;justify-content:flex-end;margin-top:15px;gap:10px}.popup-actions button{padding:8px 16px;border:none;border-radius:4px;cursor:pointer;font-size:.9rem}.popup-actions .save-button{background-color:#3b82f6;color:#fff}.popup-actions .save-button:hover{background-color:#2563eb}.popup-actions .cancel-button{background-color:#e5e7eb;color:#374151}.popup-actions .cancel-button:hover{background-color:#d1d5db}.json-to-message-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.json-to-message-content{background-color:#fff;border-radius:8px;box-shadow:0 2px 10px #0003;width:90%;max-width:1200px;height:80vh;max-height:800px;display:flex;flex-direction:column;overflow:hidden}.json-to-message-header{display:flex;justify-content:space-between;align-items:center;padding:15px 20px;border-bottom:1px solid #eaeaea}.json-to-message-header h2{margin:0;font-size:1.5rem;color:#333}.close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#666}.close-button:hover{color:#000}.json-to-message-body{display:flex;flex:1;overflow:hidden}.json-editor-pane{flex:1;padding:15px;display:flex;flex-direction:column;border-right:1px solid #eaeaea}.json-editor-pane textarea{flex:1;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;font-size:.9rem;padding:10px;border:1px solid #ddd;border-radius:4px;resize:none}.error-message{color:#e53e3e;margin-top:10px;font-size:.9rem}.message-preview-pane{flex:1;padding:15px;overflow-y:auto;background-color:#f9f9f9}.preview-container{padding:10px;background-color:#fff;border-radius:4px;min-height:100%}.no-preview{display:flex;align-items:center;justify-content:center;height:100%;color:#666;font-style:italic}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:4px;height:4px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));transform:translate(-50%,-50%)}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}.react-flow{--xy-theme-selected: #77b6ff;--xy-theme-hover: #d1d5db;--xy-theme-edge-hover: #374151;--xy-theme-color-focus: #e5e7eb;--xy-node-border-default: 1px solid #cbd5e1;--xy-node-boxshadow-default: 0 2px 4px rgba(0, 0, 0, .08), 0 1px 3px rgba(0, 0, 0, .06);--xy-node-border-radius-default: 6px;--xy-handle-background-color-default: #f9fafb;--xy-handle-border-color-default: #9ca3af;--xy-edge-label-color-default: #4b5563;--xy-node-color: #777}.react-flow.dark{--xy-node-boxshadow-default: 0 2px 4px rgba(255, 255, 255, .06), 0 1px 3px rgba(255, 255, 255, .04);--xy-theme-color-focus: #4a5568}.react-flow__node{box-shadow:var(--xy-node-boxshadow-default);border-radius:var(--xy-node-border-radius-default);background-color:var(--xy-node-background-color-default);display:flex;justify-content:center;align-items:center;text-align:center;padding:12px;font-size:14px;flex-direction:column;border:var(--xy-node-border-default);color:var(--xy-node-color, var(--xy-node-color-default))}.react-flow__node.selectable:focus{box-shadow:0 0 0 3px var(--xy-theme-color-focus);border-color:#9ca3af}.react-flow__node.selectable:focus:active{box-shadow:var(--xy-node-boxshadow-default)}.react-flow__node.selectable:hover,.react-flow__node.draggable:hover{border-color:var(--xy-theme-hover);cursor:grab}.react-flow__node.selectable.selected{border-color:var(--xy-theme-selected);box-shadow:0 0 0 2px var(--xy-theme-selected)}.react-flow__node-group{background-color:#93c5fd33;border-color:#77b6ff;border-style:dashed}.react-flow__edge.selectable:hover .react-flow__edge-path,.react-flow__edge.selectable.selected .react-flow__edge-path{stroke:var(--xy-theme-edge-hover);stroke-width:1.5px}.react-flow__handle{background-color:var(--xy-handle-background-color-default);border:1px solid var(--xy-handle-border-color-default);border-radius:3px;width:10px;height:10px}.react-flow__handle.connectionindicator:hover{pointer-events:all;border-color:var(--xy-theme-edge-hover);background-color:#fff}.react-flow__handle.connectionindicator:focus,.react-flow__handle.connectingfrom,.react-flow__handle.connectingto{border-color:var(--xy-theme-edge-hover);background-color:var(--xy-theme-selected)}.react-flow__node-resizer{border-radius:2px;border:1px solid #cbd5e1;background-color:#f9fafb}.react-flow__resize-control.handle{background-color:var(--xy-theme-selected);border-color:var(--xy-theme-selected);border-radius:2px;width:7px;height:7px}.react-flow__node-group{border-style:dashed;background-color:transparent;border-color:transparent;font-size:.5rem}.react-flow .react-flow__node-group:nth-child(1){background-color:#b3e5fc4d;border-color:#4fc3f7;color:#4fc3f7}.react-flow .react-flow__node-group:nth-child(2){background-color:#c8e6c94d;border-color:#81c784;color:#81c784}.react-flow .react-flow__node-group:nth-child(3){background-color:#ffe0b24d;border-color:#ffb74d;color:#ffb74d}.react-flow .react-flow__node-group:nth-child(4){background-color:#e1bee74d;border-color:#ba68c8;color:#ba68c8}.react-flow .react-flow__node-group:nth-child(5){background-color:#ffcccc4d;border-color:#e57373;color:#e57373}.react-flow .react-flow__node-group:nth-child(6){background-color:#fff59d4d;border-color:#ffee58;color:#ffee58}.react-flow .react-flow__node-group:nth-child(7){background-color:#a5d6a74d;border-color:#66bb6a;color:#66bb6a}.react-flow .react-flow__node-group:nth-child(8){background-color:#f48fb14d;border-color:#f06292;color:#f06292}.react-flow .react-flow__node-group:nth-child(9){background-color:#d2b48c4d;border-color:#a1887f;color:#a1887f}.react-flow .react-flow__node-group:nth-child(10){background-color:#c5b0d54d;border-color:#9575cd;color:#9575cd}.react-flow__handle.connectionindicator{visibility:hidden}.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}
|
package/dist/index.html
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
height: 100%;
|
|
15
15
|
}
|
|
16
16
|
</style>
|
|
17
|
-
<script type="module" crossorigin src="/assets/index-
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
17
|
+
<script type="module" crossorigin src="/assets/index-88Ds6iYK.js"></script>
|
|
18
|
+
<link rel="stylesheet" crossorigin href="/assets/index-_qsGvf6N.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
|
21
21
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langgraph-js/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
"main": "./dist/react/src/index.js",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@dagrejs/dagre": "^1.1.4",
|
|
15
16
|
"@nanostores/react": "^1.0.0",
|
|
16
17
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
|
17
18
|
"@vitejs/plugin-react": "^4.3.4",
|
|
19
|
+
"@xyflow/react": "^12.6.4",
|
|
18
20
|
"nanostores": "^1.0.1",
|
|
19
21
|
"react": "^19.0.0",
|
|
20
22
|
"react-dom": "^19.0.0",
|
|
21
|
-
"vite": "^6.2.0",
|
|
22
23
|
"react-markdown": "^10.1.0",
|
|
23
24
|
"remark-gfm": "^4.0.1",
|
|
24
|
-
"
|
|
25
|
+
"vite": "^6.2.0",
|
|
26
|
+
"@langgraph-js/sdk": "1.2.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"@types/react": "^19.0.10",
|
package/src/chat/Chat.tsx
CHANGED
|
@@ -9,6 +9,8 @@ import { formatTime, Message } from "@langgraph-js/sdk";
|
|
|
9
9
|
import FileList from "./components/FileList";
|
|
10
10
|
import JsonEditorPopup from "./components/JsonEditorPopup";
|
|
11
11
|
import { JsonToMessageButton } from "./components/JsonToMessage";
|
|
12
|
+
import { GraphPanel } from "../graph/GraphPanel";
|
|
13
|
+
import { setLocalConfig } from "./store";
|
|
12
14
|
|
|
13
15
|
const ChatMessages: React.FC = () => {
|
|
14
16
|
const { renderMessages, loading, inChatError, client, collapsedTools, toggleToolCollapse } = useChat();
|
|
@@ -105,7 +107,7 @@ const ChatInput: React.FC = () => {
|
|
|
105
107
|
|
|
106
108
|
const Chat: React.FC = () => {
|
|
107
109
|
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
|
108
|
-
const { showHistory, toggleHistoryVisible } = useChat();
|
|
110
|
+
const { showHistory, toggleHistoryVisible, showGraph, toggleGraphVisible } = useChat();
|
|
109
111
|
const { extraParams, setExtraParams } = useExtraParams();
|
|
110
112
|
|
|
111
113
|
return (
|
|
@@ -117,9 +119,24 @@ const Chat: React.FC = () => {
|
|
|
117
119
|
<button onClick={() => setIsPopupOpen(true)} className="edit-params-button">
|
|
118
120
|
编辑参数
|
|
119
121
|
</button>
|
|
120
|
-
<button
|
|
122
|
+
<button
|
|
123
|
+
className="history-button"
|
|
124
|
+
onClick={() => {
|
|
125
|
+
toggleHistoryVisible();
|
|
126
|
+
setLocalConfig({ showHistory: !showHistory });
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
121
129
|
历史记录
|
|
122
130
|
</button>
|
|
131
|
+
<button
|
|
132
|
+
className="graph-button"
|
|
133
|
+
onClick={() => {
|
|
134
|
+
toggleGraphVisible();
|
|
135
|
+
setLocalConfig({ showGraph: !showGraph });
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
图
|
|
139
|
+
</button>
|
|
123
140
|
<button
|
|
124
141
|
className="history-button"
|
|
125
142
|
onClick={() => {
|
|
@@ -134,6 +151,7 @@ const Chat: React.FC = () => {
|
|
|
134
151
|
<ChatInput />
|
|
135
152
|
<JsonEditorPopup isOpen={isPopupOpen} initialJson={extraParams} onClose={() => setIsPopupOpen(false)} onSave={setExtraParams} />
|
|
136
153
|
</div>
|
|
154
|
+
{showGraph && <GraphPanel />}
|
|
137
155
|
</div>
|
|
138
156
|
);
|
|
139
157
|
};
|
package/src/chat/chat.css
CHANGED
package/src/chat/store/index.ts
CHANGED
|
@@ -7,6 +7,18 @@ const F =
|
|
|
7
7
|
}
|
|
8
8
|
: fetch;
|
|
9
9
|
|
|
10
|
+
const getLocalConfig = () => {
|
|
11
|
+
return {
|
|
12
|
+
showHistory: localStorage.getItem("showHistory") === "true" || false,
|
|
13
|
+
showGraph: localStorage.getItem("showGraph") === "true" || false,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export const setLocalConfig = (config: Partial<{ showHistory: boolean; showGraph: boolean }>) => {
|
|
17
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
18
|
+
localStorage.setItem(key, value.toString());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
10
22
|
export const globalChatStore = createChatStore(
|
|
11
23
|
localStorage.getItem("agent_name") || "",
|
|
12
24
|
{
|
|
@@ -18,6 +30,7 @@ export const globalChatStore = createChatStore(
|
|
|
18
30
|
},
|
|
19
31
|
},
|
|
20
32
|
{
|
|
33
|
+
...getLocalConfig(),
|
|
21
34
|
onInit(client) {
|
|
22
35
|
client.tools.bindTools([]);
|
|
23
36
|
},
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AssistantGraph } from "@langgraph-js/sdk";
|
|
2
|
+
import { Node } from "@xyflow/react";
|
|
3
|
+
|
|
4
|
+
export function flattenGraph(graph: AssistantGraph["nodes"]) {
|
|
5
|
+
const flatParents = new Map<string, Node>();
|
|
6
|
+
const finalNodes: Node[] = [];
|
|
7
|
+
const createParentNode = (node: Node) => {
|
|
8
|
+
const parts = node.id.split(":");
|
|
9
|
+
const parentId = parts.slice(0, -1).join(":");
|
|
10
|
+
if (parts.length === 1) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (!flatParents.has(parentId)) {
|
|
14
|
+
const parentNode: Node = {
|
|
15
|
+
id: parentId,
|
|
16
|
+
type: "group",
|
|
17
|
+
data: {
|
|
18
|
+
label: parentId.split(":").pop(),
|
|
19
|
+
name: parentId.split(":").pop(),
|
|
20
|
+
},
|
|
21
|
+
position: { x: 0, y: 0 },
|
|
22
|
+
style: {},
|
|
23
|
+
parentId: undefined,
|
|
24
|
+
};
|
|
25
|
+
const p = createParentNode(parentNode);
|
|
26
|
+
if (p) {
|
|
27
|
+
parentNode.parentId = p.id;
|
|
28
|
+
}
|
|
29
|
+
flatParents.set(parentId, parentNode);
|
|
30
|
+
}
|
|
31
|
+
return flatParents.get(parentId);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
graph.forEach((node) => {
|
|
35
|
+
const flowNode: Node = { ...node, id: node.id.toString(), position: { x: 0, y: 0 }, data: { label: (node.id as string).split(":").pop(), name: (node.id as string).split(":").pop() } };
|
|
36
|
+
const parentNode = createParentNode(flowNode);
|
|
37
|
+
if (parentNode) {
|
|
38
|
+
flowNode.parentId = parentNode.id.toString();
|
|
39
|
+
}
|
|
40
|
+
finalNodes.push(flowNode);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const data = [...Array.from(flatParents.values()), ...finalNodes];
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/* xyflow theme files - Adjusted for a cleaner look */
|
|
2
|
+
.react-flow {
|
|
3
|
+
/* Custom Variables - Refined Palette */
|
|
4
|
+
--xy-theme-selected: #77b6ff; /* 更柔和且更具现代感的蓝色 */
|
|
5
|
+
--xy-theme-hover: #d1d5db; /* 更柔和的悬停效果 */
|
|
6
|
+
--xy-theme-edge-hover: #374151; /* 深灰色,更专业 */
|
|
7
|
+
--xy-theme-color-focus: #e5e7eb; /* 更浅的焦点颜色 */
|
|
8
|
+
|
|
9
|
+
/* Built-in Variables - Subtle Adjustments */
|
|
10
|
+
--xy-node-border-default: 1px solid #cbd5e1; /* 更柔和的边框 */
|
|
11
|
+
--xy-node-boxshadow-default: 0 2px 4px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06); /* 更柔和的阴影 */
|
|
12
|
+
--xy-node-border-radius-default: 6px; /* 略微减小圆角 */
|
|
13
|
+
--xy-handle-background-color-default: #f9fafb; /* 更柔和的连接点背景 */
|
|
14
|
+
--xy-handle-border-color-default: #9ca3af; /* 更柔和的连接点边框 */
|
|
15
|
+
--xy-edge-label-color-default: #4b5563; /* 更深的标签颜色 */
|
|
16
|
+
--xy-node-color: #777;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.react-flow.dark {
|
|
20
|
+
--xy-node-boxshadow-default: 0 2px 4px rgba(255, 255, 255, 0.06), 0 1px 3px rgba(255, 255, 255, 0.04); /* 调整暗色阴影 */
|
|
21
|
+
--xy-theme-color-focus: #4a5568; /* 更深的暗色焦点颜色 */
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Customizing Default Theming - Enhanced Visuals */
|
|
25
|
+
|
|
26
|
+
.react-flow__node {
|
|
27
|
+
box-shadow: var(--xy-node-boxshadow-default);
|
|
28
|
+
border-radius: var(--xy-node-border-radius-default);
|
|
29
|
+
background-color: var(--xy-node-background-color-default);
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
align-items: center;
|
|
33
|
+
text-align: center;
|
|
34
|
+
padding: 12px; /* 略微增加内边距 */
|
|
35
|
+
font-size: 14px; /* 略微增大字体 */
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
border: var(--xy-node-border-default);
|
|
38
|
+
color: var(--xy-node-color, var(--xy-node-color-default));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.react-flow__node.selectable:focus {
|
|
42
|
+
box-shadow: 0 0 0 3px var(--xy-theme-color-focus); /* 更柔和的焦点边框 */
|
|
43
|
+
border-color: #9ca3af; /* 更柔和的焦点边框颜色 */
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.react-flow__node.selectable:focus:active {
|
|
47
|
+
box-shadow: var(--xy-node-boxshadow-default);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.react-flow__node.selectable:hover,
|
|
51
|
+
.react-flow__node.draggable:hover {
|
|
52
|
+
border-color: var(--xy-theme-hover);
|
|
53
|
+
cursor: grab; /* 增加手型光标 */
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.react-flow__node.selectable.selected {
|
|
57
|
+
border-color: var(--xy-theme-selected);
|
|
58
|
+
box-shadow: 0 0 0 2px var(--xy-theme-selected); /* 更清晰的选中效果 */
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.react-flow__node-group {
|
|
62
|
+
background-color: rgba(147, 197, 253, 0.2); /* 更柔和的 Group 背景 */
|
|
63
|
+
border-color: #77b6ff; /* 与选中颜色一致 */
|
|
64
|
+
border-style: dashed; /* 虚线边框 */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.react-flow__edge.selectable:hover .react-flow__edge-path,
|
|
68
|
+
.react-flow__edge.selectable.selected .react-flow__edge-path {
|
|
69
|
+
stroke: var(--xy-theme-edge-hover);
|
|
70
|
+
stroke-width: 1.5px; /* 略微加粗选中的边 */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.react-flow__handle {
|
|
74
|
+
background-color: var(--xy-handle-background-color-default);
|
|
75
|
+
border: 1px solid var(--xy-handle-border-color-default); /* 添加边框 */
|
|
76
|
+
border-radius: 3px; /* 略微圆角 */
|
|
77
|
+
width: 10px; /* 略微增大尺寸 */
|
|
78
|
+
height: 10px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.react-flow__handle.connectionindicator:hover {
|
|
82
|
+
pointer-events: all;
|
|
83
|
+
border-color: var(--xy-theme-edge-hover);
|
|
84
|
+
background-color: white;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.react-flow__handle.connectionindicator:focus,
|
|
88
|
+
.react-flow__handle.connectingfrom,
|
|
89
|
+
.react-flow__handle.connectingto {
|
|
90
|
+
border-color: var(--xy-theme-edge-hover);
|
|
91
|
+
background-color: var(--xy-theme-selected); /* 连接时高亮 */
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.react-flow__node-resizer {
|
|
95
|
+
border-radius: 2px; /* 略微圆角 */
|
|
96
|
+
border: 1px solid #cbd5e1; /* 更柔和的边框 */
|
|
97
|
+
background-color: #f9fafb; /* 与 handle 颜色一致 */
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.react-flow__resize-control.handle {
|
|
101
|
+
background-color: var(--xy-theme-selected); /* 使用选中颜色 */
|
|
102
|
+
border-color: var(--xy-theme-selected);
|
|
103
|
+
border-radius: 2px; /* 略微圆角 */
|
|
104
|
+
width: 7px; /* 略微增大尺寸 */
|
|
105
|
+
height: 7px; /* 略微增大尺寸 */
|
|
106
|
+
}
|
|
107
|
+
.react-flow__node-group {
|
|
108
|
+
border-style: dashed;
|
|
109
|
+
background-color: transparent;
|
|
110
|
+
border-color: transparent;
|
|
111
|
+
font-size: 0.5rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.react-flow .react-flow__node-group:nth-child(1) {
|
|
115
|
+
background-color: rgba(179, 229, 252, 0.3); /* 非常浅的蓝色 */
|
|
116
|
+
border-color: #4fc3f7; /* 柔和的蓝色 */
|
|
117
|
+
color: #4fc3f7;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.react-flow .react-flow__node-group:nth-child(2) {
|
|
121
|
+
background-color: rgba(200, 230, 201, 0.3); /* 非常浅的绿色 */
|
|
122
|
+
border-color: #81c784; /* 柔和的绿色 */
|
|
123
|
+
color: #81c784;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.react-flow .react-flow__node-group:nth-child(3) {
|
|
127
|
+
background-color: rgba(255, 224, 178, 0.3); /* 非常浅的橙色 */
|
|
128
|
+
border-color: #ffb74d; /* 柔和的橙色 */
|
|
129
|
+
color: #ffb74d;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.react-flow .react-flow__node-group:nth-child(4) {
|
|
133
|
+
background-color: rgba(225, 190, 231, 0.3); /* 非常浅的紫色 */
|
|
134
|
+
border-color: #ba68c8; /* 柔和的紫色 */
|
|
135
|
+
color: #ba68c8;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.react-flow .react-flow__node-group:nth-child(5) {
|
|
139
|
+
background-color: rgba(255, 204, 204, 0.3); /* 非常浅的粉色 */
|
|
140
|
+
border-color: #e57373; /* 柔和的粉色 */
|
|
141
|
+
color: #e57373;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* 更多柔和的颜色 */
|
|
145
|
+
.react-flow .react-flow__node-group:nth-child(6) {
|
|
146
|
+
background-color: rgba(255, 245, 157, 0.3); /* 非常浅的黄色 */
|
|
147
|
+
border-color: #ffee58; /* 柔和的黄色 */
|
|
148
|
+
color: #ffee58;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.react-flow .react-flow__node-group:nth-child(7) {
|
|
152
|
+
background-color: rgba(165, 214, 167, 0.3); /* 非常浅的薄荷绿 */
|
|
153
|
+
border-color: #66bb6a; /* 柔和的薄荷绿 */
|
|
154
|
+
color: #66bb6a;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.react-flow .react-flow__node-group:nth-child(8) {
|
|
158
|
+
background-color: rgba(244, 143, 177, 0.3); /* 非常浅的玫瑰色 */
|
|
159
|
+
border-color: #f06292; /* 柔和的玫瑰色 */
|
|
160
|
+
color: #f06292;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.react-flow .react-flow__node-group:nth-child(9) {
|
|
164
|
+
background-color: rgba(210, 180, 140, 0.3); /* 非常浅的卡其色 */
|
|
165
|
+
border-color: #a1887f; /* 柔和的棕色 */
|
|
166
|
+
color: #a1887f;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.react-flow .react-flow__node-group:nth-child(10) {
|
|
170
|
+
background-color: rgba(197, 176, 213, 0.3); /* 非常浅的薰衣草色 */
|
|
171
|
+
border-color: #9575cd; /* 柔和的薰衣草色 */
|
|
172
|
+
color: #9575cd;
|
|
173
|
+
}
|
|
174
|
+
.react-flow__handle.connectionindicator {
|
|
175
|
+
visibility: hidden;
|
|
176
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ReactFlow, Background, Controls, Node, Edge, ReactFlowProvider, useNodesState, useEdgesState, useReactFlow, Panel } from "@xyflow/react";
|
|
2
|
+
import "@xyflow/react/dist/style.css";
|
|
3
|
+
import { useCallback, useEffect } from "react";
|
|
4
|
+
import { useChat } from "../chat/context/ChatContext";
|
|
5
|
+
import Dagre from "@dagrejs/dagre";
|
|
6
|
+
import { flattenGraph } from "./flattenGraph";
|
|
7
|
+
import { AssistantGraph } from "@langgraph-js/sdk";
|
|
8
|
+
import "./flow.css";
|
|
9
|
+
const nodeTypes = {
|
|
10
|
+
group: ({ data }: { data: any }) => (
|
|
11
|
+
<div style={{ position: "absolute", bottom: "100%", left: 0 }}>
|
|
12
|
+
<span>{data.name}</span>
|
|
13
|
+
</div>
|
|
14
|
+
),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const transformEdges = (edges: AssistantGraph["edges"], nodes: Node[]): Edge[] => {
|
|
18
|
+
const newEdges = edges.map((edge): Edge => {
|
|
19
|
+
const sourceNode = nodes.find((n) => n.id === edge.source.toString());
|
|
20
|
+
const targetNode = nodes.find((n) => n.id === edge.target.toString());
|
|
21
|
+
const sourceId = sourceNode?.id;
|
|
22
|
+
const targetId = targetNode?.id;
|
|
23
|
+
return {
|
|
24
|
+
id: `${sourceId}=${targetId}`,
|
|
25
|
+
source: sourceId!,
|
|
26
|
+
target: targetId!,
|
|
27
|
+
// type: edge.conditional ? "smoothstep" : "straight",
|
|
28
|
+
animated: edge.conditional,
|
|
29
|
+
label: edge.data,
|
|
30
|
+
style: {
|
|
31
|
+
stroke: edge.conditional ? "#2563eb" : "#64748b",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
if (!newEdges.find((i) => i.target === "__end__")) {
|
|
36
|
+
const end = [...nodes].reverse().find((i) => i.id.endsWith(":__end__"));
|
|
37
|
+
if (end) {
|
|
38
|
+
newEdges.push({
|
|
39
|
+
id: `${end.id}=__end__`,
|
|
40
|
+
source: end.id,
|
|
41
|
+
target: "__end__",
|
|
42
|
+
type: "smoothstep",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return newEdges;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const LayoutFlow = () => {
|
|
50
|
+
const { fitView } = useReactFlow();
|
|
51
|
+
const { graphVisualize } = useChat();
|
|
52
|
+
const graphData = graphVisualize || { nodes: [], edges: [] };
|
|
53
|
+
|
|
54
|
+
const initialNodes = flattenGraph(graphData.nodes.map((node) => ({ ...node, type: "default" })));
|
|
55
|
+
const initialEdges = transformEdges(graphData.edges, initialNodes);
|
|
56
|
+
|
|
57
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
58
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
59
|
+
const onLayout = useCallback(
|
|
60
|
+
(direction: "TB" | "LR") => {
|
|
61
|
+
const layouted = getLayoutedElements(nodes, edges, { direction });
|
|
62
|
+
setNodes([...layouted.nodes]);
|
|
63
|
+
setEdges([...layouted.edges]);
|
|
64
|
+
fitView();
|
|
65
|
+
},
|
|
66
|
+
[nodes, edges]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (graphData.nodes.length > 0) {
|
|
71
|
+
const layouted = getLayoutedElements(initialNodes, initialEdges, { direction: "TB" });
|
|
72
|
+
setNodes([...layouted.nodes]);
|
|
73
|
+
setEdges([...layouted.edges]);
|
|
74
|
+
fitView();
|
|
75
|
+
}
|
|
76
|
+
}, [graphData]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div style={{ width: "30%", height: "100%", position: "relative", overflow: "hidden" }}>
|
|
80
|
+
<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} fitView className="w-full h-full" nodeTypes={nodeTypes}>
|
|
81
|
+
<Background />
|
|
82
|
+
<Controls />
|
|
83
|
+
<Panel position="top-right">
|
|
84
|
+
<button onClick={() => onLayout("TB")}>垂直布局</button>
|
|
85
|
+
<button onClick={() => onLayout("LR")}>水平布局</button>
|
|
86
|
+
</Panel>
|
|
87
|
+
</ReactFlow>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getLayoutedElements = (nodes: Node[], edges: Edge[], options: { direction: "TB" | "LR" }) => {
|
|
93
|
+
const g = new Dagre.graphlib.Graph({ compound: true, multigraph: true }).setDefaultEdgeLabel(() => ({}));
|
|
94
|
+
g.setGraph({ rankdir: options.direction, nodesep: 40, ranksep: 40, edgesep: 20 });
|
|
95
|
+
|
|
96
|
+
nodes.forEach((node) => {
|
|
97
|
+
if (node.type === "group") {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
g.setNode(node.id, {
|
|
101
|
+
...node,
|
|
102
|
+
width: 128,
|
|
103
|
+
height: 20,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
|
|
107
|
+
|
|
108
|
+
Dagre.layout(g);
|
|
109
|
+
const newNodes = nodes.map((node) => {
|
|
110
|
+
const position = g.node(node.id);
|
|
111
|
+
if (!position) return { ...node };
|
|
112
|
+
let x = 0;
|
|
113
|
+
let y = 0;
|
|
114
|
+
|
|
115
|
+
x = position.x + (position?.width ?? 0);
|
|
116
|
+
y = position.y + (position?.height ?? 0);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
...node,
|
|
120
|
+
position: { x, y },
|
|
121
|
+
_p: position,
|
|
122
|
+
width: position.width,
|
|
123
|
+
height: position.height,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const children = new Map<string, Node[]>();
|
|
128
|
+
const padding = 15;
|
|
129
|
+
[...newNodes].reverse().forEach((node) => {
|
|
130
|
+
if (node.type === "group") {
|
|
131
|
+
const nodes = children.get(node.id!) || [];
|
|
132
|
+
const minX = Math.min(...nodes.map((i) => i.position.x));
|
|
133
|
+
const minY = Math.min(...nodes.map((i) => i.position.y));
|
|
134
|
+
const maxX = Math.max(...nodes.map((i) => i.position.x + (i.width ?? 0)));
|
|
135
|
+
const maxY = Math.max(...nodes.map((i) => i.position.y + (i.height ?? 0)));
|
|
136
|
+
node.position.x = minX - padding;
|
|
137
|
+
node.position.y = minY - padding;
|
|
138
|
+
node.width = maxX - minX + padding * 2;
|
|
139
|
+
node.height = maxY - minY + padding * 2;
|
|
140
|
+
nodes.forEach((i) => {
|
|
141
|
+
i.position.x = i.position.x - node.position.x;
|
|
142
|
+
i.position.y = i.position.y - node.position.y;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (node.parentId) {
|
|
146
|
+
children.set(node.parentId!, [...(children.get(node.parentId!) ?? []), node]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
nodes: newNodes,
|
|
151
|
+
edges,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export function Graph() {
|
|
156
|
+
return (
|
|
157
|
+
<ReactFlowProvider>
|
|
158
|
+
<LayoutFlow />
|
|
159
|
+
</ReactFlowProvider>
|
|
160
|
+
);
|
|
161
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import { defineConfig } from "vite";
|
|
3
3
|
import basicSsl from "@vitejs/plugin-basic-ssl";
|
|
4
|
-
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
// https://vitejs.dev/config/
|
|
6
6
|
export default defineConfig(({ mode }) => {
|
|
7
7
|
const isHttps = mode === "https";
|
|
8
8
|
return {
|
|
9
9
|
plugins: [react(), isHttps ? basicSsl() : undefined],
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
"@langgraph-js/sdk": new URL("../langgraph-client/src", import.meta.url).pathname,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
10
15
|
optimizeDeps: {
|
|
11
16
|
exclude: ["@langgraph-js/ui", "@langgraph-js/sdk"],
|
|
12
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:.5rem 1rem 1rem;background-color:#fff}.chat-input-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.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}.edit-params-button{padding:6px 12px;background-color:#f0f0f0;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer;font-size:.8rem;color:#333;white-space:nowrap}.edit-params-button:hover{background-color:#e0e0e0}.file-list{display:flex;gap:.5rem;border-radius:8px;flex:1}.file-list-header{margin-bottom:1rem}.file-upload-button{display:inline-flex;align-items:center;justify-content:center;width:80px;height:80px;color:#929292;background-color:#ebebeb;border-radius:6px;cursor:pointer;transition:all .2s}.file-upload-button svg{width:32px;height:32px}.file-upload-button.empty{width:32px;height:32px}.file-upload-button.empty svg{width:20px;height:20px}.file-list-content{display:flex;flex-wrap:wrap;gap:.5rem}.file-item{position:relative;width:80px;height:80px;border-radius:6px;overflow:hidden}.file-item img{border:1px solid #e5e7eb;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{color:#000}.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}.json-editor-popup-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.json-editor-popup-content{background-color:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 10px #0000001a;width:80%;max-width:600px;display:flex;flex-direction:column}.json-editor-popup-content h2{margin-top:0;margin-bottom:15px;font-size:1.5rem;color:#333}.json-editor-popup-content textarea{width:100%;padding:10px;border:1px solid #ccc;border-radius:4px;font-family:monospace;font-size:.9rem;resize:vertical;box-sizing:border-box}.json-editor-popup-content .error-message{color:red;font-size:.8rem;margin-top:5px;margin-bottom:10px}.popup-actions{display:flex;justify-content:flex-end;margin-top:15px;gap:10px}.popup-actions button{padding:8px 16px;border:none;border-radius:4px;cursor:pointer;font-size:.9rem}.popup-actions .save-button{background-color:#3b82f6;color:#fff}.popup-actions .save-button:hover{background-color:#2563eb}.popup-actions .cancel-button{background-color:#e5e7eb;color:#374151}.popup-actions .cancel-button:hover{background-color:#d1d5db}.json-to-message-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.json-to-message-content{background-color:#fff;border-radius:8px;box-shadow:0 2px 10px #0003;width:90%;max-width:1200px;height:80vh;max-height:800px;display:flex;flex-direction:column;overflow:hidden}.json-to-message-header{display:flex;justify-content:space-between;align-items:center;padding:15px 20px;border-bottom:1px solid #eaeaea}.json-to-message-header h2{margin:0;font-size:1.5rem;color:#333}.close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#666}.close-button:hover{color:#000}.json-to-message-body{display:flex;flex:1;overflow:hidden}.json-editor-pane{flex:1;padding:15px;display:flex;flex-direction:column;border-right:1px solid #eaeaea}.json-editor-pane textarea{flex:1;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;font-size:.9rem;padding:10px;border:1px solid #ddd;border-radius:4px;resize:none}.error-message{color:#e53e3e;margin-top:10px;font-size:.9rem}.message-preview-pane{flex:1;padding:15px;overflow-y:auto;background-color:#f9f9f9}.preview-container{padding:10px;background-color:#fff;border-radius:4px;min-height:100%}.no-preview{display:flex;align-items:center;justify-content:center;height:100%;color:#666;font-style:italic}.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}
|