claude-ws 0.2.1 → 0.3.1
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/bin/claudekanban.js +30 -7
- package/next.config.ts +4 -1
- package/package.json +5 -2
- package/server.ts +80 -26
- package/src/app/{layout.tsx → [locale]/layout.tsx} +25 -9
- package/src/app/{page.tsx → [locale]/page.tsx} +15 -25
- package/src/app/api/agent-factory/discover/route.ts +123 -19
- package/src/app/api/git/generate-message/route.ts +1 -1
- package/src/app/api/tasks/[id]/conversation/route.ts +7 -1
- package/src/app/api/tasks/[id]/route.ts +20 -1
- package/src/app/globals.css +11 -0
- package/src/components/agent-factory/discovery-dialog.tsx +375 -142
- package/src/components/auth/api-key-dialog.tsx +68 -10
- package/src/components/claude/message-block.tsx +98 -121
- package/src/components/claude/tool-use-block.tsx +4 -3
- package/src/components/editor/markdown-file-viewer.tsx +234 -0
- package/src/components/header.tsx +11 -8
- package/src/components/kanban/board.tsx +7 -1
- package/src/components/kanban/column.tsx +15 -10
- package/src/components/kanban/create-task-dialog.tsx +19 -17
- package/src/components/kanban/task-card.tsx +2 -52
- package/src/components/project-settings/project-settings-dialog.tsx +1 -0
- package/src/components/providers/socket-provider.tsx +7 -11
- package/src/components/right-sidebar.tsx +74 -9
- package/src/components/settings/settings-dialog.tsx +5 -68
- package/src/components/settings/settings-page.tsx +101 -0
- package/src/components/sidebar/file-browser/file-create-buttons.tsx +183 -0
- package/src/components/sidebar/file-browser/file-tab-content.tsx +120 -10
- package/src/components/sidebar/file-browser/file-tree-context-menu.tsx +26 -21
- package/src/components/sidebar/file-browser/file-tree-item.tsx +11 -5
- package/src/components/sidebar/file-browser/file-tree.tsx +64 -9
- package/src/components/sidebar/file-browser/unified-search.tsx +10 -8
- package/src/components/sidebar/git-changes/diff-viewer.tsx +8 -1
- package/src/components/sidebar/git-changes/git-panel.tsx +44 -23
- package/src/components/sidebar/sidebar-panel.tsx +7 -5
- package/src/components/task/conversation-view.tsx +78 -77
- package/src/components/task/file-drop-zone.tsx +3 -1
- package/src/components/task/prompt-input.tsx +13 -11
- package/src/components/task/shell-log-view.tsx +1 -1
- package/src/components/task/task-detail-panel.tsx +16 -16
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/language-switcher.tsx +69 -0
- package/src/hooks/use-attempt-stream.ts +58 -77
- package/src/hooks/use-touch-detection.ts +51 -0
- package/src/i18n/config.ts +31 -0
- package/src/i18n/request.ts +15 -0
- package/src/lib/agent-manager.ts +109 -13
- package/src/lib/i18n.ts +30 -0
- package/src/lib/inline-edit-manager.ts +23 -4
- package/src/lib/sdk-event-adapter.ts +9 -61
- package/src/lib/session-manager.ts +7 -4
- package/src/lib/system-prompt.ts +10 -19
- package/src/lib/utils.ts +39 -0
- package/src/proxy.ts +35 -20
- package/src/stores/agent-factory-store.ts +24 -3
- package/src/stores/locale-store.ts +40 -0
- package/src/stores/settings-ui-store.ts +13 -0
- package/src/stores/sidebar-store.ts +7 -3
- package/src/types/agent-factory.ts +10 -0
- package/src/types/index.ts +6 -6
- package/tsconfig.json +3 -0
- /package/src/app/{docs → [locale]/docs}/swagger/page.tsx +0 -0
- /package/src/app/{not-found.tsx → [locale]/not-found.tsx} +0 -0
package/bin/claudekanban.js
CHANGED
|
@@ -14,6 +14,26 @@ const path = require('path');
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const os = require('os');
|
|
16
16
|
|
|
17
|
+
const isWindows = process.platform === 'win32';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert Windows backslash paths to forward slashes for safe shell embedding.
|
|
21
|
+
* On Unix, this is a no-op since paths already use forward slashes.
|
|
22
|
+
*/
|
|
23
|
+
function toShellSafePath(p) {
|
|
24
|
+
return isWindows ? p.replace(/\\/g, '/') : p;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cross-platform command existence check.
|
|
29
|
+
* Uses 'where' on Windows, 'which' on Unix.
|
|
30
|
+
*/
|
|
31
|
+
function whichCommand(cmd) {
|
|
32
|
+
const { execSync } = require('child_process');
|
|
33
|
+
const checker = isWindows ? 'where' : 'which';
|
|
34
|
+
execSync(`${checker} ${cmd}`, { stdio: 'ignore' });
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
// Load environment variables
|
|
18
38
|
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
|
19
39
|
|
|
@@ -80,8 +100,7 @@ async function runMigrations() {
|
|
|
80
100
|
if (!tsxCmd) {
|
|
81
101
|
// Try global tsx
|
|
82
102
|
try {
|
|
83
|
-
|
|
84
|
-
execSync('which tsx', { stdio: 'ignore' });
|
|
103
|
+
whichCommand('tsx');
|
|
85
104
|
tsxCmd = 'tsx';
|
|
86
105
|
} catch {
|
|
87
106
|
throw new Error('tsx not found - this should not happen after dependency installation');
|
|
@@ -89,9 +108,13 @@ async function runMigrations() {
|
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
const { execSync } = require('child_process');
|
|
92
|
-
|
|
111
|
+
// Use forward slashes in shell-embedded paths to avoid Windows backslash escape issues
|
|
112
|
+
const safeDbPath = toShellSafePath(dbPath);
|
|
113
|
+
const safeTsxCmd = toShellSafePath(tsxCmd);
|
|
114
|
+
execSync(`"${safeTsxCmd}" -e "require('${safeDbPath}'); console.log('[Claude Workspace] ✓ Database ready');"`, {
|
|
93
115
|
cwd: packageRoot,
|
|
94
116
|
stdio: 'inherit',
|
|
117
|
+
shell: true,
|
|
95
118
|
env: { ...process.env }
|
|
96
119
|
});
|
|
97
120
|
|
|
@@ -119,7 +142,7 @@ async function startServer() {
|
|
|
119
142
|
|
|
120
143
|
let installCmd = 'npm install --production=false';
|
|
121
144
|
try {
|
|
122
|
-
|
|
145
|
+
whichCommand('pnpm');
|
|
123
146
|
installCmd = 'pnpm install --no-frozen-lockfile';
|
|
124
147
|
} catch {
|
|
125
148
|
// pnpm not found, use npm
|
|
@@ -172,9 +195,10 @@ async function startServer() {
|
|
|
172
195
|
const { execSync } = require('child_process');
|
|
173
196
|
try {
|
|
174
197
|
const nextBin = path.join(packageRoot, 'node_modules', '.bin', 'next');
|
|
198
|
+
const safeNextBin = toShellSafePath(nextBin);
|
|
175
199
|
|
|
176
200
|
// Run next build using local binary directly
|
|
177
|
-
execSync(`"${
|
|
201
|
+
execSync(`"${safeNextBin}" build`, {
|
|
178
202
|
cwd: packageRoot,
|
|
179
203
|
stdio: 'inherit',
|
|
180
204
|
shell: true,
|
|
@@ -218,8 +242,7 @@ async function startServer() {
|
|
|
218
242
|
if (!tsxCmd) {
|
|
219
243
|
try {
|
|
220
244
|
// Try using tsx from global or local pnpm/npm
|
|
221
|
-
|
|
222
|
-
execSync('which tsx', { stdio: 'ignore' });
|
|
245
|
+
whichCommand('tsx');
|
|
223
246
|
tsxCmd = 'tsx';
|
|
224
247
|
} catch {
|
|
225
248
|
console.error('[Claude Workspace] Error: tsx not found');
|
package/next.config.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import createNextIntlPlugin from 'next-intl/plugin';
|
|
4
|
+
|
|
5
|
+
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
|
3
6
|
|
|
4
7
|
const nextConfig: NextConfig = {
|
|
5
8
|
outputFileTracingRoot: path.join(__dirname),
|
|
@@ -26,4 +29,4 @@ const nextConfig: NextConfig = {
|
|
|
26
29
|
},
|
|
27
30
|
};
|
|
28
31
|
|
|
29
|
-
export default nextConfig;
|
|
32
|
+
export default withNextIntl(nextConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-ws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A beautifully crafted workspace interface for Claude Code with real-time streaming and local SQLite database",
|
|
6
6
|
"keywords": [
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"sqlite"
|
|
17
17
|
],
|
|
18
18
|
"license": "MIT",
|
|
19
|
+
"author": "Claude Workspace",
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
21
22
|
"url": "https://github.com/Claude-Workspace/claude-ws.git"
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"prepublishOnly": "npm run build"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
70
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
71
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.20",
|
|
71
72
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
72
73
|
"@codemirror/lang-cpp": "^6.0.3",
|
|
73
74
|
"@codemirror/lang-css": "^6.3.1",
|
|
@@ -122,9 +123,11 @@
|
|
|
122
123
|
"eslint-config-next": "^16.1.3",
|
|
123
124
|
"highlight.js": "^11.11.1",
|
|
124
125
|
"js-yaml": "^4.1.1",
|
|
126
|
+
"lowlight": "^3.3.0",
|
|
125
127
|
"lucide-react": "^0.562.0",
|
|
126
128
|
"nanoid": "^5.1.6",
|
|
127
129
|
"next": "^16.1.3",
|
|
130
|
+
"next-intl": "^4.7.0",
|
|
128
131
|
"next-themes": "^0.4.6",
|
|
129
132
|
"react": "19.2.3",
|
|
130
133
|
"react-dom": "19.2.3",
|
package/server.ts
CHANGED
|
@@ -26,12 +26,31 @@ const dev = process.env.NODE_ENV !== 'production';
|
|
|
26
26
|
const hostname = 'localhost';
|
|
27
27
|
const port = parseInt(process.env.PORT || '8556', 10);
|
|
28
28
|
|
|
29
|
+
// API authentication key (optional)
|
|
30
|
+
const API_ACCESS_KEY = process.env.API_ACCESS_KEY;
|
|
31
|
+
|
|
29
32
|
const app = next({ dev, hostname, port, turbopack: false });
|
|
30
33
|
const handle = app.getRequestHandler();
|
|
31
34
|
|
|
32
35
|
app.prepare().then(async () => {
|
|
33
36
|
const httpServer = createServer((req, res) => {
|
|
34
37
|
const parsedUrl = parse(req.url!, true);
|
|
38
|
+
const pathname = parsedUrl.pathname || '';
|
|
39
|
+
|
|
40
|
+
// API authentication check
|
|
41
|
+
const isApiRoute = pathname.startsWith('/api/');
|
|
42
|
+
const isVerifyEndpoint = pathname === '/api/auth/verify';
|
|
43
|
+
|
|
44
|
+
if (isApiRoute && !isVerifyEndpoint && API_ACCESS_KEY && API_ACCESS_KEY.length > 0) {
|
|
45
|
+
const providedKey = req.headers['x-api-key'];
|
|
46
|
+
|
|
47
|
+
if (!providedKey || providedKey !== API_ACCESS_KEY) {
|
|
48
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify({ error: 'Unauthorized', message: 'Valid API key required' }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
handle(req, res, parsedUrl);
|
|
36
55
|
});
|
|
37
56
|
|
|
@@ -635,9 +654,50 @@ app.prepare().then(async () => {
|
|
|
635
654
|
});
|
|
636
655
|
if (!project) return;
|
|
637
656
|
|
|
638
|
-
//
|
|
639
|
-
|
|
657
|
+
// Extract port and find existing process PID
|
|
658
|
+
// Add delay to let nohup process bind to port before checking
|
|
659
|
+
const portMatch = shell.originalCommand?.match(/lsof\s+-ti\s+:(\d+)/);
|
|
660
|
+
if (portMatch) {
|
|
661
|
+
const port = portMatch[1];
|
|
662
|
+
console.log(`[Server] Waiting 6.6s for process to bind to port ${port}...`);
|
|
663
|
+
await new Promise(resolve => setTimeout(resolve, 6666));
|
|
664
|
+
try {
|
|
665
|
+
const { execSync } = require('child_process');
|
|
666
|
+
const pidOutput = execSync(`lsof -ti :${port} 2>/dev/null || true`, { encoding: 'utf-8' }).trim();
|
|
667
|
+
if (pidOutput) {
|
|
668
|
+
const pid = parseInt(pidOutput.split('\n')[0], 10);
|
|
669
|
+
if (pid) {
|
|
670
|
+
// Track existing process instead of respawning
|
|
671
|
+
console.log(`[Server] Found existing process on port ${port}: PID ${pid}`);
|
|
672
|
+
const shellId = shellManager.trackExternalProcess({
|
|
673
|
+
projectId: project.id,
|
|
674
|
+
attemptId,
|
|
675
|
+
pid,
|
|
676
|
+
command: shell.command,
|
|
677
|
+
cwd: project.path,
|
|
678
|
+
});
|
|
640
679
|
|
|
680
|
+
if (shellId) {
|
|
681
|
+
await db.insert(schema.shells).values({
|
|
682
|
+
id: shellId,
|
|
683
|
+
projectId: project.id,
|
|
684
|
+
attemptId,
|
|
685
|
+
command: shell.command,
|
|
686
|
+
cwd: project.path,
|
|
687
|
+
pid,
|
|
688
|
+
status: 'running',
|
|
689
|
+
});
|
|
690
|
+
console.log(`[Server] Tracking external process ${shellId} (PID ${pid})`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} catch {
|
|
696
|
+
// Fall through to spawn new shell
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// No existing process found, spawn new shell
|
|
641
701
|
const shellId = shellManager.spawn({
|
|
642
702
|
projectId: project.id,
|
|
643
703
|
attemptId,
|
|
@@ -663,8 +723,8 @@ app.prepare().then(async () => {
|
|
|
663
723
|
});
|
|
664
724
|
|
|
665
725
|
// Handle tracked process from BGPID pattern in bash output
|
|
666
|
-
//
|
|
667
|
-
agentManager.on('trackedProcess', async ({ attemptId, pid, command }) => {
|
|
726
|
+
// Track existing process instead of kill-and-respawn to avoid port conflicts
|
|
727
|
+
agentManager.on('trackedProcess', async ({ attemptId, pid, command, logFile: eventLogFile }) => {
|
|
668
728
|
console.log(`[Server] Tracked process detected for ${attemptId}: PID ${pid}`);
|
|
669
729
|
|
|
670
730
|
try {
|
|
@@ -695,49 +755,43 @@ app.prepare().then(async () => {
|
|
|
695
755
|
return;
|
|
696
756
|
}
|
|
697
757
|
|
|
698
|
-
//
|
|
699
|
-
try {
|
|
700
|
-
process.kill(pid, 'SIGTERM');
|
|
701
|
-
console.log(`[Server] Killed nohup'd process ${pid}`);
|
|
702
|
-
} catch {
|
|
703
|
-
console.log(`[Server] Process ${pid} already dead or not killable`);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Extract actual command from nohup wrapper
|
|
707
|
-
// Pattern: "nohup <command> > /tmp/xxx.log 2>&1 & echo ..."
|
|
708
|
-
// or: "kill ...; sleep ...; nohup <command> > ..."
|
|
758
|
+
// Extract actual command from nohup wrapper, use eventLogFile if provided
|
|
709
759
|
let actualCommand = command;
|
|
710
|
-
|
|
760
|
+
let logFile = eventLogFile;
|
|
761
|
+
const nohupMatch = command.match(/nohup\s+(.+?)\s*>\s*(\/tmp\/[^\s]+\.log)/);
|
|
711
762
|
if (nohupMatch) {
|
|
712
763
|
actualCommand = nohupMatch[1].trim();
|
|
764
|
+
logFile = logFile || nohupMatch[2];
|
|
713
765
|
}
|
|
714
|
-
console.log(`[Server] Extracted command: ${actualCommand}`);
|
|
715
|
-
|
|
716
|
-
// Wait for port to be released
|
|
717
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
766
|
+
console.log(`[Server] Extracted command: ${actualCommand}, logFile: ${logFile}`);
|
|
718
767
|
|
|
719
|
-
//
|
|
720
|
-
const shellId = shellManager.
|
|
768
|
+
// Track existing process via ShellManager (no kill-and-respawn)
|
|
769
|
+
const shellId = shellManager.trackExternalProcess({
|
|
721
770
|
projectId: project.id,
|
|
722
771
|
attemptId,
|
|
772
|
+
pid,
|
|
723
773
|
command: actualCommand,
|
|
724
774
|
cwd: project.path,
|
|
725
|
-
|
|
775
|
+
logFile,
|
|
726
776
|
});
|
|
727
777
|
|
|
778
|
+
if (!shellId) {
|
|
779
|
+
console.error(`[Server] Failed to track process: PID ${pid} not alive`);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
728
783
|
// Save to database for persistence
|
|
729
|
-
const shellInfo = shellManager.getShellInfo(shellId);
|
|
730
784
|
await db.insert(schema.shells).values({
|
|
731
785
|
id: shellId,
|
|
732
786
|
projectId: project.id,
|
|
733
787
|
attemptId,
|
|
734
788
|
command: actualCommand,
|
|
735
789
|
cwd: project.path,
|
|
736
|
-
pid
|
|
790
|
+
pid,
|
|
737
791
|
status: 'running',
|
|
738
792
|
});
|
|
739
793
|
|
|
740
|
-
console.log(`[Server]
|
|
794
|
+
console.log(`[Server] Tracking external process ${shellId} (PID ${pid}) for project ${project.id}`);
|
|
741
795
|
} catch (error) {
|
|
742
796
|
console.error(`[Server] Failed to track process:`, error);
|
|
743
797
|
}
|
|
@@ -3,7 +3,11 @@ import { Geist, Geist_Mono } from 'next/font/google';
|
|
|
3
3
|
import { Toaster } from 'sonner';
|
|
4
4
|
import { ThemeProvider } from '@/components/providers/theme-provider';
|
|
5
5
|
import { SocketProvider } from '@/components/providers/socket-provider';
|
|
6
|
-
import '
|
|
6
|
+
import { NextIntlClientProvider } from 'next-intl';
|
|
7
|
+
import { getMessages } from 'next-intl/server';
|
|
8
|
+
import { notFound } from 'next/navigation';
|
|
9
|
+
import { locales } from '@/i18n/config';
|
|
10
|
+
import '../globals.css';
|
|
7
11
|
|
|
8
12
|
// Force dynamic rendering to avoid Next.js 16 Turbopack bugs
|
|
9
13
|
export const dynamic = 'force-dynamic';
|
|
@@ -42,22 +46,34 @@ export const metadata: Metadata = {
|
|
|
42
46
|
},
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
export default function RootLayout({
|
|
49
|
+
export default async function RootLayout({
|
|
46
50
|
children,
|
|
51
|
+
params,
|
|
47
52
|
}: Readonly<{
|
|
48
53
|
children: React.ReactNode;
|
|
54
|
+
params: Promise<{ locale: string }>;
|
|
49
55
|
}>) {
|
|
56
|
+
const { locale } = await params;
|
|
57
|
+
|
|
58
|
+
if (!locales.includes(locale as any)) {
|
|
59
|
+
notFound();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const messages = await getMessages();
|
|
63
|
+
|
|
50
64
|
return (
|
|
51
|
-
<html lang=
|
|
65
|
+
<html lang={locale} suppressHydrationWarning>
|
|
52
66
|
<body
|
|
53
67
|
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background text-foreground`}
|
|
54
68
|
>
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
<NextIntlClientProvider messages={messages}>
|
|
70
|
+
<SocketProvider>
|
|
71
|
+
<ThemeProvider>
|
|
72
|
+
{children}
|
|
73
|
+
<Toaster position="top-right" richColors closeButton />
|
|
74
|
+
</ThemeProvider>
|
|
75
|
+
</SocketProvider>
|
|
76
|
+
</NextIntlClientProvider>
|
|
61
77
|
</body>
|
|
62
78
|
</html>
|
|
63
79
|
);
|
|
@@ -7,27 +7,26 @@ import { Header } from '@/components/header';
|
|
|
7
7
|
import { Board } from '@/components/kanban/board';
|
|
8
8
|
import { CreateTaskDialog } from '@/components/kanban/create-task-dialog';
|
|
9
9
|
import { TaskDetailPanel } from '@/components/task/task-detail-panel';
|
|
10
|
-
import {
|
|
10
|
+
import { SettingsPage } from '@/components/settings/settings-page';
|
|
11
11
|
import { SetupDialog } from '@/components/settings/setup-dialog';
|
|
12
12
|
import { SidebarPanel, FileTabsPanel, DiffTabsPanel } from '@/components/sidebar';
|
|
13
13
|
import { RightSidebar } from '@/components/right-sidebar';
|
|
14
|
-
import { ApiKeyProvider
|
|
14
|
+
import { ApiKeyProvider } from '@/components/auth/api-key-dialog';
|
|
15
15
|
import { PluginList } from '@/components/agent-factory/plugin-list';
|
|
16
16
|
import { useProjectStore } from '@/stores/project-store';
|
|
17
17
|
import { useTaskStore } from '@/stores/task-store';
|
|
18
18
|
import { Task } from '@/types';
|
|
19
19
|
import { useSidebarStore } from '@/stores/sidebar-store';
|
|
20
20
|
import { useAgentFactoryUIStore } from '@/stores/agent-factory-ui-store';
|
|
21
|
+
import { useSettingsUIStore } from '@/stores/settings-ui-store';
|
|
21
22
|
|
|
22
23
|
function KanbanApp() {
|
|
23
24
|
const [createTaskOpen, setCreateTaskOpen] = useState(false);
|
|
24
|
-
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
25
25
|
const [setupOpen, setSetupOpen] = useState(false);
|
|
26
|
-
const [apiKeyRefresh, setApiKeyRefresh] = useState(0);
|
|
27
26
|
const [searchQuery, setSearchQuery] = useState('');
|
|
28
27
|
|
|
29
|
-
const { needsApiKey } = useApiKeyCheck(apiKeyRefresh);
|
|
30
28
|
const { open: agentFactoryOpen, setOpen: setAgentFactoryOpen } = useAgentFactoryUIStore();
|
|
29
|
+
const { open: settingsOpen, setOpen: setSettingsOpen } = useSettingsUIStore();
|
|
31
30
|
|
|
32
31
|
const { projects, selectedProjectIds, fetchProjects, loading: projectLoading } = useProjectStore();
|
|
33
32
|
const { selectedTask, fetchTasks, setSelectedTask, setPendingAutoStartTask } = useTaskStore();
|
|
@@ -48,8 +47,8 @@ function KanbanApp() {
|
|
|
48
47
|
// Rehydrate from localStorage and fetch projects on mount
|
|
49
48
|
useEffect(() => {
|
|
50
49
|
useProjectStore.persist.rehydrate();
|
|
51
|
-
fetchProjects();
|
|
52
|
-
}, [
|
|
50
|
+
useProjectStore.getState().fetchProjects();
|
|
51
|
+
}, []);
|
|
53
52
|
|
|
54
53
|
// Read project from URL and select it
|
|
55
54
|
useEffect(() => {
|
|
@@ -90,9 +89,9 @@ function KanbanApp() {
|
|
|
90
89
|
// Fetch tasks when selectedProjectIds changes
|
|
91
90
|
useEffect(() => {
|
|
92
91
|
if (!projectLoading) {
|
|
93
|
-
fetchTasks(selectedProjectIds);
|
|
92
|
+
useTaskStore.getState().fetchTasks(selectedProjectIds);
|
|
94
93
|
}
|
|
95
|
-
}, [selectedProjectIds, projectLoading
|
|
94
|
+
}, [selectedProjectIds, projectLoading]);
|
|
96
95
|
|
|
97
96
|
// Handle task created event - select task if startNow is true
|
|
98
97
|
const handleTaskCreated = (task: Task, startNow: boolean, processedPrompt?: string, fileIds?: string[]) => {
|
|
@@ -182,7 +181,6 @@ function KanbanApp() {
|
|
|
182
181
|
<div className="flex h-screen flex-col">
|
|
183
182
|
<Header
|
|
184
183
|
onCreateTask={() => setCreateTaskOpen(true)}
|
|
185
|
-
onOpenSettings={() => setSettingsOpen(true)}
|
|
186
184
|
onAddProject={() => setSetupOpen(true)}
|
|
187
185
|
searchQuery={searchQuery}
|
|
188
186
|
onSearchChange={setSearchQuery}
|
|
@@ -227,21 +225,7 @@ function KanbanApp() {
|
|
|
227
225
|
onOpenChange={setCreateTaskOpen}
|
|
228
226
|
onTaskCreated={handleTaskCreated}
|
|
229
227
|
/>
|
|
230
|
-
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
|
231
228
|
<SetupDialog open={setupOpen || autoShowSetup} onOpenChange={setSetupOpen} />
|
|
232
|
-
<ApiKeyDialog
|
|
233
|
-
open={needsApiKey}
|
|
234
|
-
onOpenChange={(open) => {
|
|
235
|
-
// Allow closing only if not needed
|
|
236
|
-
if (!open && !needsApiKey) return;
|
|
237
|
-
}}
|
|
238
|
-
onSuccess={() => {
|
|
239
|
-
// Trigger refresh to re-check API key status
|
|
240
|
-
setApiKeyRefresh(prev => prev + 1);
|
|
241
|
-
// Refetch projects after API key is set
|
|
242
|
-
fetchProjects();
|
|
243
|
-
}}
|
|
244
|
-
/>
|
|
245
229
|
|
|
246
230
|
{/* Agent Factory Dialog */}
|
|
247
231
|
{agentFactoryOpen && (
|
|
@@ -250,11 +234,17 @@ function KanbanApp() {
|
|
|
250
234
|
</div>
|
|
251
235
|
)}
|
|
252
236
|
|
|
237
|
+
{/* Settings Page */}
|
|
238
|
+
{settingsOpen && (
|
|
239
|
+
<div className="fixed inset-0 z-50 bg-background">
|
|
240
|
+
<SettingsPage />
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
253
244
|
{/* Right Sidebar - actions panel */}
|
|
254
245
|
<RightSidebar
|
|
255
246
|
projectId={selectedProjectIds[0]}
|
|
256
247
|
onCreateTask={() => setCreateTaskOpen(true)}
|
|
257
|
-
onOpenSettings={() => setSettingsOpen(true)}
|
|
258
248
|
/>
|
|
259
249
|
</div>
|
|
260
250
|
);
|