jettypod 4.4.120 → 4.4.121
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/.env +2 -1
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +54 -49
- package/apps/dashboard/app/demo/gates/page.tsx +3 -5
- package/apps/dashboard/app/design-system/page.tsx +1 -1
- package/apps/dashboard/app/globals.css +74 -2
- package/apps/dashboard/app/install-claude/page.tsx +3 -5
- package/apps/dashboard/app/login/page.tsx +17 -20
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +60 -12
- package/apps/dashboard/app/signup/page.tsx +14 -17
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +12 -15
- package/apps/dashboard/app/work/[id]/page.tsx +90 -75
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +70 -61
- package/apps/dashboard/components/CardMenu.tsx +0 -1
- package/apps/dashboard/components/ClaudePanel.tsx +541 -283
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/CopyableId.tsx +1 -2
- package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
- package/apps/dashboard/components/DragContext.tsx +132 -62
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +5 -6
- package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +0 -1
- package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
- package/apps/dashboard/components/EpicGroup.tsx +100 -70
- package/apps/dashboard/components/GateCard.tsx +0 -1
- package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
- package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/JettyLoader.tsx +0 -1
- package/apps/dashboard/components/KanbanBoard.tsx +319 -173
- package/apps/dashboard/components/KanbanCard.tsx +341 -107
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
- package/apps/dashboard/components/MainNav.tsx +24 -25
- package/apps/dashboard/components/MessageBlock.tsx +93 -16
- package/apps/dashboard/components/ModeStartCard.tsx +0 -1
- package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
- package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
- package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
- package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
- package/apps/dashboard/components/ReviewFooter.tsx +12 -14
- package/apps/dashboard/components/SessionList.tsx +0 -1
- package/apps/dashboard/components/SubscribeContent.tsx +40 -11
- package/apps/dashboard/components/TestTree.tsx +1 -2
- package/apps/dashboard/components/TipCard.tsx +2 -4
- package/apps/dashboard/components/Toast.tsx +0 -1
- package/apps/dashboard/components/TypeIcon.tsx +7 -8
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
- package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
- package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
- package/apps/dashboard/components/WorkItemTree.tsx +2 -4
- package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +20 -73
- package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
- package/apps/dashboard/components/ui/Button.tsx +1 -1
- package/apps/dashboard/components/ui/Input.tsx +1 -1
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
- package/apps/dashboard/contexts/UsageContext.tsx +62 -31
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1302
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +226 -26
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +253 -122
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +3 -3
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -33
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +167 -30
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/jettypod.js +96 -4
- package/lib/bdd-preflight.js +96 -0
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/work-commands/index.js +58 -16
- package/lib/work-tracking/index.js +108 -8
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +43 -1
- package/skills-templates/chore-mode/SKILL.md +40 -1
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +14 -0
- package/skills-templates/feature-planning/SKILL.md +90 -1
- package/skills-templates/production-mode/SKILL.md +20 -0
- package/skills-templates/simple-improvement/SKILL.md +39 -2
- package/skills-templates/speed-mode/SKILL.md +10 -15
- package/skills-templates/stable-mode/SKILL.md +47 -0
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -55
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
- package/apps/dashboard/electron/ipc-handlers.js +0 -1026
- package/apps/dashboard/electron/main.js +0 -2306
- package/apps/dashboard/electron/preload.js +0 -125
- package/apps/dashboard/electron/session-manager.js +0 -163
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/backlog-parser.ts +0 -50
- package/apps/dashboard/lib/claude-process-manager.ts +0 -529
- package/apps/dashboard/lib/db-bridge.ts +0 -283
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -66
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/bug-icon.svg +0 -9
- package/apps/dashboard/public/buoy-icon.svg +0 -9
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/in-flight-seagull.svg +0 -9
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/pier-icon.svg +0 -14
- package/apps/dashboard/public/star-icon.svg +0 -9
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/public/wrench-icon.svg +0 -9
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useCallback } from 'react';
|
|
4
|
+
import { openDialog } from '@/lib/tauri';
|
|
5
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
6
|
+
import type { ContextDocument } from '@/lib/data-bridge';
|
|
7
|
+
|
|
8
|
+
interface ContextDocumentsSectionProps {
|
|
9
|
+
initialDocuments: ContextDocument[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ContextDocumentsSection({ initialDocuments }: ContextDocumentsSectionProps) {
|
|
13
|
+
const [documents, setDocuments] = useState<ContextDocument[]>(initialDocuments);
|
|
14
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
15
|
+
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
16
|
+
const [editingDoc, setEditingDoc] = useState<{ name: string; content: string }>({ name: '', content: '' });
|
|
17
|
+
const [isAdding, setIsAdding] = useState(false);
|
|
18
|
+
const [dragOver, setDragOver] = useState(false);
|
|
19
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
20
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
22
|
+
|
|
23
|
+
const handleAddText = () => {
|
|
24
|
+
setDropdownOpen(false);
|
|
25
|
+
setIsAdding(true);
|
|
26
|
+
setEditingDoc({ name: '', content: '' });
|
|
27
|
+
setValidationError(null);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleAddFile = async () => {
|
|
31
|
+
setDropdownOpen(false);
|
|
32
|
+
const selected = await openDialog({ multiple: true, title: 'Select context files' });
|
|
33
|
+
if (!selected) return;
|
|
34
|
+
const paths = Array.isArray(selected) ? selected : [selected];
|
|
35
|
+
for (const filePath of paths) {
|
|
36
|
+
if (typeof filePath === 'string') {
|
|
37
|
+
const name = filePath.split('/').pop() || filePath;
|
|
38
|
+
// Skip duplicates by name
|
|
39
|
+
if (documents.some(d => d.name === name)) continue;
|
|
40
|
+
const doc: ContextDocument = { type: 'file', name, path: filePath };
|
|
41
|
+
await dataBridge.addContextDocument(doc);
|
|
42
|
+
setDocuments(prev => [...prev, doc]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleSaveNew = async () => {
|
|
48
|
+
if (!editingDoc.name.trim()) {
|
|
49
|
+
setValidationError('Name is required');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!editingDoc.content.trim()) {
|
|
53
|
+
setValidationError('Content is required');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setValidationError(null);
|
|
57
|
+
const doc: ContextDocument = { type: 'text', name: editingDoc.name.trim(), content: editingDoc.content };
|
|
58
|
+
await dataBridge.addContextDocument(doc);
|
|
59
|
+
setDocuments(prev => [...prev, doc]);
|
|
60
|
+
setIsAdding(false);
|
|
61
|
+
setEditingDoc({ name: '', content: '' });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleCancelNew = () => {
|
|
65
|
+
setIsAdding(false);
|
|
66
|
+
setEditingDoc({ name: '', content: '' });
|
|
67
|
+
setValidationError(null);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleEdit = (index: number) => {
|
|
71
|
+
const doc = documents[index];
|
|
72
|
+
setEditingIndex(index);
|
|
73
|
+
setEditingDoc({ name: doc.name, content: doc.content || '' });
|
|
74
|
+
setValidationError(null);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleSaveEdit = async () => {
|
|
78
|
+
if (editingIndex === null) return;
|
|
79
|
+
if (!editingDoc.name.trim()) {
|
|
80
|
+
setValidationError('Name is required');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!editingDoc.content.trim()) {
|
|
84
|
+
setValidationError('Content is required');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setValidationError(null);
|
|
88
|
+
const doc: ContextDocument = { type: 'text', name: editingDoc.name.trim(), content: editingDoc.content };
|
|
89
|
+
await dataBridge.updateContextDocument(editingIndex, doc);
|
|
90
|
+
setDocuments(prev => prev.map((d, i) => i === editingIndex ? doc : d));
|
|
91
|
+
setEditingIndex(null);
|
|
92
|
+
setEditingDoc({ name: '', content: '' });
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleCancelEdit = () => {
|
|
96
|
+
setEditingIndex(null);
|
|
97
|
+
setEditingDoc({ name: '', content: '' });
|
|
98
|
+
setValidationError(null);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleRemove = async (index: number) => {
|
|
102
|
+
await dataBridge.removeContextDocument(index);
|
|
103
|
+
setDocuments(prev => prev.filter((_, i) => i !== index));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
setDragOver(true);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
112
|
+
if (containerRef.current && !containerRef.current.contains(e.relatedTarget as Node)) {
|
|
113
|
+
setDragOver(false);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
setDragOver(false);
|
|
120
|
+
const files = Array.from(e.dataTransfer.files);
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const filePath = (file as unknown as { path?: string }).path || file.name;
|
|
123
|
+
const name = file.name;
|
|
124
|
+
// Skip duplicates by name
|
|
125
|
+
if (documents.some(d => d.name === name)) continue;
|
|
126
|
+
const doc: ContextDocument = { type: 'file', name, path: filePath };
|
|
127
|
+
await dataBridge.addContextDocument(doc);
|
|
128
|
+
setDocuments(prev => [...prev, doc]);
|
|
129
|
+
}
|
|
130
|
+
}, [documents]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="mt-8">
|
|
134
|
+
<div className="flex items-center justify-between mb-3">
|
|
135
|
+
<h3 className="text-[15px] font-medium text-zinc-900 dark:text-zinc-100">
|
|
136
|
+
Context Documents
|
|
137
|
+
</h3>
|
|
138
|
+
<div className="relative" ref={dropdownRef}>
|
|
139
|
+
<button
|
|
140
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
141
|
+
className="w-8 h-8 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 text-zinc-400 dark:text-zinc-500 text-lg flex items-center justify-center transition-all duration-200 hover:border-[#819D9F] hover:text-zinc-900 dark:hover:text-zinc-100 active:scale-95"
|
|
142
|
+
>
|
|
143
|
+
+
|
|
144
|
+
</button>
|
|
145
|
+
{dropdownOpen && (
|
|
146
|
+
<>
|
|
147
|
+
<div className="fixed inset-0 z-10" onClick={() => setDropdownOpen(false)} />
|
|
148
|
+
<div className="absolute top-[calc(100%+6px)] right-0 z-20 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl p-1.5 min-w-[220px] shadow-lg">
|
|
149
|
+
<button
|
|
150
|
+
onClick={handleAddFile}
|
|
151
|
+
className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg w-full text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
|
|
152
|
+
>
|
|
153
|
+
<svg className="w-[18px] h-[18px] text-zinc-400" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
154
|
+
<path d="M10 4v12M15 9.5c0 2.5-2.5 4.5-5 4.5S5 12 5 9.5V6a3 3 0 016 0v4a1.5 1.5 0 01-3 0V6.5" />
|
|
155
|
+
</svg>
|
|
156
|
+
Upload from device
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
onClick={handleAddText}
|
|
160
|
+
className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg w-full text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
|
|
161
|
+
>
|
|
162
|
+
<svg className="w-[18px] h-[18px] text-zinc-400" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
163
|
+
<path d="M4 5h12M4 10h8M4 15h5" />
|
|
164
|
+
<path d="M15 12v6M12 15h6" />
|
|
165
|
+
</svg>
|
|
166
|
+
Add text content
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div
|
|
175
|
+
ref={containerRef}
|
|
176
|
+
onDragOver={handleDragOver}
|
|
177
|
+
onDragLeave={handleDragLeave}
|
|
178
|
+
onDrop={handleDrop}
|
|
179
|
+
className={`bg-zinc-50 dark:bg-zinc-800/50 rounded-xl border-2 transition-all duration-200 overflow-hidden ${
|
|
180
|
+
dragOver
|
|
181
|
+
? 'border-[#819D9F] shadow-[0_0_0_3px_rgba(129,157,159,0.15)]'
|
|
182
|
+
: 'border-zinc-200 dark:border-zinc-700'
|
|
183
|
+
}`}
|
|
184
|
+
>
|
|
185
|
+
{documents.length === 0 && !isAdding ? (
|
|
186
|
+
<div className="py-10 text-center text-zinc-400 dark:text-zinc-500 text-sm">
|
|
187
|
+
Drop files here or use + to add context
|
|
188
|
+
</div>
|
|
189
|
+
) : (
|
|
190
|
+
<>
|
|
191
|
+
{documents.map((doc, index) => (
|
|
192
|
+
editingIndex === index ? (
|
|
193
|
+
<div key={index} className="bg-zinc-100 dark:bg-zinc-900/50 border-b border-zinc-200/50 dark:border-zinc-700/40">
|
|
194
|
+
<div className="flex items-center gap-2.5 px-3.5 pt-3">
|
|
195
|
+
<Grip />
|
|
196
|
+
<TypeIcon type={doc.type} />
|
|
197
|
+
<input
|
|
198
|
+
type="text"
|
|
199
|
+
value={editingDoc.name}
|
|
200
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, name: e.target.value }))}
|
|
201
|
+
placeholder="Name this snippet..."
|
|
202
|
+
className="flex-1 bg-transparent border-none text-[13px] font-medium text-zinc-900 dark:text-zinc-100 outline-none"
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="px-3.5 mt-2">
|
|
206
|
+
<textarea
|
|
207
|
+
value={editingDoc.content}
|
|
208
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, content: e.target.value }))}
|
|
209
|
+
placeholder="Type or paste your context text here..."
|
|
210
|
+
className="w-full bg-white dark:bg-zinc-800 border-2 border-zinc-200 dark:border-zinc-700 rounded-lg px-3 py-2.5 text-[13px] text-zinc-700 dark:text-zinc-200 leading-relaxed resize-y min-h-[72px] focus:outline-none focus:border-[#819D9F] focus:shadow-[0_0_0_3px_rgba(129,157,159,0.15)] transition-all"
|
|
211
|
+
rows={3}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<div className="flex items-center justify-end gap-2 px-3.5 py-2">
|
|
215
|
+
{validationError && (
|
|
216
|
+
<span className="text-[12px] text-red-500 dark:text-red-400 mr-auto">{validationError}</span>
|
|
217
|
+
)}
|
|
218
|
+
<button onClick={handleCancelEdit} className="px-3.5 py-1.5 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-transparent text-zinc-500 text-[13px] font-medium hover:border-zinc-300 dark:hover:border-zinc-600 hover:text-zinc-700 dark:hover:text-zinc-300 transition-all">Cancel</button>
|
|
219
|
+
<button onClick={handleSaveEdit} className="px-3.5 py-1.5 rounded-lg border-none bg-[#819D9F] text-zinc-900 text-[13px] font-medium hover:brightness-105 transition-all active:scale-[0.98]">Save</button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
) : (
|
|
223
|
+
<div
|
|
224
|
+
key={index}
|
|
225
|
+
className="group flex items-center px-3.5 py-2.5 border-b border-zinc-200/50 dark:border-zinc-700/40 last:border-b-0 hover:bg-white/50 dark:hover:bg-white/[0.02] transition-colors"
|
|
226
|
+
>
|
|
227
|
+
<Grip />
|
|
228
|
+
<TypeIcon type={doc.type} />
|
|
229
|
+
<div className="flex-1 min-w-0 ml-2.5">
|
|
230
|
+
<div className="text-[13px] font-medium text-zinc-900 dark:text-zinc-100 truncate">{doc.name}</div>
|
|
231
|
+
<div className="text-[11px] text-zinc-400 dark:text-zinc-500 truncate font-mono mt-0.5">
|
|
232
|
+
{doc.type === 'file' ? doc.path : (doc.content || '').slice(0, 80) + ((doc.content || '').length > 80 ? '...' : '')}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
236
|
+
{doc.type === 'text' && (
|
|
237
|
+
<button onClick={() => handleEdit(index)} className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-200 transition-all text-[13px]" title="Edit">
|
|
238
|
+
✎
|
|
239
|
+
</button>
|
|
240
|
+
)}
|
|
241
|
+
<button onClick={() => handleRemove(index)} className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-zinc-400 hover:bg-red-100 dark:hover:bg-red-900/20 hover:text-red-600 dark:hover:text-red-400 transition-all text-[13px]" title="Remove">
|
|
242
|
+
×
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
))}
|
|
248
|
+
|
|
249
|
+
{isAdding && (
|
|
250
|
+
<div className="bg-zinc-100 dark:bg-zinc-900/50 border-b border-zinc-200/50 dark:border-zinc-700/40">
|
|
251
|
+
<div className="flex items-center gap-2.5 px-3.5 pt-3">
|
|
252
|
+
<Grip />
|
|
253
|
+
<TypeIcon type="text" />
|
|
254
|
+
<input
|
|
255
|
+
type="text"
|
|
256
|
+
value={editingDoc.name}
|
|
257
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, name: e.target.value }))}
|
|
258
|
+
placeholder="Name this snippet..."
|
|
259
|
+
className="flex-1 bg-transparent border-none text-[13px] font-medium text-zinc-900 dark:text-zinc-100 outline-none"
|
|
260
|
+
autoFocus
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
<div className="px-3.5 mt-2">
|
|
264
|
+
<textarea
|
|
265
|
+
value={editingDoc.content}
|
|
266
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, content: e.target.value }))}
|
|
267
|
+
placeholder="Type or paste your context text here..."
|
|
268
|
+
className="w-full bg-white dark:bg-zinc-800 border-2 border-zinc-200 dark:border-zinc-700 rounded-lg px-3 py-2.5 text-[13px] text-zinc-700 dark:text-zinc-200 leading-relaxed resize-y min-h-[72px] focus:outline-none focus:border-[#819D9F] focus:shadow-[0_0_0_3px_rgba(129,157,159,0.15)] transition-all"
|
|
269
|
+
rows={3}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="flex items-center justify-end gap-2 px-3.5 py-2">
|
|
273
|
+
{validationError && (
|
|
274
|
+
<span className="text-[12px] text-red-500 dark:text-red-400 mr-auto">{validationError}</span>
|
|
275
|
+
)}
|
|
276
|
+
<button onClick={handleCancelNew} className="px-3.5 py-1.5 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-transparent text-zinc-500 text-[13px] font-medium hover:border-zinc-300 dark:hover:border-zinc-600 hover:text-zinc-700 dark:hover:text-zinc-300 transition-all">Cancel</button>
|
|
277
|
+
<button onClick={handleSaveNew} className="px-3.5 py-1.5 rounded-lg border-none bg-[#819D9F] text-zinc-900 text-[13px] font-medium hover:brightness-105 transition-all active:scale-[0.98]">Save</button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{dragOver && (
|
|
285
|
+
<div className="py-5 text-center border-t border-dashed border-zinc-300 dark:border-zinc-600">
|
|
286
|
+
<span className="text-[13px] font-medium text-[#819D9F]">Drop files to add as context</span>
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function Grip() {
|
|
295
|
+
return (
|
|
296
|
+
<div className="flex flex-col gap-[1.5px] cursor-grab opacity-20 hover:opacity-60 transition-opacity p-1">
|
|
297
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
298
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
299
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function TypeIcon({ type }: { type: 'file' | 'text' }) {
|
|
305
|
+
if (type === 'file') {
|
|
306
|
+
return (
|
|
307
|
+
<div className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#819D9F]/15 text-[#819D9F]">
|
|
308
|
+
F
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return (
|
|
313
|
+
<div className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#e57a44]/12 text-[#e57a44]">
|
|
314
|
+
T
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
|
|
3
2
|
import { useState } from 'react';
|
|
3
|
+
import { invoke } from '@/lib/tauri';
|
|
4
4
|
import { Input } from '@/components/ui/Input';
|
|
5
5
|
import { Button } from '@/components/ui/Button';
|
|
6
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
6
7
|
|
|
7
8
|
interface EnvVar {
|
|
8
9
|
name: string;
|
|
@@ -43,22 +44,8 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
43
44
|
setCurrentFile(filename);
|
|
44
45
|
setErrorMessage(null);
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: { 'Content-Type': 'application/json' },
|
|
50
|
-
body: JSON.stringify({ action: 'select', file: filename }),
|
|
51
|
-
});
|
|
52
|
-
// Load vars from selected file
|
|
53
|
-
const res = await fetch(`/api/settings/env-vars?file=${encodeURIComponent(filename)}`);
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
const data = await res.json();
|
|
56
|
-
setErrorMessage(data.error || 'Failed to load environment variables');
|
|
57
|
-
setEnvVars([]);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const vars = await res.json();
|
|
61
|
-
setEnvVars(vars);
|
|
47
|
+
const vars = await dataBridge.getEnvVars(filename);
|
|
48
|
+
setEnvVars(vars.map(v => ({ name: v.key, value: v.value })));
|
|
62
49
|
} catch {
|
|
63
50
|
setErrorMessage('Failed to load environment variables');
|
|
64
51
|
setEnvVars([]);
|
|
@@ -67,22 +54,16 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
67
54
|
|
|
68
55
|
const refreshFiles = async () => {
|
|
69
56
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (varsRes.ok) {
|
|
81
|
-
setEnvVars(await varsRes.json());
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
setEnvVars([]);
|
|
85
|
-
}
|
|
57
|
+
const files = await dataBridge.discoverEnvFiles();
|
|
58
|
+
setEnvFiles(files);
|
|
59
|
+
if (currentFile && !files.includes(currentFile)) {
|
|
60
|
+
const fallback = files[0] || null;
|
|
61
|
+
setCurrentFile(fallback);
|
|
62
|
+
if (fallback) {
|
|
63
|
+
const vars = await dataBridge.getEnvVars(fallback);
|
|
64
|
+
setEnvVars(vars.map(v => ({ name: v.key, value: v.value })));
|
|
65
|
+
} else {
|
|
66
|
+
setEnvVars([]);
|
|
86
67
|
}
|
|
87
68
|
}
|
|
88
69
|
} catch {
|
|
@@ -93,16 +74,9 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
93
74
|
const handleCreateEnvFile = async () => {
|
|
94
75
|
setErrorMessage(null);
|
|
95
76
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
body: JSON.stringify({ action: 'create', file: '.env' }),
|
|
100
|
-
});
|
|
101
|
-
if (!res.ok) {
|
|
102
|
-
setErrorMessage('Failed to create .env file');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
setEnvFiles(['.env']);
|
|
77
|
+
await invoke('db_create_env_file', { filename: '.env' });
|
|
78
|
+
const files = await dataBridge.discoverEnvFiles();
|
|
79
|
+
setEnvFiles(files);
|
|
106
80
|
setCurrentFile('.env');
|
|
107
81
|
setEnvVars([]);
|
|
108
82
|
showSuccess('.env file created');
|
|
@@ -128,16 +102,7 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
128
102
|
|
|
129
103
|
setErrorMessage(null);
|
|
130
104
|
try {
|
|
131
|
-
|
|
132
|
-
method: 'POST',
|
|
133
|
-
headers: { 'Content-Type': 'application/json' },
|
|
134
|
-
body: JSON.stringify({ action: 'add', name: formName, value: formValue, file: currentFile }),
|
|
135
|
-
});
|
|
136
|
-
if (!res.ok) {
|
|
137
|
-
const data = await res.json();
|
|
138
|
-
setErrorMessage(data.error || 'Failed to add variable');
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
105
|
+
await invoke('db_create_env_var', { file: currentFile, key: formName, value: formValue });
|
|
141
106
|
setEnvVars([...envVars, { name: formName, value: formValue }]);
|
|
142
107
|
setFormName('');
|
|
143
108
|
setFormValue('');
|
|
@@ -152,16 +117,7 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
152
117
|
if (!editingName) return;
|
|
153
118
|
setErrorMessage(null);
|
|
154
119
|
try {
|
|
155
|
-
|
|
156
|
-
method: 'POST',
|
|
157
|
-
headers: { 'Content-Type': 'application/json' },
|
|
158
|
-
body: JSON.stringify({ name: editingName, value: formValue, file: currentFile }),
|
|
159
|
-
});
|
|
160
|
-
if (!res.ok) {
|
|
161
|
-
const data = await res.json();
|
|
162
|
-
setErrorMessage(data.error || 'Failed to update variable');
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
120
|
+
await invoke('db_update_env_var', { file: currentFile, key: editingName, value: formValue });
|
|
165
121
|
setEnvVars(envVars.map(v => v.name === editingName ? { ...v, value: formValue } : v));
|
|
166
122
|
setFormValue('');
|
|
167
123
|
setEditingName(null);
|
|
@@ -175,16 +131,7 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
|
|
|
175
131
|
if (!deletingName) return;
|
|
176
132
|
setErrorMessage(null);
|
|
177
133
|
try {
|
|
178
|
-
|
|
179
|
-
method: 'DELETE',
|
|
180
|
-
headers: { 'Content-Type': 'application/json' },
|
|
181
|
-
body: JSON.stringify({ name: deletingName, file: currentFile }),
|
|
182
|
-
});
|
|
183
|
-
if (!res.ok) {
|
|
184
|
-
const data = await res.json();
|
|
185
|
-
setErrorMessage(data.error || 'Failed to delete variable');
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
134
|
+
await invoke('db_delete_env_var', { file: currentFile, key: deletingName });
|
|
188
135
|
setEnvVars(envVars.filter(v => v.name !== deletingName));
|
|
189
136
|
setDeletingName(null);
|
|
190
137
|
showSuccess('Variable deleted successfully');
|
|
@@ -1,24 +1,37 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
|
|
3
2
|
import { useState } from 'react';
|
|
3
|
+
import { invoke } from '@/lib/tauri';
|
|
4
4
|
import { Input } from '@/components/ui/Input';
|
|
5
5
|
import { Button } from '@/components/ui/Button';
|
|
6
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
6
7
|
|
|
7
8
|
interface MainBranchInfo {
|
|
8
9
|
branch: string;
|
|
9
10
|
source: 'configured' | 'detected';
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
const CLAUDE_MODELS = [
|
|
14
|
+
{ id: 'default', label: 'Default', description: 'Opus 4.6', value: null },
|
|
15
|
+
{ id: 'sonnet', label: 'Sonnet', description: 'Sonnet 4.6', value: 'sonnet' },
|
|
16
|
+
{ id: 'haiku', label: 'Haiku', description: 'Haiku 4.5', value: 'haiku' },
|
|
17
|
+
];
|
|
18
|
+
|
|
12
19
|
interface GeneralSectionProps {
|
|
13
20
|
initialMainBranch: MainBranchInfo;
|
|
21
|
+
initialClaudeModel: string | null;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
export function GeneralSection({ initialMainBranch }: GeneralSectionProps) {
|
|
24
|
+
export function GeneralSection({ initialMainBranch, initialClaudeModel }: GeneralSectionProps) {
|
|
17
25
|
const [mainBranch, setMainBranch] = useState(initialMainBranch);
|
|
18
26
|
const [inputValue, setInputValue] = useState(initialMainBranch.branch);
|
|
19
27
|
const [isEditing, setIsEditing] = useState(false);
|
|
20
28
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
21
29
|
|
|
30
|
+
// Claude model state
|
|
31
|
+
const [claudeModel, setClaudeModel] = useState<string | null>(initialClaudeModel);
|
|
32
|
+
const [selectedModel, setSelectedModel] = useState<string | null>(initialClaudeModel);
|
|
33
|
+
const [isEditingModel, setIsEditingModel] = useState(false);
|
|
34
|
+
|
|
22
35
|
const showSuccess = (message: string) => {
|
|
23
36
|
setSuccessMessage(message);
|
|
24
37
|
setTimeout(() => setSuccessMessage(null), 3000);
|
|
@@ -28,19 +41,16 @@ export function GeneralSection({ initialMainBranch }: GeneralSectionProps) {
|
|
|
28
41
|
const trimmed = inputValue.trim();
|
|
29
42
|
const value = trimmed === '' ? null : trimmed;
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
if (res.ok) {
|
|
38
|
-
const data = await res.json();
|
|
39
|
-
setMainBranch(data.mainBranch);
|
|
40
|
-
setInputValue(data.mainBranch.branch);
|
|
41
|
-
setIsEditing(false);
|
|
42
|
-
showSuccess(value ? 'Main branch updated' : 'Reset to auto-detect');
|
|
44
|
+
if (value) {
|
|
45
|
+
await invoke('db_set_main_branch', { branch: value });
|
|
46
|
+
} else {
|
|
47
|
+
await invoke('db_reset_main_branch');
|
|
43
48
|
}
|
|
49
|
+
const branch = await dataBridge.getMainBranch();
|
|
50
|
+
setMainBranch({ branch, source: value ? 'configured' : 'detected' });
|
|
51
|
+
setInputValue(branch);
|
|
52
|
+
setIsEditing(false);
|
|
53
|
+
showSuccess(value ? 'Main branch updated' : 'Reset to auto-detect');
|
|
44
54
|
};
|
|
45
55
|
|
|
46
56
|
const handleCancel = () => {
|
|
@@ -49,21 +59,42 @@ export function GeneralSection({ initialMainBranch }: GeneralSectionProps) {
|
|
|
49
59
|
};
|
|
50
60
|
|
|
51
61
|
const handleReset = async () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
await invoke('db_reset_main_branch');
|
|
63
|
+
const branch = await dataBridge.getMainBranch();
|
|
64
|
+
setMainBranch({ branch, source: 'detected' });
|
|
65
|
+
setInputValue(branch);
|
|
66
|
+
setIsEditing(false);
|
|
67
|
+
showSuccess('Reset to auto-detect');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Claude model handlers
|
|
71
|
+
const handleModelSave = async () => {
|
|
72
|
+
if (selectedModel) {
|
|
73
|
+
await dataBridge.setClaudeModel(selectedModel);
|
|
74
|
+
} else {
|
|
75
|
+
await dataBridge.resetClaudeModel();
|
|
64
76
|
}
|
|
77
|
+
setClaudeModel(selectedModel);
|
|
78
|
+
setIsEditingModel(false);
|
|
79
|
+
const modelLabel = CLAUDE_MODELS.find(m => m.value === selectedModel)?.label || 'Default';
|
|
80
|
+
showSuccess(`Model set to ${modelLabel}`);
|
|
65
81
|
};
|
|
66
82
|
|
|
83
|
+
const handleModelCancel = () => {
|
|
84
|
+
setSelectedModel(claudeModel);
|
|
85
|
+
setIsEditingModel(false);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleModelReset = async () => {
|
|
89
|
+
await dataBridge.resetClaudeModel();
|
|
90
|
+
setClaudeModel(null);
|
|
91
|
+
setSelectedModel(null);
|
|
92
|
+
setIsEditingModel(false);
|
|
93
|
+
showSuccess('Model reset to default');
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const currentModel = CLAUDE_MODELS.find(m => m.value === claudeModel) || CLAUDE_MODELS[0];
|
|
97
|
+
|
|
67
98
|
return (
|
|
68
99
|
<section id="general">
|
|
69
100
|
<div className="flex items-center justify-between mb-6">
|
|
@@ -136,6 +167,86 @@ export function GeneralSection({ initialMainBranch }: GeneralSectionProps) {
|
|
|
136
167
|
</div>
|
|
137
168
|
)}
|
|
138
169
|
</div>
|
|
170
|
+
|
|
171
|
+
{/* Claude Code Model Setting */}
|
|
172
|
+
<div className="p-6 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg mt-4">
|
|
173
|
+
<div className="flex items-center justify-between">
|
|
174
|
+
<div>
|
|
175
|
+
<label className="block text-base font-medium text-zinc-900 dark:text-zinc-100">
|
|
176
|
+
Claude Code Model
|
|
177
|
+
</label>
|
|
178
|
+
<p className="text-base text-zinc-500 dark:text-zinc-400 mt-1">
|
|
179
|
+
The model used for Claude Code sessions in this project.
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
{!isEditingModel && (
|
|
183
|
+
<div className="flex items-center gap-3">
|
|
184
|
+
<span className="text-base text-zinc-900 dark:text-zinc-100">
|
|
185
|
+
{currentModel.label}
|
|
186
|
+
</span>
|
|
187
|
+
<span className="px-2 py-1 text-xs rounded bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400">
|
|
188
|
+
{claudeModel ? 'configured' : 'default'}
|
|
189
|
+
</span>
|
|
190
|
+
<Button
|
|
191
|
+
onClick={() => {
|
|
192
|
+
setSelectedModel(claudeModel);
|
|
193
|
+
setIsEditingModel(true);
|
|
194
|
+
}}
|
|
195
|
+
variant="ghost"
|
|
196
|
+
size="sm"
|
|
197
|
+
>
|
|
198
|
+
Edit
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
{isEditingModel && (
|
|
205
|
+
<div className="mt-4 space-y-4">
|
|
206
|
+
<div className="space-y-2">
|
|
207
|
+
{CLAUDE_MODELS.map((model) => (
|
|
208
|
+
<label
|
|
209
|
+
key={model.id}
|
|
210
|
+
className={`flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-colors duration-200 ${
|
|
211
|
+
selectedModel === model.value
|
|
212
|
+
? 'bg-zinc-200 dark:bg-zinc-700'
|
|
213
|
+
: 'hover:bg-zinc-100 dark:hover:bg-zinc-700/50'
|
|
214
|
+
}`}
|
|
215
|
+
>
|
|
216
|
+
<input
|
|
217
|
+
type="radio"
|
|
218
|
+
name="claude-model"
|
|
219
|
+
checked={selectedModel === model.value}
|
|
220
|
+
onChange={() => setSelectedModel(model.value)}
|
|
221
|
+
className="w-4 h-4 accent-[#819D9F]"
|
|
222
|
+
/>
|
|
223
|
+
<div>
|
|
224
|
+
<span className="text-base font-medium text-zinc-900 dark:text-zinc-100">
|
|
225
|
+
{model.label}
|
|
226
|
+
</span>
|
|
227
|
+
<span className="text-sm text-zinc-500 dark:text-zinc-400 ml-2">
|
|
228
|
+
{model.description}
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
231
|
+
</label>
|
|
232
|
+
))}
|
|
233
|
+
</div>
|
|
234
|
+
<div className="flex gap-3">
|
|
235
|
+
<Button onClick={handleModelSave} size="sm">
|
|
236
|
+
Save
|
|
237
|
+
</Button>
|
|
238
|
+
{claudeModel && (
|
|
239
|
+
<Button onClick={handleModelReset} variant="ghost" size="sm">
|
|
240
|
+
Reset to Default
|
|
241
|
+
</Button>
|
|
242
|
+
)}
|
|
243
|
+
<Button onClick={handleModelCancel} variant="ghost" size="sm">
|
|
244
|
+
Cancel
|
|
245
|
+
</Button>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
139
250
|
</section>
|
|
140
251
|
);
|
|
141
252
|
}
|