@pennyfarthing/cyclist 10.0.3 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/agent-load.d.ts +3 -0
- package/dist/api/agent-load.d.ts.map +1 -0
- package/dist/api/agent-load.js +124 -0
- package/dist/api/agent-load.js.map +1 -0
- package/dist/api/code-markers.d.ts +9 -0
- package/dist/api/code-markers.d.ts.map +1 -0
- package/dist/api/code-markers.js +62 -0
- package/dist/api/code-markers.js.map +1 -0
- package/dist/api/complexity.d.ts +3 -0
- package/dist/api/complexity.d.ts.map +1 -0
- package/dist/api/complexity.js +47 -0
- package/dist/api/complexity.js.map +1 -0
- package/dist/api/dead-code.d.ts +3 -0
- package/dist/api/dead-code.d.ts.map +1 -0
- package/dist/api/dead-code.js +70 -0
- package/dist/api/dead-code.js.map +1 -0
- package/dist/api/dependencies.d.ts +3 -0
- package/dist/api/dependencies.d.ts.map +1 -0
- package/dist/api/dependencies.js +43 -0
- package/dist/api/dependencies.js.map +1 -0
- package/dist/api/git.d.ts +3 -2
- package/dist/api/git.d.ts.map +1 -1
- package/dist/api/git.js +11 -6
- package/dist/api/git.js.map +1 -1
- package/dist/api/health-score.d.ts +3 -0
- package/dist/api/health-score.d.ts.map +1 -0
- package/dist/api/health-score.js +47 -0
- package/dist/api/health-score.js.map +1 -0
- package/dist/api/hotspots.d.ts.map +1 -1
- package/dist/api/hotspots.js +9 -1
- package/dist/api/hotspots.js.map +1 -1
- package/dist/api/index.d.ts +7 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +12 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/persona.d.ts +2 -0
- package/dist/api/persona.d.ts.map +1 -1
- package/dist/api/persona.js +19 -1
- package/dist/api/persona.js.map +1 -1
- package/dist/api/settings.js +1 -1
- package/dist/api/settings.js.map +1 -1
- package/dist/claude-service.d.ts +8 -2
- package/dist/claude-service.d.ts.map +1 -1
- package/dist/claude-service.js +21 -2
- package/dist/claude-service.js.map +1 -1
- package/dist/git-diff.d.ts.map +1 -1
- package/dist/git-diff.js +6 -5
- package/dist/git-diff.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -2
- package/dist/main.js.map +1 -1
- package/dist/plugin-loader.d.ts +49 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +92 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/preload.js +12 -1
- package/dist/preload.js.map +1 -1
- package/dist/prime.d.ts +3 -2
- package/dist/prime.d.ts.map +1 -1
- package/dist/prime.js +25 -8
- package/dist/prime.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +50 -39
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +19 -16
- package/dist/server.js.map +1 -1
- package/dist/sprint-data.d.ts +6 -0
- package/dist/sprint-data.d.ts.map +1 -1
- package/dist/sprint-data.js +118 -67
- package/dist/sprint-data.js.map +1 -1
- package/dist/story-parser.js +1 -1
- package/dist/story-parser.js.map +1 -1
- package/dist/theme-metadata.js +2 -2
- package/dist/theme-metadata.js.map +1 -1
- package/dist/websocket.d.ts +0 -6
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +36 -40
- package/dist/websocket.js.map +1 -1
- package/package.json +2 -1
- package/portraits/fifth-element/large/cornelius-54343.png +0 -0
- package/portraits/fifth-element/large/diva-53453.png +0 -0
- package/portraits/fifth-element/large/korben-34232.png +0 -0
- package/portraits/fifth-element/large/leeloo-54333.png +0 -0
- package/portraits/fifth-element/large/lindberg-34432.png +0 -0
- package/portraits/fifth-element/large/mondoshawan-55131.png +0 -0
- package/portraits/fifth-element/large/munro-25321.png +0 -0
- package/portraits/fifth-element/large/pacoli-45232.png +0 -0
- package/portraits/fifth-element/large/ruby-53544.png +0 -0
- package/portraits/fifth-element/large/zorg-45312.png +0 -0
- package/portraits/fifth-element/medium/cornelius-54343.png +0 -0
- package/portraits/fifth-element/medium/diva-53453.png +0 -0
- package/portraits/fifth-element/medium/korben-34232.png +0 -0
- package/portraits/fifth-element/medium/leeloo-54333.png +0 -0
- package/portraits/fifth-element/medium/lindberg-34432.png +0 -0
- package/portraits/fifth-element/medium/mondoshawan-55131.png +0 -0
- package/portraits/fifth-element/medium/munro-25321.png +0 -0
- package/portraits/fifth-element/medium/pacoli-45232.png +0 -0
- package/portraits/fifth-element/medium/ruby-53544.png +0 -0
- package/portraits/fifth-element/medium/zorg-45312.png +0 -0
- package/src/public/App.tsx +0 -2
- package/src/public/components/AgentLoadDialog.tsx +202 -0
- package/src/public/components/AgentPopup.tsx +3 -5
- package/src/public/components/ContextSparkline.tsx +56 -0
- package/src/public/components/ControlBar.tsx +140 -6
- package/src/public/components/DeadCodeDialog.tsx +169 -0
- package/src/public/components/DockviewWorkspace.tsx +0 -3
- package/src/public/components/FullFileTree.tsx +18 -4
- package/src/public/components/HealthGauge.tsx +181 -0
- package/src/public/components/MessageView.tsx +23 -6
- package/src/public/components/PersonaHeader.tsx +46 -3
- package/src/public/components/TandemPortrait.tsx +71 -0
- package/src/public/components/ToolCallBlock.tsx +21 -6
- package/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
- package/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
- package/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
- package/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
- package/src/public/components/dialogs/ToolDialog.tsx +43 -0
- package/src/public/components/panels/ACPanel.tsx +1 -1
- package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +15 -30
- package/src/public/components/panels/DebugPanel.tsx +79 -3
- package/src/public/components/panels/GitPanel.tsx +25 -30
- package/src/public/components/panels/MessagePanel.tsx +44 -2
- package/src/public/components/panels/SettingsPanel.tsx +4 -4
- package/src/public/components/panels/SprintPanel.tsx +247 -123
- package/src/public/components/panels/index.ts +0 -1
- package/src/public/components/ui/dialog.tsx +3 -3
- package/src/public/css/theme-system.css +98 -11
- package/src/public/hooks/index.ts +4 -0
- package/src/public/hooks/useAgentLoad.ts +105 -0
- package/src/public/hooks/useCodeMarkers.ts +101 -0
- package/src/public/hooks/useColorScheme.ts +25 -10
- package/src/public/hooks/useComplexity.ts +80 -0
- package/src/public/hooks/useDeadCode.ts +99 -0
- package/src/public/hooks/useDependencies.ts +82 -0
- package/src/public/hooks/useHealthScore.ts +69 -0
- package/src/public/hooks/useHotspots.ts +11 -1
- package/src/public/hooks/usePersona.ts +26 -3
- package/src/public/hooks/useSprint.ts +7 -1
- package/src/public/styles/tailwind.css +389 -83
- package/src/public/utils/messageFilters.ts +77 -6
- package/src/public/utils/slash-commands.ts +3 -35
- package/dist/hooks/cyclist-pretooluse-hook.d.ts +0 -60
- package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/cyclist-pretooluse-hook.js +0 -57
- package/dist/hooks/cyclist-pretooluse-hook.js.map +0 -1
- package/dist/hooks/pretooluse-hook.d.ts +0 -89
- package/dist/hooks/pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/pretooluse-hook.js +0 -235
- package/dist/hooks/pretooluse-hook.js.map +0 -1
- package/dist/notification-sound.d.ts +0 -59
- package/dist/notification-sound.d.ts.map +0 -1
- package/dist/notification-sound.js +0 -219
- package/dist/notification-sound.js.map +0 -1
- package/src/public/types/electron.d.ts +0 -18
|
@@ -8,10 +8,19 @@
|
|
|
8
8
|
* Note: Tool call display moved to AuditLogPanel for comprehensive view.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useState, useEffect } from 'react';
|
|
11
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
12
12
|
import { Button } from '@/components/ui/button';
|
|
13
13
|
import { Badge } from '@/components/ui/badge';
|
|
14
14
|
import { Separator } from '@/components/ui/separator';
|
|
15
|
+
import { HotspotsDialog } from '../dialogs/HotspotsDialog';
|
|
16
|
+
import { CodeMarkersDialog } from '../dialogs/CodeMarkersDialog';
|
|
17
|
+
import { ComplexityDialog } from '../dialogs/ComplexityDialog';
|
|
18
|
+
import { DependenciesDialog } from '../dialogs/DependenciesDialog';
|
|
19
|
+
import { AgentLoadDialog } from '../AgentLoadDialog';
|
|
20
|
+
import { DeadCodeDialog } from '../DeadCodeDialog';
|
|
21
|
+
import { HealthGauge } from '../HealthGauge';
|
|
22
|
+
import { ContextSparkline, SparklinePoint } from '../ContextSparkline';
|
|
23
|
+
import { useHealthScore } from '../../hooks/useHealthScore';
|
|
15
24
|
|
|
16
25
|
/** Context tier type */
|
|
17
26
|
type ContextTier = 'FULL' | 'REFRESH' | 'HANDOFF' | 'MINIMAL';
|
|
@@ -94,6 +103,49 @@ export function DebugPanel(): React.ReactElement {
|
|
|
94
103
|
const [context, setContext] = useState<ContextData | null>(null);
|
|
95
104
|
const [tokenStats, setTokenStats] = useState<Record<string, unknown> | null>(null);
|
|
96
105
|
const [breakdownExpanded, setBreakdownExpanded] = useState(false);
|
|
106
|
+
const [hotspotsOpen, setHotspotsOpen] = useState(false);
|
|
107
|
+
const [codeMarkersOpen, setCodeMarkersOpen] = useState(false);
|
|
108
|
+
const [complexityOpen, setComplexityOpen] = useState(false);
|
|
109
|
+
const [dependenciesOpen, setDependenciesOpen] = useState(false);
|
|
110
|
+
const [agentLoadOpen, setAgentLoadOpen] = useState(false);
|
|
111
|
+
const [deadCodeOpen, setDeadCodeOpen] = useState(false);
|
|
112
|
+
const healthScore = useHealthScore();
|
|
113
|
+
const sparklineRef = useRef<SparklinePoint[]>([]);
|
|
114
|
+
const [sparklineVersion, setSparklineVersion] = useState(0);
|
|
115
|
+
|
|
116
|
+
const pushSparklinePoint = useCallback((percent: number, tokens: number) => {
|
|
117
|
+
const buf = sparklineRef.current;
|
|
118
|
+
buf.push({ percent, tokens, timestamp: Date.now() });
|
|
119
|
+
if (buf.length > 50) buf.shift();
|
|
120
|
+
setSparklineVersion(v => v + 1);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const handleDimensionClick = (dimensionName: string) => {
|
|
124
|
+
switch (dimensionName) {
|
|
125
|
+
case 'churn':
|
|
126
|
+
setHotspotsOpen(true);
|
|
127
|
+
break;
|
|
128
|
+
case 'test_gaps':
|
|
129
|
+
// TODO: TestGapsDialog — for now no drill-down
|
|
130
|
+
break;
|
|
131
|
+
case 'todo_density':
|
|
132
|
+
case 'deprecation_debt':
|
|
133
|
+
setCodeMarkersOpen(true);
|
|
134
|
+
break;
|
|
135
|
+
case 'complexity':
|
|
136
|
+
setComplexityOpen(true);
|
|
137
|
+
break;
|
|
138
|
+
case 'dead_code':
|
|
139
|
+
setDeadCodeOpen(true);
|
|
140
|
+
break;
|
|
141
|
+
case 'dependency_freshness':
|
|
142
|
+
setDependenciesOpen(true);
|
|
143
|
+
break;
|
|
144
|
+
case 'agent_context_efficiency':
|
|
145
|
+
setAgentLoadOpen(true);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
97
149
|
|
|
98
150
|
useEffect(() => {
|
|
99
151
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
@@ -104,7 +156,11 @@ export function DebugPanel(): React.ReactElement {
|
|
|
104
156
|
try {
|
|
105
157
|
const data = JSON.parse(event.data);
|
|
106
158
|
if (data.type === 'init' || data.type === 'update') {
|
|
107
|
-
|
|
159
|
+
const ctx = data.context as ContextData;
|
|
160
|
+
setContext(ctx);
|
|
161
|
+
if (ctx.percent != null) {
|
|
162
|
+
pushSparklinePoint(ctx.percent, ctx.tokens ?? 0);
|
|
163
|
+
}
|
|
108
164
|
}
|
|
109
165
|
} catch {
|
|
110
166
|
// Ignore parse errors
|
|
@@ -126,7 +182,7 @@ export function DebugPanel(): React.ReactElement {
|
|
|
126
182
|
contextWs.close();
|
|
127
183
|
tokenWs.close();
|
|
128
184
|
};
|
|
129
|
-
}, []);
|
|
185
|
+
}, [pushSparklinePoint]);
|
|
130
186
|
|
|
131
187
|
// Compute tier-specific CSS class
|
|
132
188
|
const tierClass = context?.tier ? `tier-${context.tier.toLowerCase()}` : '';
|
|
@@ -134,6 +190,19 @@ export function DebugPanel(): React.ReactElement {
|
|
|
134
190
|
|
|
135
191
|
return (
|
|
136
192
|
<div className="debug-panel" data-testid="debug-panel">
|
|
193
|
+
<HealthGauge
|
|
194
|
+
score={healthScore.data?.composite_score ?? null}
|
|
195
|
+
dimensions={healthScore.data?.dimensions ?? []}
|
|
196
|
+
totalDimensions={8}
|
|
197
|
+
onDimensionClick={handleDimensionClick}
|
|
198
|
+
isLoading={healthScore.isLoading}
|
|
199
|
+
lastFetchedAt={healthScore.lastFetchedAt}
|
|
200
|
+
onRefresh={healthScore.refresh}
|
|
201
|
+
error={healthScore.error}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
<Separator className="my-3" />
|
|
205
|
+
|
|
137
206
|
<h4>Context Usage</h4>
|
|
138
207
|
{context ? (
|
|
139
208
|
<div className="context-info">
|
|
@@ -200,6 +269,7 @@ export function DebugPanel(): React.ReactElement {
|
|
|
200
269
|
style={{ width: `${context.percent || 0}%` }}
|
|
201
270
|
/>
|
|
202
271
|
</div>
|
|
272
|
+
<ContextSparkline history={sparklineRef.current} key={sparklineVersion} />
|
|
203
273
|
<span className="context-text">
|
|
204
274
|
{(context.tokens ?? 0).toLocaleString()} / {context.baseline != null && context.available != null
|
|
205
275
|
? (context.baseline + context.available).toLocaleString()
|
|
@@ -261,6 +331,12 @@ export function DebugPanel(): React.ReactElement {
|
|
|
261
331
|
<div className="placeholder">No token stats</div>
|
|
262
332
|
)}
|
|
263
333
|
|
|
334
|
+
<HotspotsDialog open={hotspotsOpen} onOpenChange={setHotspotsOpen} />
|
|
335
|
+
<CodeMarkersDialog open={codeMarkersOpen} onOpenChange={setCodeMarkersOpen} />
|
|
336
|
+
<ComplexityDialog open={complexityOpen} onOpenChange={setComplexityOpen} />
|
|
337
|
+
<DependenciesDialog open={dependenciesOpen} onOpenChange={setDependenciesOpen} />
|
|
338
|
+
<AgentLoadDialog isOpen={agentLoadOpen} onClose={() => setAgentLoadOpen(false)} />
|
|
339
|
+
<DeadCodeDialog isOpen={deadCodeOpen} onClose={() => setDeadCodeOpen(false)} />
|
|
264
340
|
</div>
|
|
265
341
|
);
|
|
266
342
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useState } from 'react';
|
|
9
|
+
import { RefreshCw } from 'lucide-react';
|
|
9
10
|
import { Button } from '@/components/ui/button';
|
|
10
11
|
import { Badge } from '@/components/ui/badge';
|
|
11
12
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
@@ -61,14 +62,12 @@ function FileList({ files }: FileListProps): React.ReactElement {
|
|
|
61
62
|
|
|
62
63
|
interface RepoStatusProps {
|
|
63
64
|
repo: RepoStatusData;
|
|
64
|
-
onPullDevelop?: (repoName: string, repoPath: string) => void;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function RepoStatus({ repo
|
|
67
|
+
function RepoStatus({ repo }: RepoStatusProps): React.ReactElement {
|
|
68
68
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
69
|
-
const { name,
|
|
69
|
+
const { name, branch, ahead, behind, developBehind, staged, modified, untracked, isDirty, files } = repo;
|
|
70
70
|
const hasFiles = files.length > 0;
|
|
71
|
-
const hasDevelopUpdates = developBehind !== undefined && developBehind > 0;
|
|
72
71
|
|
|
73
72
|
return (
|
|
74
73
|
<TooltipProvider delayDuration={300}>
|
|
@@ -90,14 +89,14 @@ function RepoStatus({ repo, onPullDevelop }: RepoStatusProps): React.ReactElemen
|
|
|
90
89
|
<span className="branch-name">{branch}</span>
|
|
91
90
|
</div>
|
|
92
91
|
|
|
93
|
-
{(ahead !== undefined && ahead > 0) || (behind !== undefined && behind > 0)
|
|
92
|
+
{((ahead !== undefined && ahead > 0) || (behind !== undefined && behind > 0) || (developBehind !== undefined && developBehind > 0)) && (
|
|
94
93
|
<div className="sync-status">
|
|
95
94
|
{ahead !== undefined && ahead > 0 && (
|
|
96
95
|
<Tooltip>
|
|
97
96
|
<TooltipTrigger asChild>
|
|
98
97
|
<span className="ahead">↑{ahead}</span>
|
|
99
98
|
</TooltipTrigger>
|
|
100
|
-
<TooltipContent>Commits ahead</TooltipContent>
|
|
99
|
+
<TooltipContent>Commits ahead of remote</TooltipContent>
|
|
101
100
|
</Tooltip>
|
|
102
101
|
)}
|
|
103
102
|
{behind !== undefined && behind > 0 && (
|
|
@@ -105,29 +104,15 @@ function RepoStatus({ repo, onPullDevelop }: RepoStatusProps): React.ReactElemen
|
|
|
105
104
|
<TooltipTrigger asChild>
|
|
106
105
|
<span className="behind">↓{behind}</span>
|
|
107
106
|
</TooltipTrigger>
|
|
108
|
-
<TooltipContent>Commits behind</TooltipContent>
|
|
107
|
+
<TooltipContent>Commits behind remote</TooltipContent>
|
|
109
108
|
</Tooltip>
|
|
110
109
|
)}
|
|
111
|
-
|
|
112
|
-
) : null}
|
|
113
|
-
|
|
114
|
-
{hasDevelopUpdates && (
|
|
115
|
-
<div className="develop-behind-warning">
|
|
116
|
-
<span className="warning-icon">⚠️</span>
|
|
117
|
-
<span className="warning-text">develop is {developBehind} commit{developBehind > 1 ? 's' : ''} ahead</span>
|
|
118
|
-
{onPullDevelop && (
|
|
110
|
+
{developBehind !== undefined && developBehind > 0 && (
|
|
119
111
|
<Tooltip>
|
|
120
112
|
<TooltipTrigger asChild>
|
|
121
|
-
<
|
|
122
|
-
variant="outline"
|
|
123
|
-
size="sm"
|
|
124
|
-
className="pull-develop-btn"
|
|
125
|
-
onClick={() => onPullDevelop(name, path)}
|
|
126
|
-
>
|
|
127
|
-
Pull
|
|
128
|
-
</Button>
|
|
113
|
+
<span className="develop-behind">⟳{developBehind}</span>
|
|
129
114
|
</TooltipTrigger>
|
|
130
|
-
<TooltipContent>
|
|
115
|
+
<TooltipContent>develop is {developBehind} commit{developBehind > 1 ? 's' : ''} ahead</TooltipContent>
|
|
131
116
|
</Tooltip>
|
|
132
117
|
)}
|
|
133
118
|
</div>
|
|
@@ -169,11 +154,8 @@ export function GitPanel(): React.ReactElement {
|
|
|
169
154
|
const { repos, isLoading, error } = useGitStatus();
|
|
170
155
|
const { send } = useClaudeContext();
|
|
171
156
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
? `pull develop`
|
|
175
|
-
: `cd ${repoPath} && git pull origin develop`;
|
|
176
|
-
send(prompt);
|
|
157
|
+
const handleSyncAll = () => {
|
|
158
|
+
send('Sync all repos');
|
|
177
159
|
};
|
|
178
160
|
|
|
179
161
|
if (isLoading) {
|
|
@@ -211,8 +193,21 @@ export function GitPanel(): React.ReactElement {
|
|
|
211
193
|
|
|
212
194
|
return (
|
|
213
195
|
<div className="git-panel stacked" data-testid="git-panel">
|
|
196
|
+
<div className="git-panel-actions">
|
|
197
|
+
<TooltipProvider delayDuration={300}>
|
|
198
|
+
<Tooltip>
|
|
199
|
+
<TooltipTrigger asChild>
|
|
200
|
+
<button className="sync-all-btn" onClick={handleSyncAll} aria-label="Sync all repos">
|
|
201
|
+
<RefreshCw size={14} />
|
|
202
|
+
<span>Sync all repos</span>
|
|
203
|
+
</button>
|
|
204
|
+
</TooltipTrigger>
|
|
205
|
+
<TooltipContent>Pull latest changes for all repos</TooltipContent>
|
|
206
|
+
</Tooltip>
|
|
207
|
+
</TooltipProvider>
|
|
208
|
+
</div>
|
|
214
209
|
{repos.map(repo => (
|
|
215
|
-
<RepoStatus key={repo.name} repo={repo}
|
|
210
|
+
<RepoStatus key={repo.name} repo={repo} />
|
|
216
211
|
))}
|
|
217
212
|
</div>
|
|
218
213
|
);
|
|
@@ -248,6 +248,7 @@ export function MessagePanel(): React.ReactElement {
|
|
|
248
248
|
handleBellModeChange,
|
|
249
249
|
handleRelayModeChange,
|
|
250
250
|
handleTirePump,
|
|
251
|
+
handleAgentSwitch,
|
|
251
252
|
} = useControlBar();
|
|
252
253
|
|
|
253
254
|
// Claude context for WebSocket communication
|
|
@@ -415,13 +416,53 @@ export function MessagePanel(): React.ReactElement {
|
|
|
415
416
|
handleStop();
|
|
416
417
|
}, [pauseQueue, handleStop]);
|
|
417
418
|
|
|
419
|
+
// Resizable editor panel
|
|
420
|
+
const [editorHeight, setEditorHeight] = useState<number | null>(null);
|
|
421
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
422
|
+
const isDragging = useRef(false);
|
|
423
|
+
|
|
424
|
+
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
425
|
+
e.preventDefault();
|
|
426
|
+
isDragging.current = true;
|
|
427
|
+
const startY = e.clientY;
|
|
428
|
+
const panelEl = panelRef.current;
|
|
429
|
+
if (!panelEl) return;
|
|
430
|
+
const panelRect = panelEl.getBoundingClientRect();
|
|
431
|
+
const startEditorHeight = editorHeight ?? panelEl.querySelector('.message-panel-editor')?.getBoundingClientRect().height ?? 150;
|
|
432
|
+
|
|
433
|
+
const onMouseMove = (ev: MouseEvent) => {
|
|
434
|
+
if (!isDragging.current) return;
|
|
435
|
+
const delta = startY - ev.clientY;
|
|
436
|
+
const newHeight = Math.max(80, Math.min(startEditorHeight + delta, panelRect.height * 0.7));
|
|
437
|
+
setEditorHeight(newHeight);
|
|
438
|
+
};
|
|
439
|
+
const onMouseUp = () => {
|
|
440
|
+
isDragging.current = false;
|
|
441
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
442
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
443
|
+
document.body.style.cursor = '';
|
|
444
|
+
document.body.style.userSelect = '';
|
|
445
|
+
};
|
|
446
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
447
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
448
|
+
document.body.style.cursor = 'row-resize';
|
|
449
|
+
document.body.style.userSelect = 'none';
|
|
450
|
+
}, [editorHeight]);
|
|
451
|
+
|
|
418
452
|
return (
|
|
419
|
-
<div className="message-panel" data-testid="message-panel">
|
|
453
|
+
<div className="message-panel" data-testid="message-panel" ref={panelRef}>
|
|
420
454
|
<PersonaHeader />
|
|
421
455
|
<div className="message-panel-content">
|
|
422
456
|
<MessageView messages={messages} />
|
|
423
457
|
</div>
|
|
424
|
-
<div
|
|
458
|
+
<div
|
|
459
|
+
className="message-panel-resize-handle"
|
|
460
|
+
onMouseDown={handleResizeStart}
|
|
461
|
+
/>
|
|
462
|
+
<div
|
|
463
|
+
className="message-panel-editor"
|
|
464
|
+
style={editorHeight != null ? { height: editorHeight, flexShrink: 0 } : undefined}
|
|
465
|
+
>
|
|
425
466
|
<div className="editor-with-controls">
|
|
426
467
|
<div className="editor-area">
|
|
427
468
|
<Editor
|
|
@@ -444,6 +485,7 @@ export function MessagePanel(): React.ReactElement {
|
|
|
444
485
|
contextPercent={contextPercent}
|
|
445
486
|
currentAgent={currentAgent}
|
|
446
487
|
onTirePump={handleTirePump}
|
|
488
|
+
onAgentSwitch={handleAgentSwitch}
|
|
447
489
|
/>
|
|
448
490
|
</div>
|
|
449
491
|
</div>
|
|
@@ -53,11 +53,11 @@ interface Settings {
|
|
|
53
53
|
interface ThemeMetadata {
|
|
54
54
|
id: string;
|
|
55
55
|
name: string;
|
|
56
|
-
tier:
|
|
56
|
+
tier: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Tier sort order: S=0, A=1, B=2,
|
|
60
|
-
const TIER_ORDER: Record<string, number> = { S: 0, A: 1, B: 2
|
|
59
|
+
// Tier sort order: S=0, A=1, B=2, unranked=3
|
|
60
|
+
const TIER_ORDER: Record<string, number> = { S: 0, A: 1, B: 2 };
|
|
61
61
|
|
|
62
62
|
// Panel display names for the visibility toggles
|
|
63
63
|
const PANEL_DISPLAY_NAMES: Record<string, string> = {
|
|
@@ -348,7 +348,7 @@ export function SettingsPanel(): React.ReactElement {
|
|
|
348
348
|
>
|
|
349
349
|
{sortedThemes.map(theme => (
|
|
350
350
|
<option key={theme.id} value={theme.id}>
|
|
351
|
-
[{theme.tier}] {theme.name}
|
|
351
|
+
[{theme.tier || 'Unranked'}] {theme.name}
|
|
352
352
|
</option>
|
|
353
353
|
))}
|
|
354
354
|
</select>
|