@akiojin/gwt 4.1.1 → 4.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/README.md +28 -3
- package/dist/claude.d.ts +4 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +13 -1
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +68 -68
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +3 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +13 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +6 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/client/assets/index-ChHC-Puh.css +1 -0
- package/dist/client/assets/index-PqK9jkug.js +78 -0
- package/dist/client/index.html +2 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -0
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +10 -1
- package/dist/config/tools.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -4
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +15 -0
- package/dist/launcher.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts.map +1 -1
- package/dist/services/aiToolResolver.js +55 -8
- package/dist/services/aiToolResolver.js.map +1 -1
- package/dist/services/customToolResolver.d.ts.map +1 -1
- package/dist/services/customToolResolver.js +22 -17
- package/dist/services/customToolResolver.js.map +1 -1
- package/dist/utils/prompt.d.ts +12 -0
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js +60 -10
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/webui.js +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts +5 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +35 -108
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts +15 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js +57 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.js +103 -0
- package/dist/web/client/src/components/graph/BranchNode.js.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.js +109 -0
- package/dist/web/client/src/components/graph/ClusterNode.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts +17 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js +94 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts +13 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js +113 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts +67 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.js +175 -0
- package/dist/web/client/src/components/graph/graphUtils.js.map +1 -0
- package/dist/web/client/src/components/graph/index.d.ts +10 -0
- package/dist/web/client/src/components/graph/index.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/index.js +10 -0
- package/dist/web/client/src/components/graph/index.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +2 -1
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/vite.config.js +1 -1
- package/dist/web/server/env/importer.d.ts.map +1 -1
- package/dist/web/server/env/importer.js +4 -0
- package/dist/web/server/env/importer.js.map +1 -1
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +9 -0
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +24 -1
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +7 -0
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/tray.d.ts +1 -1
- package/dist/web/server/tray.d.ts.map +1 -1
- package/dist/web/server/tray.js +52 -34
- package/dist/web/server/tray.js.map +1 -1
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +4 -0
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +6 -3
- package/src/claude.ts +15 -1
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +2 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +142 -8
- package/src/cli/ui/__tests__/components/App.test.tsx +4 -3
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +1 -0
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +45 -0
- package/src/cli/ui/components/App.tsx +91 -81
- package/src/cli/ui/components/common/Select.tsx +14 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
- package/src/cli/ui/types.ts +1 -1
- package/src/config/builtin-tools.ts +3 -0
- package/src/config/tools.ts +24 -1
- package/src/index.ts +50 -3
- package/src/launcher.ts +26 -0
- package/src/services/aiToolResolver.ts +75 -9
- package/src/services/customToolResolver.ts +32 -17
- package/src/utils/__tests__/prompt.test.ts +72 -35
- package/src/utils/prompt.ts +79 -10
- package/src/utils/webui.ts +1 -1
- package/src/web/client/src/components/BranchGraph.tsx +51 -208
- package/src/web/client/src/components/graph/BranchDetailPanel.tsx +152 -0
- package/src/web/client/src/components/graph/BranchNode.tsx +200 -0
- package/src/web/client/src/components/graph/ClusterNode.tsx +211 -0
- package/src/web/client/src/components/graph/SynapticCanvas.tsx +171 -0
- package/src/web/client/src/components/graph/SynapticEdge.tsx +311 -0
- package/src/web/client/src/components/graph/graphUtils.ts +265 -0
- package/src/web/client/src/components/graph/index.ts +10 -0
- package/src/web/client/src/index.css +314 -29
- package/src/web/client/src/lib/websocket.ts +2 -1
- package/src/web/client/vite.config.ts +1 -1
- package/src/web/server/env/importer.ts +5 -0
- package/src/web/server/index.ts +10 -0
- package/src/web/server/pty/manager.ts +43 -1
- package/src/web/server/routes/sessions.ts +15 -0
- package/src/web/server/tray.ts +62 -46
- package/src/web/server/websocket/handler.ts +13 -0
- package/dist/client/assets/index-DsDNCy5f.css +0 -1
- package/dist/client/assets/index-v8smkNOL.js +0 -72
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* クラスタノードコンポーネント
|
|
3
|
+
*
|
|
4
|
+
* ニューロン群(神経細胞集合体)をイメージした有機的ノード
|
|
5
|
+
* 変形する膜と軌道を描く核で複数ブランチのグループを表現
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { memo, useMemo } from "react";
|
|
9
|
+
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import type { GraphNode } from "./graphUtils";
|
|
12
|
+
|
|
13
|
+
type ClusterNodeProps = NodeProps<GraphNode>;
|
|
14
|
+
|
|
15
|
+
export const ClusterNode = memo(function ClusterNode({
|
|
16
|
+
data,
|
|
17
|
+
selected,
|
|
18
|
+
}: ClusterNodeProps) {
|
|
19
|
+
const { clusterSize = 0, expanded } = data;
|
|
20
|
+
|
|
21
|
+
// クラスタサイズに応じてノードサイズを調整
|
|
22
|
+
const baseSize = 70;
|
|
23
|
+
const size = Math.min(baseSize + clusterSize * 5, 120);
|
|
24
|
+
|
|
25
|
+
// 内部の核の数(最大8個)
|
|
26
|
+
const nucleiCount = Math.min(clusterSize, 8);
|
|
27
|
+
|
|
28
|
+
// 核の配置角度を計算
|
|
29
|
+
const nuclei = useMemo(() => {
|
|
30
|
+
return Array.from({ length: nucleiCount }).map((_, i) => {
|
|
31
|
+
const angle = (360 / nucleiCount) * i;
|
|
32
|
+
const delay = i * 0.5;
|
|
33
|
+
const orbitRadius = size * 0.28;
|
|
34
|
+
return { angle, delay, orbitRadius };
|
|
35
|
+
});
|
|
36
|
+
}, [nucleiCount, size]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{/* 入力ハンドル */}
|
|
41
|
+
<Handle
|
|
42
|
+
type="target"
|
|
43
|
+
position={Position.Top}
|
|
44
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
45
|
+
style={{
|
|
46
|
+
borderColor: "hsl(185 100% 65%)",
|
|
47
|
+
boxShadow: "0 0 8px hsl(185 100% 65% / 0.4)",
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
{/* クラスタ本体 */}
|
|
52
|
+
<div
|
|
53
|
+
className={cn(
|
|
54
|
+
"group relative flex cursor-pointer items-center justify-center",
|
|
55
|
+
"transition-all duration-500 ease-out",
|
|
56
|
+
"animate-membrane-morph",
|
|
57
|
+
selected &&
|
|
58
|
+
"ring-2 ring-primary ring-offset-4 ring-offset-background",
|
|
59
|
+
)}
|
|
60
|
+
style={{
|
|
61
|
+
width: size,
|
|
62
|
+
height: size,
|
|
63
|
+
background: expanded
|
|
64
|
+
? "radial-gradient(circle at 40% 40%, hsl(185 100% 65% / 0.15), hsl(310 85% 60% / 0.08) 50%, transparent 70%)"
|
|
65
|
+
: "radial-gradient(circle at 40% 40%, hsl(220 30% 15% / 0.8), hsl(220 25% 10% / 0.6) 60%, transparent 80%)",
|
|
66
|
+
border: expanded
|
|
67
|
+
? "2px solid hsl(185 100% 65% / 0.6)"
|
|
68
|
+
: "2px dashed hsl(200 30% 30%)",
|
|
69
|
+
boxShadow: expanded
|
|
70
|
+
? "0 0 30px hsl(185 100% 65% / 0.3), inset 0 0 30px hsl(185 100% 65% / 0.1)"
|
|
71
|
+
: "0 0 20px hsl(220 30% 0% / 0.5), inset 0 0 20px hsl(220 30% 5% / 0.5)",
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{/* 外殻グロー */}
|
|
75
|
+
<div
|
|
76
|
+
className={cn(
|
|
77
|
+
"absolute -inset-3 rounded-full blur-lg transition-opacity duration-500",
|
|
78
|
+
expanded ? "opacity-40" : "opacity-20",
|
|
79
|
+
)}
|
|
80
|
+
style={{
|
|
81
|
+
backgroundColor: expanded
|
|
82
|
+
? "hsl(185 100% 65%)"
|
|
83
|
+
: "hsl(200 30% 40%)",
|
|
84
|
+
borderRadius: "inherit",
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{/* 内部膜 */}
|
|
89
|
+
<div
|
|
90
|
+
className="absolute inset-3 rounded-full border border-dashed opacity-30"
|
|
91
|
+
style={{
|
|
92
|
+
borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
|
|
93
|
+
borderRadius: "inherit",
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* 軌道を描く核 */}
|
|
98
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
99
|
+
{nuclei.map((nucleus, i) => (
|
|
100
|
+
<div
|
|
101
|
+
key={i}
|
|
102
|
+
className="absolute animate-nucleus-orbit"
|
|
103
|
+
style={{
|
|
104
|
+
animationDelay: `${-nucleus.delay}s`,
|
|
105
|
+
animationDuration: `${8 + i * 0.5}s`,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
className="h-2.5 w-2.5 rounded-full animate-vesicle-release"
|
|
110
|
+
style={{
|
|
111
|
+
backgroundColor: expanded
|
|
112
|
+
? `hsl(${185 + i * 15} 80% 60%)`
|
|
113
|
+
: `hsl(${200 + i * 10} 50% 50%)`,
|
|
114
|
+
boxShadow: expanded
|
|
115
|
+
? `0 0 8px hsl(${185 + i * 15} 80% 60% / 0.8)`
|
|
116
|
+
: `0 0 4px hsl(${200 + i * 10} 50% 50% / 0.5)`,
|
|
117
|
+
animationDelay: `${nucleus.delay * 0.3}s`,
|
|
118
|
+
transform: `translateX(${nucleus.orbitRadius}px)`,
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* 中心核 - クラスタサイズ表示 */}
|
|
126
|
+
<div
|
|
127
|
+
className={cn(
|
|
128
|
+
"relative z-10 flex items-center justify-center rounded-full",
|
|
129
|
+
"border-2 backdrop-blur-sm transition-all duration-300",
|
|
130
|
+
)}
|
|
131
|
+
style={{
|
|
132
|
+
width: size * 0.4,
|
|
133
|
+
height: size * 0.4,
|
|
134
|
+
backgroundColor: expanded
|
|
135
|
+
? "hsl(220 30% 8% / 0.9)"
|
|
136
|
+
: "hsl(220 30% 10% / 0.95)",
|
|
137
|
+
borderColor: expanded
|
|
138
|
+
? "hsl(185 100% 65% / 0.8)"
|
|
139
|
+
: "hsl(200 30% 35%)",
|
|
140
|
+
boxShadow: expanded
|
|
141
|
+
? "0 0 20px hsl(185 100% 65% / 0.4), inset 0 0 15px hsl(185 100% 65% / 0.1)"
|
|
142
|
+
: "0 0 10px hsl(220 30% 0% / 0.5)",
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<span
|
|
146
|
+
className={cn(
|
|
147
|
+
"text-sm font-bold transition-colors duration-300",
|
|
148
|
+
expanded ? "text-primary" : "text-muted-foreground",
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
{clusterSize}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* 展開/折りたたみインジケータ */}
|
|
156
|
+
<div
|
|
157
|
+
className={cn(
|
|
158
|
+
"absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full",
|
|
159
|
+
"border-2 text-xs font-bold transition-all duration-300",
|
|
160
|
+
)}
|
|
161
|
+
style={{
|
|
162
|
+
backgroundColor: expanded
|
|
163
|
+
? "hsl(185 100% 65%)"
|
|
164
|
+
: "hsl(220 30% 12%)",
|
|
165
|
+
borderColor: expanded ? "hsl(185 100% 70%)" : "hsl(200 30% 25%)",
|
|
166
|
+
color: expanded ? "hsl(220 30% 5%)" : "hsl(185 100% 65%)",
|
|
167
|
+
boxShadow: expanded
|
|
168
|
+
? "0 0 15px hsl(185 100% 65% / 0.6)"
|
|
169
|
+
: "0 0 8px hsl(220 30% 0% / 0.5)",
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{expanded ? "−" : "+"}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* ホバーツールチップ */}
|
|
176
|
+
<div
|
|
177
|
+
className={cn(
|
|
178
|
+
"absolute -bottom-14 left-1/2 z-50 -translate-x-1/2",
|
|
179
|
+
"rounded-lg border border-border/50 bg-card/95 px-3 py-2 backdrop-blur-md",
|
|
180
|
+
"opacity-0 shadow-xl transition-all duration-300",
|
|
181
|
+
"group-hover:opacity-100 group-hover:-translate-y-1",
|
|
182
|
+
)}
|
|
183
|
+
style={{
|
|
184
|
+
boxShadow:
|
|
185
|
+
"0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px hsl(185 100% 65% / 0.2)",
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<p className="whitespace-nowrap text-xs font-medium text-foreground">
|
|
189
|
+
{clusterSize} branches
|
|
190
|
+
</p>
|
|
191
|
+
<p className="text-[10px] text-muted-foreground">
|
|
192
|
+
Click to {expanded ? "collapse" : "expand"}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* 出力ハンドル */}
|
|
198
|
+
<Handle
|
|
199
|
+
type="source"
|
|
200
|
+
position={Position.Bottom}
|
|
201
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
202
|
+
style={{
|
|
203
|
+
borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
|
|
204
|
+
boxShadow: expanded
|
|
205
|
+
? "0 0 8px hsl(185 100% 65% / 0.4)"
|
|
206
|
+
: "0 0 6px hsl(200 30% 40% / 0.3)",
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</>
|
|
210
|
+
);
|
|
211
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* シナプティックキャンバス
|
|
3
|
+
*
|
|
4
|
+
* React Flowベースのブランチグラフ表示コンテナ
|
|
5
|
+
* パン/ズーム/ミニマップを提供
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
9
|
+
import {
|
|
10
|
+
ReactFlow,
|
|
11
|
+
Background,
|
|
12
|
+
Controls,
|
|
13
|
+
MiniMap,
|
|
14
|
+
useNodesState,
|
|
15
|
+
useEdgesState,
|
|
16
|
+
type NodeMouseHandler,
|
|
17
|
+
BackgroundVariant,
|
|
18
|
+
} from "@xyflow/react";
|
|
19
|
+
import "@xyflow/react/dist/style.css";
|
|
20
|
+
|
|
21
|
+
import type { Branch } from "../../../../../types/api.js";
|
|
22
|
+
import { BranchNode } from "./BranchNode";
|
|
23
|
+
import { ClusterNode } from "./ClusterNode";
|
|
24
|
+
import { SynapticEdge } from "./SynapticEdge";
|
|
25
|
+
import { branchesToGraph, type GraphNode, type GraphEdge } from "./graphUtils";
|
|
26
|
+
|
|
27
|
+
/** カスタムノードタイプ */
|
|
28
|
+
const nodeTypes = {
|
|
29
|
+
branch: BranchNode,
|
|
30
|
+
cluster: ClusterNode,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** カスタムエッジタイプ */
|
|
34
|
+
const edgeTypes = {
|
|
35
|
+
synaptic: SynapticEdge,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
interface SynapticCanvasProps {
|
|
39
|
+
branches: Branch[];
|
|
40
|
+
onNodeClick?: (branch: Branch | null) => void;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function SynapticCanvas({
|
|
45
|
+
branches,
|
|
46
|
+
onNodeClick,
|
|
47
|
+
className,
|
|
48
|
+
}: SynapticCanvasProps) {
|
|
49
|
+
const [nodes, setNodes, onNodesChange] = useNodesState<GraphNode>([]);
|
|
50
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState<GraphEdge>([]);
|
|
51
|
+
const [expandedClusters, setExpandedClusters] = useState<Set<string>>(
|
|
52
|
+
new Set(),
|
|
53
|
+
);
|
|
54
|
+
const [isLayouting, setIsLayouting] = useState(false);
|
|
55
|
+
|
|
56
|
+
// レイアウト計算
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (branches.length === 0) return;
|
|
59
|
+
|
|
60
|
+
setIsLayouting(true);
|
|
61
|
+
|
|
62
|
+
// キャンバスサイズ(仮)
|
|
63
|
+
const width = 800;
|
|
64
|
+
const height = 600;
|
|
65
|
+
|
|
66
|
+
branchesToGraph(branches, expandedClusters, width, height)
|
|
67
|
+
.then(({ nodes: newNodes, edges: newEdges }) => {
|
|
68
|
+
setNodes(newNodes);
|
|
69
|
+
setEdges(newEdges);
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
console.error("Failed to calculate graph layout:", error);
|
|
73
|
+
})
|
|
74
|
+
.finally(() => {
|
|
75
|
+
setIsLayouting(false);
|
|
76
|
+
});
|
|
77
|
+
}, [branches, expandedClusters, setNodes, setEdges]);
|
|
78
|
+
|
|
79
|
+
// ノードクリックハンドラ
|
|
80
|
+
const handleNodeClick: NodeMouseHandler<GraphNode> = useCallback(
|
|
81
|
+
(event, node) => {
|
|
82
|
+
if (node.data.isCluster) {
|
|
83
|
+
// クラスタノード: 展開/折りたたみ
|
|
84
|
+
setExpandedClusters((prev) => {
|
|
85
|
+
const next = new Set(prev);
|
|
86
|
+
if (next.has(node.id)) {
|
|
87
|
+
next.delete(node.id);
|
|
88
|
+
} else {
|
|
89
|
+
next.add(node.id);
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
// ブランチノード: 詳細パネル表示
|
|
95
|
+
onNodeClick?.(node.data.branch ?? null);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[onNodeClick],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ミニマップのノードカラー
|
|
102
|
+
const minimapNodeColor = useCallback((node: GraphNode) => {
|
|
103
|
+
if (node.data.isCluster) {
|
|
104
|
+
return "hsl(var(--muted-foreground))";
|
|
105
|
+
}
|
|
106
|
+
if (node.data.branch?.worktreePath) {
|
|
107
|
+
return "hsl(var(--success))";
|
|
108
|
+
}
|
|
109
|
+
if (node.data.branch?.type === "local") {
|
|
110
|
+
return "hsl(var(--local))";
|
|
111
|
+
}
|
|
112
|
+
return "hsl(var(--remote))";
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={className} style={{ width: "100%", height: "100%" }}>
|
|
117
|
+
<ReactFlow
|
|
118
|
+
nodes={nodes}
|
|
119
|
+
edges={edges}
|
|
120
|
+
onNodesChange={onNodesChange}
|
|
121
|
+
onEdgesChange={onEdgesChange}
|
|
122
|
+
onNodeClick={handleNodeClick}
|
|
123
|
+
nodeTypes={nodeTypes}
|
|
124
|
+
edgeTypes={edgeTypes}
|
|
125
|
+
fitView
|
|
126
|
+
fitViewOptions={{
|
|
127
|
+
padding: 0.2,
|
|
128
|
+
maxZoom: 1.5,
|
|
129
|
+
}}
|
|
130
|
+
minZoom={0.1}
|
|
131
|
+
maxZoom={2}
|
|
132
|
+
attributionPosition="bottom-left"
|
|
133
|
+
proOptions={{ hideAttribution: true }}
|
|
134
|
+
className="bg-gradient-to-br from-background via-background to-muted/20"
|
|
135
|
+
>
|
|
136
|
+
{/* 背景グリッド */}
|
|
137
|
+
<Background
|
|
138
|
+
variant={BackgroundVariant.Dots}
|
|
139
|
+
gap={20}
|
|
140
|
+
size={1}
|
|
141
|
+
color="hsl(var(--muted-foreground) / 0.15)"
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
{/* コントロール */}
|
|
145
|
+
<Controls
|
|
146
|
+
showInteractive={false}
|
|
147
|
+
className="!bg-card !border !border-border !shadow-md"
|
|
148
|
+
/>
|
|
149
|
+
|
|
150
|
+
{/* ミニマップ */}
|
|
151
|
+
<MiniMap
|
|
152
|
+
nodeColor={minimapNodeColor}
|
|
153
|
+
maskColor="hsl(var(--background) / 0.8)"
|
|
154
|
+
className="!bg-card !border !border-border !shadow-md"
|
|
155
|
+
/>
|
|
156
|
+
</ReactFlow>
|
|
157
|
+
|
|
158
|
+
{/* ローディングオーバーレイ */}
|
|
159
|
+
{isLayouting && (
|
|
160
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm">
|
|
161
|
+
<div className="flex items-center gap-2 rounded-lg bg-card px-4 py-2 shadow-lg">
|
|
162
|
+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
163
|
+
<span className="text-sm text-muted-foreground">
|
|
164
|
+
Calculating layout...
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* シナプスエッジコンポーネント
|
|
3
|
+
*
|
|
4
|
+
* 神経細胞の軸索(アクソン)をイメージした脈動するエッジ
|
|
5
|
+
* 神経伝達物質の放出と伝播を表現するアニメーション
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { memo, useMemo } from "react";
|
|
9
|
+
import {
|
|
10
|
+
BaseEdge,
|
|
11
|
+
EdgeLabelRenderer,
|
|
12
|
+
getBezierPath,
|
|
13
|
+
type EdgeProps,
|
|
14
|
+
} from "@xyflow/react";
|
|
15
|
+
import type { GraphEdge } from "./graphUtils";
|
|
16
|
+
|
|
17
|
+
type SynapticEdgeProps = EdgeProps<GraphEdge>;
|
|
18
|
+
|
|
19
|
+
export const SynapticEdge = memo(function SynapticEdge({
|
|
20
|
+
id,
|
|
21
|
+
sourceX,
|
|
22
|
+
sourceY,
|
|
23
|
+
targetX,
|
|
24
|
+
targetY,
|
|
25
|
+
sourcePosition,
|
|
26
|
+
targetPosition,
|
|
27
|
+
style = {},
|
|
28
|
+
markerEnd,
|
|
29
|
+
}: SynapticEdgeProps) {
|
|
30
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
31
|
+
sourceX,
|
|
32
|
+
sourceY,
|
|
33
|
+
sourcePosition,
|
|
34
|
+
targetX,
|
|
35
|
+
targetY,
|
|
36
|
+
targetPosition,
|
|
37
|
+
curvature: 0.25,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// エッジの長さに基づいてパーティクル数を調整
|
|
41
|
+
const edgeLength = useMemo(() => {
|
|
42
|
+
const dx = targetX - sourceX;
|
|
43
|
+
const dy = targetY - sourceY;
|
|
44
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
45
|
+
}, [sourceX, sourceY, targetX, targetY]);
|
|
46
|
+
|
|
47
|
+
const particleCount = Math.max(2, Math.min(4, Math.floor(edgeLength / 100)));
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
{/* 外側グロー - 深い影 */}
|
|
52
|
+
<BaseEdge
|
|
53
|
+
id={`${id}-outer-glow`}
|
|
54
|
+
path={edgePath}
|
|
55
|
+
style={{
|
|
56
|
+
...style,
|
|
57
|
+
strokeWidth: 12,
|
|
58
|
+
stroke: "hsl(185 100% 65% / 0.08)",
|
|
59
|
+
filter: "blur(8px)",
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
{/* 中間グロー */}
|
|
64
|
+
<BaseEdge
|
|
65
|
+
id={`${id}-mid-glow`}
|
|
66
|
+
path={edgePath}
|
|
67
|
+
style={{
|
|
68
|
+
...style,
|
|
69
|
+
strokeWidth: 6,
|
|
70
|
+
stroke: "hsl(185 100% 65% / 0.15)",
|
|
71
|
+
filter: "blur(4px)",
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
{/* メインエッジ - 軸索 */}
|
|
76
|
+
<BaseEdge
|
|
77
|
+
id={id}
|
|
78
|
+
path={edgePath}
|
|
79
|
+
{...(markerEnd ? { markerEnd } : {})}
|
|
80
|
+
style={{
|
|
81
|
+
...style,
|
|
82
|
+
strokeWidth: 2,
|
|
83
|
+
stroke: "hsl(185 100% 65% / 0.5)",
|
|
84
|
+
strokeLinecap: "round",
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{/* 内側の明るいコア */}
|
|
89
|
+
<BaseEdge
|
|
90
|
+
id={`${id}-core`}
|
|
91
|
+
path={edgePath}
|
|
92
|
+
style={{
|
|
93
|
+
...style,
|
|
94
|
+
strokeWidth: 1,
|
|
95
|
+
stroke: "hsl(185 100% 75% / 0.6)",
|
|
96
|
+
strokeLinecap: "round",
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
{/* SVGアニメーション定義 */}
|
|
101
|
+
<EdgeLabelRenderer>
|
|
102
|
+
<svg
|
|
103
|
+
style={{
|
|
104
|
+
position: "absolute",
|
|
105
|
+
left: 0,
|
|
106
|
+
top: 0,
|
|
107
|
+
width: "100%",
|
|
108
|
+
height: "100%",
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
overflow: "visible",
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<defs>
|
|
114
|
+
{/* 神経伝達物質パルスグラデーション */}
|
|
115
|
+
<linearGradient
|
|
116
|
+
id={`vesicle-pulse-${id}`}
|
|
117
|
+
x1="0%"
|
|
118
|
+
y1="0%"
|
|
119
|
+
x2="100%"
|
|
120
|
+
y2="0%"
|
|
121
|
+
>
|
|
122
|
+
<stop offset="0%" stopColor="transparent">
|
|
123
|
+
<animate
|
|
124
|
+
attributeName="offset"
|
|
125
|
+
values="-0.3;1"
|
|
126
|
+
dur="2.5s"
|
|
127
|
+
repeatCount="indefinite"
|
|
128
|
+
/>
|
|
129
|
+
</stop>
|
|
130
|
+
<stop offset="5%" stopColor="hsl(185 100% 70% / 0.3)">
|
|
131
|
+
<animate
|
|
132
|
+
attributeName="offset"
|
|
133
|
+
values="-0.25;1.05"
|
|
134
|
+
dur="2.5s"
|
|
135
|
+
repeatCount="indefinite"
|
|
136
|
+
/>
|
|
137
|
+
</stop>
|
|
138
|
+
<stop offset="10%" stopColor="hsl(185 100% 80%)">
|
|
139
|
+
<animate
|
|
140
|
+
attributeName="offset"
|
|
141
|
+
values="-0.2;1.1"
|
|
142
|
+
dur="2.5s"
|
|
143
|
+
repeatCount="indefinite"
|
|
144
|
+
/>
|
|
145
|
+
</stop>
|
|
146
|
+
<stop offset="15%" stopColor="hsl(185 100% 70% / 0.3)">
|
|
147
|
+
<animate
|
|
148
|
+
attributeName="offset"
|
|
149
|
+
values="-0.15;1.15"
|
|
150
|
+
dur="2.5s"
|
|
151
|
+
repeatCount="indefinite"
|
|
152
|
+
/>
|
|
153
|
+
</stop>
|
|
154
|
+
<stop offset="20%" stopColor="transparent">
|
|
155
|
+
<animate
|
|
156
|
+
attributeName="offset"
|
|
157
|
+
values="-0.1;1.2"
|
|
158
|
+
dur="2.5s"
|
|
159
|
+
repeatCount="indefinite"
|
|
160
|
+
/>
|
|
161
|
+
</stop>
|
|
162
|
+
</linearGradient>
|
|
163
|
+
|
|
164
|
+
{/* 二次パルス(シナプス間隙) */}
|
|
165
|
+
<linearGradient
|
|
166
|
+
id={`synapse-pulse-${id}`}
|
|
167
|
+
x1="0%"
|
|
168
|
+
y1="0%"
|
|
169
|
+
x2="100%"
|
|
170
|
+
y2="0%"
|
|
171
|
+
>
|
|
172
|
+
<stop offset="0%" stopColor="transparent">
|
|
173
|
+
<animate
|
|
174
|
+
attributeName="offset"
|
|
175
|
+
values="-0.4;1"
|
|
176
|
+
dur="3s"
|
|
177
|
+
begin="0.8s"
|
|
178
|
+
repeatCount="indefinite"
|
|
179
|
+
/>
|
|
180
|
+
</stop>
|
|
181
|
+
<stop offset="8%" stopColor="hsl(310 85% 65% / 0.6)">
|
|
182
|
+
<animate
|
|
183
|
+
attributeName="offset"
|
|
184
|
+
values="-0.32;1.08"
|
|
185
|
+
dur="3s"
|
|
186
|
+
begin="0.8s"
|
|
187
|
+
repeatCount="indefinite"
|
|
188
|
+
/>
|
|
189
|
+
</stop>
|
|
190
|
+
<stop offset="16%" stopColor="transparent">
|
|
191
|
+
<animate
|
|
192
|
+
attributeName="offset"
|
|
193
|
+
values="-0.24;1.16"
|
|
194
|
+
dur="3s"
|
|
195
|
+
begin="0.8s"
|
|
196
|
+
repeatCount="indefinite"
|
|
197
|
+
/>
|
|
198
|
+
</stop>
|
|
199
|
+
</linearGradient>
|
|
200
|
+
|
|
201
|
+
{/* グロー効果フィルター */}
|
|
202
|
+
<filter
|
|
203
|
+
id={`glow-${id}`}
|
|
204
|
+
x="-50%"
|
|
205
|
+
y="-50%"
|
|
206
|
+
width="200%"
|
|
207
|
+
height="200%"
|
|
208
|
+
>
|
|
209
|
+
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
|
|
210
|
+
<feMerge>
|
|
211
|
+
<feMergeNode in="coloredBlur" />
|
|
212
|
+
<feMergeNode in="SourceGraphic" />
|
|
213
|
+
</feMerge>
|
|
214
|
+
</filter>
|
|
215
|
+
</defs>
|
|
216
|
+
|
|
217
|
+
{/* メインパルス */}
|
|
218
|
+
<path
|
|
219
|
+
d={edgePath}
|
|
220
|
+
fill="none"
|
|
221
|
+
stroke={`url(#vesicle-pulse-${id})`}
|
|
222
|
+
strokeWidth={4}
|
|
223
|
+
strokeLinecap="round"
|
|
224
|
+
filter={`url(#glow-${id})`}
|
|
225
|
+
style={{ mixBlendMode: "screen" }}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
{/* セカンダリパルス(マゼンタ) */}
|
|
229
|
+
<path
|
|
230
|
+
d={edgePath}
|
|
231
|
+
fill="none"
|
|
232
|
+
stroke={`url(#synapse-pulse-${id})`}
|
|
233
|
+
strokeWidth={3}
|
|
234
|
+
strokeLinecap="round"
|
|
235
|
+
style={{ mixBlendMode: "screen" }}
|
|
236
|
+
/>
|
|
237
|
+
</svg>
|
|
238
|
+
</EdgeLabelRenderer>
|
|
239
|
+
|
|
240
|
+
{/* 流れるパーティクル(神経伝達物質) - SVG animateMotion使用 */}
|
|
241
|
+
<EdgeLabelRenderer>
|
|
242
|
+
<svg
|
|
243
|
+
style={{
|
|
244
|
+
position: "absolute",
|
|
245
|
+
left: 0,
|
|
246
|
+
top: 0,
|
|
247
|
+
width: "100%",
|
|
248
|
+
height: "100%",
|
|
249
|
+
pointerEvents: "none",
|
|
250
|
+
overflow: "visible",
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
{Array.from({ length: particleCount }).map((_, i) => (
|
|
254
|
+
<circle
|
|
255
|
+
key={i}
|
|
256
|
+
r={3 - i * 0.3}
|
|
257
|
+
fill="hsl(185 100% 80%)"
|
|
258
|
+
opacity={0}
|
|
259
|
+
>
|
|
260
|
+
<animateMotion
|
|
261
|
+
dur={`${2 + i * 0.3}s`}
|
|
262
|
+
repeatCount="indefinite"
|
|
263
|
+
path={edgePath}
|
|
264
|
+
begin={`${i * 0.6}s`}
|
|
265
|
+
/>
|
|
266
|
+
<animate
|
|
267
|
+
attributeName="opacity"
|
|
268
|
+
values="0;1;1;0"
|
|
269
|
+
keyTimes="0;0.1;0.9;1"
|
|
270
|
+
dur={`${2 + i * 0.3}s`}
|
|
271
|
+
repeatCount="indefinite"
|
|
272
|
+
begin={`${i * 0.6}s`}
|
|
273
|
+
/>
|
|
274
|
+
</circle>
|
|
275
|
+
))}
|
|
276
|
+
</svg>
|
|
277
|
+
</EdgeLabelRenderer>
|
|
278
|
+
|
|
279
|
+
{/* シナプス接合点グロー */}
|
|
280
|
+
<EdgeLabelRenderer>
|
|
281
|
+
<div
|
|
282
|
+
className="absolute"
|
|
283
|
+
style={{
|
|
284
|
+
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
{/* 外側の拡散グロー */}
|
|
288
|
+
<div
|
|
289
|
+
className="absolute -inset-3 rounded-full opacity-30"
|
|
290
|
+
style={{
|
|
291
|
+
background:
|
|
292
|
+
"radial-gradient(circle, hsl(185 100% 65%) 0%, transparent 70%)",
|
|
293
|
+
animation: "synapse-spark 3s ease-in-out infinite",
|
|
294
|
+
}}
|
|
295
|
+
/>
|
|
296
|
+
{/* 中心核 */}
|
|
297
|
+
<div
|
|
298
|
+
className="h-2 w-2 rounded-full"
|
|
299
|
+
style={{
|
|
300
|
+
background:
|
|
301
|
+
"radial-gradient(circle at 30% 30%, hsl(185 100% 80%), hsl(185 100% 60%))",
|
|
302
|
+
boxShadow:
|
|
303
|
+
"0 0 10px hsl(185 100% 65% / 0.8), 0 0 20px hsl(185 100% 65% / 0.4)",
|
|
304
|
+
animation: "vesicle-release 2s ease-in-out infinite",
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
</EdgeLabelRenderer>
|
|
309
|
+
</>
|
|
310
|
+
);
|
|
311
|
+
});
|