chronicle-ai 0.0.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/README.md +132 -0
- package/package.json +86 -0
- package/packages/README.md +139 -0
- package/packages/cli/README.md +92 -0
- package/packages/cli/bin/chronicle.js +2 -0
- package/packages/cli/dist/commands/add.d.ts +2 -0
- package/packages/cli/dist/commands/add.d.ts.map +1 -0
- package/packages/cli/dist/commands/add.js +82 -0
- package/packages/cli/dist/commands/add.js.map +1 -0
- package/packages/cli/dist/commands/diff.d.ts +11 -0
- package/packages/cli/dist/commands/diff.d.ts.map +1 -0
- package/packages/cli/dist/commands/diff.js +164 -0
- package/packages/cli/dist/commands/diff.js.map +1 -0
- package/packages/cli/dist/commands/init.d.ts +2 -0
- package/packages/cli/dist/commands/init.d.ts.map +1 -0
- package/packages/cli/dist/commands/init.js +54 -0
- package/packages/cli/dist/commands/init.js.map +1 -0
- package/packages/cli/dist/commands/list.d.ts +2 -0
- package/packages/cli/dist/commands/list.d.ts.map +1 -0
- package/packages/cli/dist/commands/list.js +62 -0
- package/packages/cli/dist/commands/list.js.map +1 -0
- package/packages/cli/dist/commands/log.d.ts +4 -0
- package/packages/cli/dist/commands/log.d.ts.map +1 -0
- package/packages/cli/dist/commands/log.js +223 -0
- package/packages/cli/dist/commands/log.js.map +1 -0
- package/packages/cli/dist/commands/pause.d.ts +3 -0
- package/packages/cli/dist/commands/pause.d.ts.map +1 -0
- package/packages/cli/dist/commands/pause.js +49 -0
- package/packages/cli/dist/commands/pause.js.map +1 -0
- package/packages/cli/dist/commands/queue.d.ts +2 -0
- package/packages/cli/dist/commands/queue.d.ts.map +1 -0
- package/packages/cli/dist/commands/queue.js +96 -0
- package/packages/cli/dist/commands/queue.js.map +1 -0
- package/packages/cli/dist/commands/remove.d.ts +2 -0
- package/packages/cli/dist/commands/remove.d.ts.map +1 -0
- package/packages/cli/dist/commands/remove.js +80 -0
- package/packages/cli/dist/commands/remove.js.map +1 -0
- package/packages/cli/dist/commands/reset.d.ts +5 -0
- package/packages/cli/dist/commands/reset.d.ts.map +1 -0
- package/packages/cli/dist/commands/reset.js +90 -0
- package/packages/cli/dist/commands/reset.js.map +1 -0
- package/packages/cli/dist/commands/restart.d.ts +2 -0
- package/packages/cli/dist/commands/restart.d.ts.map +1 -0
- package/packages/cli/dist/commands/restart.js +72 -0
- package/packages/cli/dist/commands/restart.js.map +1 -0
- package/packages/cli/dist/commands/start.d.ts +2 -0
- package/packages/cli/dist/commands/start.d.ts.map +1 -0
- package/packages/cli/dist/commands/start.js +127 -0
- package/packages/cli/dist/commands/start.js.map +1 -0
- package/packages/cli/dist/commands/status.d.ts +2 -0
- package/packages/cli/dist/commands/status.d.ts.map +1 -0
- package/packages/cli/dist/commands/status.js +181 -0
- package/packages/cli/dist/commands/status.js.map +1 -0
- package/packages/cli/dist/commands/stop.d.ts +2 -0
- package/packages/cli/dist/commands/stop.d.ts.map +1 -0
- package/packages/cli/dist/commands/stop.js +64 -0
- package/packages/cli/dist/commands/stop.js.map +1 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +85 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/utils/paths.d.ts +26 -0
- package/packages/cli/dist/utils/paths.d.ts.map +1 -0
- package/packages/cli/dist/utils/paths.js +183 -0
- package/packages/cli/dist/utils/paths.js.map +1 -0
- package/packages/cli/package.json +25 -0
- package/packages/daemon/README.md +83 -0
- package/packages/daemon/dist/ai_test.d.ts +2 -0
- package/packages/daemon/dist/ai_test.d.ts.map +1 -0
- package/packages/daemon/dist/ai_test.js +4 -0
- package/packages/daemon/dist/ai_test.js.map +1 -0
- package/packages/daemon/dist/index.d.ts +2 -0
- package/packages/daemon/dist/index.d.ts.map +1 -0
- package/packages/daemon/dist/index.js +147 -0
- package/packages/daemon/dist/index.js.map +1 -0
- package/packages/daemon/dist/jobs/AIProcessor.d.ts +6 -0
- package/packages/daemon/dist/jobs/AIProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/AIProcessor.js +58 -0
- package/packages/daemon/dist/jobs/AIProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/DocProcessor.d.ts +8 -0
- package/packages/daemon/dist/jobs/DocProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/DocProcessor.js +336 -0
- package/packages/daemon/dist/jobs/DocProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/FileProcessor.d.ts +7 -0
- package/packages/daemon/dist/jobs/FileProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/FileProcessor.js +29 -0
- package/packages/daemon/dist/jobs/FileProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/SystemProcessor.d.ts +13 -0
- package/packages/daemon/dist/jobs/SystemProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/SystemProcessor.js +38 -0
- package/packages/daemon/dist/jobs/SystemProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.d.ts +21 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.js +222 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.js.map +1 -0
- package/packages/daemon/dist/services/AIService.d.ts +90 -0
- package/packages/daemon/dist/services/AIService.d.ts.map +1 -0
- package/packages/daemon/dist/services/AIService.js +451 -0
- package/packages/daemon/dist/services/AIService.js.map +1 -0
- package/packages/daemon/dist/services/ConfigService.d.ts +30 -0
- package/packages/daemon/dist/services/ConfigService.d.ts.map +1 -0
- package/packages/daemon/dist/services/ConfigService.js +69 -0
- package/packages/daemon/dist/services/ConfigService.js.map +1 -0
- package/packages/daemon/dist/services/DatabaseService.d.ts +204 -0
- package/packages/daemon/dist/services/DatabaseService.d.ts.map +1 -0
- package/packages/daemon/dist/services/DatabaseService.js +692 -0
- package/packages/daemon/dist/services/DatabaseService.js.map +1 -0
- package/packages/daemon/dist/services/GitService.d.ts +12 -0
- package/packages/daemon/dist/services/GitService.d.ts.map +1 -0
- package/packages/daemon/dist/services/GitService.js +68 -0
- package/packages/daemon/dist/services/GitService.js.map +1 -0
- package/packages/daemon/dist/services/QueueService.d.ts +16 -0
- package/packages/daemon/dist/services/QueueService.d.ts.map +1 -0
- package/packages/daemon/dist/services/QueueService.js +87 -0
- package/packages/daemon/dist/services/QueueService.js.map +1 -0
- package/packages/daemon/dist/services/TriggerService.d.ts +37 -0
- package/packages/daemon/dist/services/TriggerService.d.ts.map +1 -0
- package/packages/daemon/dist/services/TriggerService.js +150 -0
- package/packages/daemon/dist/services/TriggerService.js.map +1 -0
- package/packages/daemon/dist/services/WatcherService.d.ts +12 -0
- package/packages/daemon/dist/services/WatcherService.d.ts.map +1 -0
- package/packages/daemon/dist/services/WatcherService.js +77 -0
- package/packages/daemon/dist/services/WatcherService.js.map +1 -0
- package/packages/daemon/dist/services/index.d.ts +2 -0
- package/packages/daemon/dist/services/index.d.ts.map +1 -0
- package/packages/daemon/dist/services/index.js +18 -0
- package/packages/daemon/dist/services/index.js.map +1 -0
- package/packages/daemon/dist/test-ignore.js +0 -0
- package/packages/daemon/package.json +28 -0
- package/packages/ui/app/actions.ts +73 -0
- package/packages/ui/app/api/ai-activity/route.ts +14 -0
- package/packages/ui/app/globals.css +98 -0
- package/packages/ui/app/layout.tsx +29 -0
- package/packages/ui/app/page.tsx +109 -0
- package/packages/ui/components/AutoRefresh.tsx +18 -0
- package/packages/ui/components/DocumentationViewer.tsx +276 -0
- package/packages/ui/components/FileList.tsx +69 -0
- package/packages/ui/components/HeaderWithThinking.tsx +102 -0
- package/packages/ui/components/JobQueue.tsx +194 -0
- package/packages/ui/components/JobQueueWrapper.tsx +31 -0
- package/packages/ui/components/MermaidInit.tsx +24 -0
- package/packages/ui/components/ProjectContentArea.tsx +186 -0
- package/packages/ui/components/ProjectList.tsx +136 -0
- package/packages/ui/components/ThinkingDrawer.tsx +377 -0
- package/packages/ui/components/ThinkingPanel.tsx +63 -0
- package/packages/ui/components/TriggerSettings.tsx +185 -0
- package/packages/ui/components/VersionSelector.tsx +132 -0
- package/packages/ui/lib/db.ts +521 -0
- package/packages/ui/next-env.d.ts +5 -0
- package/packages/ui/next.config.js +4 -0
- package/packages/ui/package.json +32 -0
- package/packages/ui/postcss.config.js +6 -0
- package/packages/ui/public/logo.png +0 -0
- package/packages/ui/tailwind.config.ts +32 -0
- package/packages/ui/tsconfig.json +40 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Brain, X, Activity, Clock, Sparkles, AlertCircle, ChevronDown, ChevronRight, Wrench, CheckCircle2, XCircle } from 'lucide-react';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
|
|
7
|
+
interface AIActivity {
|
|
8
|
+
id: number;
|
|
9
|
+
project_id: number | null;
|
|
10
|
+
version_id: number | null;
|
|
11
|
+
activity_type: 'analyze' | 'generate' | 'plan' | 'embed' | 'action' | 'other';
|
|
12
|
+
prompt_summary: string | null;
|
|
13
|
+
response_preview: string | null;
|
|
14
|
+
thinking_summary: string | null;
|
|
15
|
+
tokens_used: number | null;
|
|
16
|
+
duration_ms: number | null;
|
|
17
|
+
status: 'running' | 'completed' | 'error';
|
|
18
|
+
created_at: number;
|
|
19
|
+
project_path?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
activities: AIActivity[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const activityIcons = {
|
|
29
|
+
analyze: Activity,
|
|
30
|
+
generate: Sparkles,
|
|
31
|
+
plan: Brain,
|
|
32
|
+
embed: Brain,
|
|
33
|
+
action: Wrench,
|
|
34
|
+
other: Brain
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const activityColors = {
|
|
38
|
+
analyze: 'text-blue-500',
|
|
39
|
+
generate: 'text-purple-500',
|
|
40
|
+
plan: 'text-indigo-500',
|
|
41
|
+
embed: 'text-gray-500',
|
|
42
|
+
action: 'text-orange-500',
|
|
43
|
+
other: 'text-gray-500'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const statusColors = {
|
|
47
|
+
running: 'text-blue-500 animate-pulse',
|
|
48
|
+
completed: 'text-green-500',
|
|
49
|
+
error: 'text-red-500'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function formatTimeAgo(timestamp: number): string {
|
|
53
|
+
const diff = Date.now() - timestamp;
|
|
54
|
+
const seconds = Math.floor(diff / 1000);
|
|
55
|
+
const minutes = Math.floor(seconds / 60);
|
|
56
|
+
const hours = Math.floor(minutes / 60);
|
|
57
|
+
const days = Math.floor(hours / 24);
|
|
58
|
+
|
|
59
|
+
if (days > 0) return `${days}d ago`;
|
|
60
|
+
if (hours > 0) return `${hours}h ago`;
|
|
61
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
62
|
+
if (seconds > 0) return `${seconds}s ago`;
|
|
63
|
+
return 'just now';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ActivityCard({ activity, onKill }: { activity: AIActivity; onKill?: (id: number) => void }) {
|
|
67
|
+
const [expanded, setExpanded] = useState(false);
|
|
68
|
+
const [timeAgo, setTimeAgo] = useState<string>('');
|
|
69
|
+
const [isKilling, setIsKilling] = useState(false);
|
|
70
|
+
const [showConfirm, setShowConfirm] = useState(false);
|
|
71
|
+
const Icon = activityIcons[activity.activity_type] || Brain;
|
|
72
|
+
|
|
73
|
+
// Client-side only time formatting to avoid hydration mismatch
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
// created_at is in seconds (Unix timestamp), convert to milliseconds
|
|
76
|
+
const timestampMs = activity.created_at * 1000;
|
|
77
|
+
setTimeAgo(formatTimeAgo(timestampMs));
|
|
78
|
+
const interval = setInterval(() => {
|
|
79
|
+
setTimeAgo(formatTimeAgo(timestampMs));
|
|
80
|
+
}, 1000);
|
|
81
|
+
return () => clearInterval(interval);
|
|
82
|
+
}, [activity.created_at]);
|
|
83
|
+
|
|
84
|
+
const handleKillClick = (e: React.MouseEvent) => {
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
if (!onKill || isKilling) return;
|
|
87
|
+
setShowConfirm(true);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleKillConfirm = async () => {
|
|
91
|
+
if (!onKill || isKilling) return;
|
|
92
|
+
setShowConfirm(false);
|
|
93
|
+
setIsKilling(true);
|
|
94
|
+
try {
|
|
95
|
+
await onKill(activity.id);
|
|
96
|
+
} finally {
|
|
97
|
+
setIsKilling(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
{/* Confirmation Modal */}
|
|
104
|
+
{showConfirm && (
|
|
105
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60]" onClick={() => setShowConfirm(false)}>
|
|
106
|
+
<div className="bg-white rounded-lg shadow-xl p-4 max-w-sm mx-4" onClick={e => e.stopPropagation()}>
|
|
107
|
+
<h3 className="font-bold text-gray-800 mb-2 text-sm">Kill Activity?</h3>
|
|
108
|
+
<p className="text-xs text-gray-600 mb-4">
|
|
109
|
+
Cancel this running AI activity?
|
|
110
|
+
<br />
|
|
111
|
+
<span className="text-[10px] text-gray-400">This action cannot be undone.</span>
|
|
112
|
+
</p>
|
|
113
|
+
<div className="flex justify-end gap-2">
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => setShowConfirm(false)}
|
|
116
|
+
className="px-3 py-1.5 text-xs text-gray-600 hover:bg-gray-100 rounded transition-colors"
|
|
117
|
+
>
|
|
118
|
+
Cancel
|
|
119
|
+
</button>
|
|
120
|
+
<button
|
|
121
|
+
onClick={handleKillConfirm}
|
|
122
|
+
disabled={isKilling}
|
|
123
|
+
className={clsx(
|
|
124
|
+
"px-3 py-1.5 text-xs bg-red-500 text-white rounded hover:bg-red-600 transition-colors",
|
|
125
|
+
isKilling && "opacity-50 cursor-wait"
|
|
126
|
+
)}
|
|
127
|
+
>
|
|
128
|
+
{isKilling ? 'Killing...' : 'Kill'}
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
<div className="border-b border-gray-100 last:border-0">
|
|
136
|
+
<div className="flex items-start gap-2 p-3 hover:bg-gray-50 transition-colors">
|
|
137
|
+
<button
|
|
138
|
+
onClick={() => setExpanded(!expanded)}
|
|
139
|
+
className="flex-1 text-left"
|
|
140
|
+
>
|
|
141
|
+
<div className="flex items-start gap-2">
|
|
142
|
+
<div className={clsx(
|
|
143
|
+
"mt-0.5",
|
|
144
|
+
statusColors[activity.status],
|
|
145
|
+
activity.status === 'completed' && activityColors[activity.activity_type]
|
|
146
|
+
)}>
|
|
147
|
+
<Icon size={14} />
|
|
148
|
+
</div>
|
|
149
|
+
<div className="flex-1 min-w-0">
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<span className="text-xs font-medium text-gray-700 capitalize">
|
|
152
|
+
{activity.activity_type}
|
|
153
|
+
</span>
|
|
154
|
+
<span className="text-[10px] text-gray-400">
|
|
155
|
+
{timeAgo || '...'}
|
|
156
|
+
</span>
|
|
157
|
+
{activity.duration_ms && (
|
|
158
|
+
<span className="text-[10px] text-gray-300">
|
|
159
|
+
{activity.duration_ms}ms
|
|
160
|
+
</span>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
{activity.prompt_summary && (
|
|
164
|
+
<p className="text-[11px] text-gray-500 truncate mt-0.5">
|
|
165
|
+
{activity.prompt_summary}
|
|
166
|
+
</p>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
{activity.thinking_summary && (
|
|
170
|
+
expanded ? <ChevronDown size={12} className="text-gray-400" />
|
|
171
|
+
: <ChevronRight size={12} className="text-gray-400" />
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</button>
|
|
175
|
+
{activity.status === 'running' && onKill && (
|
|
176
|
+
<button
|
|
177
|
+
onClick={handleKillClick}
|
|
178
|
+
disabled={isKilling}
|
|
179
|
+
className={clsx(
|
|
180
|
+
"p-1 rounded hover:bg-red-100 transition-colors",
|
|
181
|
+
isKilling && "opacity-50 cursor-wait"
|
|
182
|
+
)}
|
|
183
|
+
title="Kill this activity"
|
|
184
|
+
>
|
|
185
|
+
<XCircle size={14} className="text-red-500" />
|
|
186
|
+
</button>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{expanded && activity.thinking_summary && (
|
|
191
|
+
<div className="px-3 pb-3">
|
|
192
|
+
<div className="bg-purple-50 border border-purple-100 rounded p-2">
|
|
193
|
+
<div className="flex items-center gap-1 mb-1">
|
|
194
|
+
<Brain size={10} className="text-purple-500" />
|
|
195
|
+
<span className="text-[9px] font-bold text-purple-600 uppercase">Chain of Thought</span>
|
|
196
|
+
</div>
|
|
197
|
+
<p className="text-[11px] text-gray-700 font-mono whitespace-pre-wrap leading-relaxed">
|
|
198
|
+
{activity.thinking_summary}
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
{activity.response_preview && (
|
|
202
|
+
<div className="mt-2 bg-gray-50 border border-gray-100 rounded p-2">
|
|
203
|
+
<span className="text-[9px] font-bold text-gray-500 uppercase">Response</span>
|
|
204
|
+
<p className="text-[11px] text-gray-600 mt-0.5 line-clamp-3">
|
|
205
|
+
{activity.response_preview}
|
|
206
|
+
</p>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function ThinkingDrawer({ isOpen, onClose, activities }: Props) {
|
|
217
|
+
const runningCount = activities.filter(a => a.status === 'running').length;
|
|
218
|
+
const [isKillingAll, setIsKillingAll] = useState(false);
|
|
219
|
+
const [showKillAllConfirm, setShowKillAllConfirm] = useState(false);
|
|
220
|
+
|
|
221
|
+
const handleKillActivity = async (activityId: number) => {
|
|
222
|
+
try {
|
|
223
|
+
const { killAIActivityAction } = await import('../app/actions');
|
|
224
|
+
await killAIActivityAction(activityId);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('Failed to kill activity:', error);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const handleKillAllClick = () => {
|
|
231
|
+
if (isKillingAll) return;
|
|
232
|
+
setShowKillAllConfirm(true);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleKillAllConfirm = async () => {
|
|
236
|
+
if (isKillingAll) return;
|
|
237
|
+
setShowKillAllConfirm(false);
|
|
238
|
+
setIsKillingAll(true);
|
|
239
|
+
try {
|
|
240
|
+
const { killAllRunningAIActivitiesAction } = await import('../app/actions');
|
|
241
|
+
await killAllRunningAIActivitiesAction();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Failed to kill all activities:', error);
|
|
244
|
+
} finally {
|
|
245
|
+
setIsKillingAll(false);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
{/* Kill All Confirmation Modal */}
|
|
252
|
+
{showKillAllConfirm && (
|
|
253
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60]" onClick={() => setShowKillAllConfirm(false)}>
|
|
254
|
+
<div className="bg-white rounded-lg shadow-xl p-4 max-w-sm mx-4" onClick={e => e.stopPropagation()}>
|
|
255
|
+
<h3 className="font-bold text-gray-800 mb-2 text-sm">Kill All Activities?</h3>
|
|
256
|
+
<p className="text-xs text-gray-600 mb-4">
|
|
257
|
+
Cancel all {runningCount} running AI {runningCount === 1 ? 'activity' : 'activities'}?
|
|
258
|
+
<br />
|
|
259
|
+
<span className="text-[10px] text-gray-400">This action cannot be undone.</span>
|
|
260
|
+
</p>
|
|
261
|
+
<div className="flex justify-end gap-2">
|
|
262
|
+
<button
|
|
263
|
+
onClick={() => setShowKillAllConfirm(false)}
|
|
264
|
+
className="px-3 py-1.5 text-xs text-gray-600 hover:bg-gray-100 rounded transition-colors"
|
|
265
|
+
>
|
|
266
|
+
Cancel
|
|
267
|
+
</button>
|
|
268
|
+
<button
|
|
269
|
+
onClick={handleKillAllConfirm}
|
|
270
|
+
disabled={isKillingAll}
|
|
271
|
+
className={clsx(
|
|
272
|
+
"px-3 py-1.5 text-xs bg-red-500 text-white rounded hover:bg-red-600 transition-colors",
|
|
273
|
+
isKillingAll && "opacity-50 cursor-wait"
|
|
274
|
+
)}
|
|
275
|
+
>
|
|
276
|
+
{isKillingAll ? 'Killing...' : 'Kill All'}
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
{/* Backdrop */}
|
|
284
|
+
{isOpen && (
|
|
285
|
+
<div
|
|
286
|
+
className="fixed inset-0 bg-black/20 z-40"
|
|
287
|
+
onClick={onClose}
|
|
288
|
+
/>
|
|
289
|
+
)}
|
|
290
|
+
|
|
291
|
+
{/* Drawer */}
|
|
292
|
+
<div className={clsx(
|
|
293
|
+
"fixed top-0 right-0 h-full w-96 bg-white shadow-2xl z-50",
|
|
294
|
+
"transform transition-transform duration-300 ease-in-out",
|
|
295
|
+
isOpen ? "translate-x-0" : "translate-x-full"
|
|
296
|
+
)}>
|
|
297
|
+
{/* Header */}
|
|
298
|
+
<div className="h-12 border-b border-gray-200 flex items-center justify-between px-4 bg-gradient-to-r from-purple-50 to-blue-50">
|
|
299
|
+
<div className="flex items-center gap-2">
|
|
300
|
+
<Brain size={18} className="text-purple-600" />
|
|
301
|
+
<h2 className="font-bold text-sm text-gray-800">Thinking Platform</h2>
|
|
302
|
+
{runningCount > 0 && (
|
|
303
|
+
<>
|
|
304
|
+
<span className="px-1.5 py-0.5 bg-blue-100 text-blue-600 text-[9px] font-bold rounded animate-pulse">
|
|
305
|
+
{runningCount} ACTIVE
|
|
306
|
+
</span>
|
|
307
|
+
<button
|
|
308
|
+
onClick={handleKillAllClick}
|
|
309
|
+
disabled={isKillingAll}
|
|
310
|
+
className={clsx(
|
|
311
|
+
"px-2 py-0.5 text-[9px] font-bold rounded transition-colors",
|
|
312
|
+
"bg-red-100 text-red-600 hover:bg-red-200",
|
|
313
|
+
isKillingAll && "opacity-50 cursor-wait"
|
|
314
|
+
)}
|
|
315
|
+
title="Kill all running activities"
|
|
316
|
+
>
|
|
317
|
+
{isKillingAll ? 'KILLING...' : 'KILL ALL'}
|
|
318
|
+
</button>
|
|
319
|
+
</>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
<button
|
|
323
|
+
onClick={onClose}
|
|
324
|
+
className="p-1 hover:bg-gray-200 rounded transition-colors"
|
|
325
|
+
>
|
|
326
|
+
<X size={16} className="text-gray-500" />
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{/* Content */}
|
|
331
|
+
<div className="h-[calc(100%-48px)] overflow-y-auto">
|
|
332
|
+
{activities.length === 0 ? (
|
|
333
|
+
<div className="flex flex-col items-center justify-center h-full text-gray-400">
|
|
334
|
+
<Brain size={40} className="mb-3 opacity-50" />
|
|
335
|
+
<p className="text-sm">No AI activity yet</p>
|
|
336
|
+
<p className="text-xs mt-1">Run `chronicle init` or `chronicle diff`</p>
|
|
337
|
+
</div>
|
|
338
|
+
) : (
|
|
339
|
+
<div>
|
|
340
|
+
{activities.map((activity) => (
|
|
341
|
+
<ActivityCard
|
|
342
|
+
key={activity.id}
|
|
343
|
+
activity={activity}
|
|
344
|
+
onKill={handleKillActivity}
|
|
345
|
+
/>
|
|
346
|
+
))}
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
</>
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Toggle button component for the header
|
|
356
|
+
export function ThinkingToggle({ onClick, activityCount }: { onClick: () => void; activityCount: number }) {
|
|
357
|
+
return (
|
|
358
|
+
<button
|
|
359
|
+
onClick={onClick}
|
|
360
|
+
className={clsx(
|
|
361
|
+
"flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors",
|
|
362
|
+
activityCount > 0
|
|
363
|
+
? "bg-purple-100 text-purple-700 hover:bg-purple-200"
|
|
364
|
+
: "bg-gray-100 text-gray-500 hover:bg-gray-200"
|
|
365
|
+
)}
|
|
366
|
+
title="Open Thinking Platform"
|
|
367
|
+
>
|
|
368
|
+
<Brain size={14} />
|
|
369
|
+
<span className="hidden md:inline">Thinking</span>
|
|
370
|
+
{activityCount > 0 && (
|
|
371
|
+
<span className="bg-purple-500 text-white text-[9px] px-1 rounded">
|
|
372
|
+
{activityCount}
|
|
373
|
+
</span>
|
|
374
|
+
)}
|
|
375
|
+
</button>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Brain, ChevronDown, ChevronUp } from 'lucide-react';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
thinkingSummary: string | null;
|
|
9
|
+
docPath: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ThinkingPanel({ thinkingSummary, docPath }: Props) {
|
|
13
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="border-t border-gray-200 bg-gradient-to-r from-purple-50 to-blue-50">
|
|
17
|
+
<button
|
|
18
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
19
|
+
className="w-full px-4 py-2 flex items-center gap-2 hover:bg-white/50 transition-colors"
|
|
20
|
+
>
|
|
21
|
+
<Brain size={14} className="text-purple-500" />
|
|
22
|
+
<span className="text-[11px] font-medium text-purple-700">
|
|
23
|
+
Chain of Thought
|
|
24
|
+
</span>
|
|
25
|
+
<span className="text-[10px] text-purple-400 flex-1 text-left truncate">
|
|
26
|
+
{docPath.split('/').pop()}
|
|
27
|
+
</span>
|
|
28
|
+
{thinkingSummary ? (
|
|
29
|
+
<span className="text-[9px] bg-purple-100 text-purple-600 px-1.5 rounded">
|
|
30
|
+
Available
|
|
31
|
+
</span>
|
|
32
|
+
) : (
|
|
33
|
+
<span className="text-[9px] bg-gray-100 text-gray-400 px-1.5 rounded">
|
|
34
|
+
Not yet captured
|
|
35
|
+
</span>
|
|
36
|
+
)}
|
|
37
|
+
{isExpanded ? (
|
|
38
|
+
<ChevronUp size={14} className="text-purple-400" />
|
|
39
|
+
) : (
|
|
40
|
+
<ChevronDown size={14} className="text-purple-400" />
|
|
41
|
+
)}
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
{isExpanded && (
|
|
45
|
+
<div className="px-4 pb-4">
|
|
46
|
+
<div className={clsx(
|
|
47
|
+
"p-3 rounded-lg bg-white border border-purple-100",
|
|
48
|
+
"text-xs text-gray-700 leading-relaxed",
|
|
49
|
+
"max-h-[300px] overflow-y-auto",
|
|
50
|
+
"font-mono whitespace-pre-wrap"
|
|
51
|
+
)}>
|
|
52
|
+
{thinkingSummary || (
|
|
53
|
+
<span className="text-gray-400 italic">
|
|
54
|
+
Chain of thought will appear here after running `chronicle init` or `chronicle diff`
|
|
55
|
+
with the new versioning system.
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { Settings, GitCommit, Timer, Save, RefreshCw, Edit3, X } from 'lucide-react';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
import { getConfigAction, saveConfigAction } from '../app/actions';
|
|
7
|
+
|
|
8
|
+
interface TriggerConfig {
|
|
9
|
+
on_change: { enabled: boolean; debounce_ms: number };
|
|
10
|
+
on_commit: { enabled: boolean };
|
|
11
|
+
schedule: { enabled: boolean; interval_hours: number };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
config: TriggerConfig;
|
|
16
|
+
projectPath: string;
|
|
17
|
+
onSave?: (config: TriggerConfig) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TriggerSettings({ config, projectPath, onSave }: Props) {
|
|
21
|
+
const [triggers, setTriggers] = useState<TriggerConfig>(config);
|
|
22
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
23
|
+
const [saving, setSaving] = useState(false);
|
|
24
|
+
const [showEditor, setShowEditor] = useState(false);
|
|
25
|
+
const [configContent, setConfigContent] = useState('');
|
|
26
|
+
const [editorError, setEditorError] = useState('');
|
|
27
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
31
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
32
|
+
if (!showEditor) setIsOpen(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
36
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
37
|
+
}, [showEditor]);
|
|
38
|
+
|
|
39
|
+
const handleToggle = (key: 'on_change' | 'on_commit' | 'schedule') => {
|
|
40
|
+
setTriggers(prev => ({
|
|
41
|
+
...prev,
|
|
42
|
+
[key]: { ...prev[key], enabled: !prev[key].enabled }
|
|
43
|
+
}));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleSave = async () => {
|
|
47
|
+
setSaving(true);
|
|
48
|
+
if (onSave) onSave(triggers);
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
setSaving(false);
|
|
51
|
+
setIsOpen(false);
|
|
52
|
+
}, 500);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const openEditor = async () => {
|
|
56
|
+
const content = await getConfigAction(projectPath);
|
|
57
|
+
if (content) {
|
|
58
|
+
setConfigContent(content);
|
|
59
|
+
setShowEditor(true);
|
|
60
|
+
setEditorError('');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const saveEditorContent = async () => {
|
|
65
|
+
try {
|
|
66
|
+
JSON.parse(configContent);
|
|
67
|
+
setEditorError('');
|
|
68
|
+
const success = await saveConfigAction(projectPath, configContent);
|
|
69
|
+
if (success) {
|
|
70
|
+
setShowEditor(false);
|
|
71
|
+
setIsOpen(false);
|
|
72
|
+
} else {
|
|
73
|
+
setEditorError('Failed to save');
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
setEditorError('Invalid JSON');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<div className="relative" ref={dropdownRef}>
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
85
|
+
className={clsx(
|
|
86
|
+
"flex items-center gap-1.5 px-2 py-1 text-xs rounded transition-colors",
|
|
87
|
+
isOpen ? "bg-blue-100 text-blue-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<Settings size={12} />
|
|
91
|
+
Triggers
|
|
92
|
+
</button>
|
|
93
|
+
|
|
94
|
+
{isOpen && !showEditor && (
|
|
95
|
+
<div className="absolute right-0 top-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl p-3 min-w-[280px] z-50">
|
|
96
|
+
<div className="flex items-center justify-between mb-3">
|
|
97
|
+
<h3 className="font-semibold text-sm text-gray-800 flex items-center gap-1.5">
|
|
98
|
+
<Settings size={14} />
|
|
99
|
+
Trigger Settings
|
|
100
|
+
</h3>
|
|
101
|
+
<button onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-gray-600 text-xs">✕</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
<div className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
|
106
|
+
<div className="flex items-center gap-2">
|
|
107
|
+
<RefreshCw size={14} className={clsx(triggers.on_change.enabled ? 'text-blue-500' : 'text-gray-400')} />
|
|
108
|
+
<div>
|
|
109
|
+
<div className="text-xs font-medium text-gray-700">On File Change</div>
|
|
110
|
+
<div className="text-[10px] text-gray-500">Debounce: {triggers.on_change.debounce_ms / 1000}s</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<label className="relative inline-flex items-center cursor-pointer">
|
|
114
|
+
<input type="checkbox" checked={triggers.on_change.enabled} onChange={() => handleToggle('on_change')} className="sr-only peer" />
|
|
115
|
+
<div className="w-9 h-5 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-500"></div>
|
|
116
|
+
</label>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
|
120
|
+
<div className="flex items-center gap-2">
|
|
121
|
+
<GitCommit size={14} className={clsx(triggers.on_commit.enabled ? 'text-green-500' : 'text-gray-400')} />
|
|
122
|
+
<div>
|
|
123
|
+
<div className="text-xs font-medium text-gray-700">On Git Commit</div>
|
|
124
|
+
<div className="text-[10px] text-gray-500">Polls every 30s</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<label className="relative inline-flex items-center cursor-pointer">
|
|
128
|
+
<input type="checkbox" checked={triggers.on_commit.enabled} onChange={() => handleToggle('on_commit')} className="sr-only peer" />
|
|
129
|
+
<div className="w-9 h-5 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-500"></div>
|
|
130
|
+
</label>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
|
134
|
+
<div className="flex items-center gap-2">
|
|
135
|
+
<Timer size={14} className={clsx(triggers.schedule.enabled ? 'text-purple-500' : 'text-gray-400')} />
|
|
136
|
+
<div>
|
|
137
|
+
<div className="text-xs font-medium text-gray-700">Scheduled</div>
|
|
138
|
+
<div className="text-[10px] text-gray-500">Every {triggers.schedule.interval_hours}h</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<label className="relative inline-flex items-center cursor-pointer">
|
|
142
|
+
<input type="checkbox" checked={triggers.schedule.enabled} onChange={() => handleToggle('schedule')} className="sr-only peer" />
|
|
143
|
+
<div className="w-9 h-5 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-purple-500"></div>
|
|
144
|
+
</label>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="mt-3 pt-2 border-t border-gray-200 flex justify-between items-center">
|
|
149
|
+
<button onClick={openEditor} className="flex items-center gap-1 px-2 py-1 text-[10px] text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded">
|
|
150
|
+
<Edit3 size={10} />
|
|
151
|
+
Edit JSON
|
|
152
|
+
</button>
|
|
153
|
+
<button onClick={handleSave} disabled={saving} className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50">
|
|
154
|
+
<Save size={12} />
|
|
155
|
+
{saving ? 'Saving...' : 'Save'}
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{showEditor && (
|
|
163
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[100]" onClick={() => setShowEditor(false)}>
|
|
164
|
+
<div className="bg-white rounded-lg shadow-2xl w-[500px] max-h-[80vh] flex flex-col" onClick={e => e.stopPropagation()}>
|
|
165
|
+
<div className="flex items-center justify-between p-3 border-b">
|
|
166
|
+
<h3 className="font-semibold text-sm">.chronicle.json</h3>
|
|
167
|
+
<button onClick={() => setShowEditor(false)} className="p-1 hover:bg-gray-100 rounded"><X size={16} /></button>
|
|
168
|
+
</div>
|
|
169
|
+
<textarea
|
|
170
|
+
value={configContent}
|
|
171
|
+
onChange={e => setConfigContent(e.target.value)}
|
|
172
|
+
className="flex-1 p-3 font-mono text-xs resize-none focus:outline-none min-h-[300px]"
|
|
173
|
+
spellCheck={false}
|
|
174
|
+
/>
|
|
175
|
+
{editorError && <div className="px-3 py-1 text-xs text-red-500">{editorError}</div>}
|
|
176
|
+
<div className="flex justify-end gap-2 p-3 border-t">
|
|
177
|
+
<button onClick={() => setShowEditor(false)} className="px-3 py-1.5 text-xs text-gray-600 hover:bg-gray-100 rounded">Cancel</button>
|
|
178
|
+
<button onClick={saveEditorContent} className="px-3 py-1.5 text-xs bg-blue-500 text-white rounded hover:bg-blue-600">Save</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</>
|
|
184
|
+
);
|
|
185
|
+
}
|