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,73 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { enqueueJob, clearQueue, killCurrentJob, getProjectConfigRaw, saveProjectConfig, toggleProjectPaused, killAIActivity, killAllRunningAIActivities } from '../lib/db';
|
|
4
|
+
import { revalidatePath } from 'next/cache';
|
|
5
|
+
|
|
6
|
+
export async function togglePause(path: string, paused: boolean) {
|
|
7
|
+
toggleProjectPaused(path, paused);
|
|
8
|
+
revalidatePath('/');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function addProjectAction(formData: FormData) {
|
|
12
|
+
const projectPath = formData.get('path') as string;
|
|
13
|
+
|
|
14
|
+
if (!projectPath) return;
|
|
15
|
+
|
|
16
|
+
// Enqueue the job for the Daemon to pick up
|
|
17
|
+
enqueueJob('system:project_add', { path: projectPath, action: 'add' });
|
|
18
|
+
|
|
19
|
+
// Revalidate the page so the list updates eventually (polling handles it too, but this helps)
|
|
20
|
+
revalidatePath('/');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function removeProjectAction(projectPath: string) {
|
|
24
|
+
if (!projectPath) return;
|
|
25
|
+
|
|
26
|
+
// Enqueue the job for the Daemon to remove the project
|
|
27
|
+
enqueueJob('system:project_remove', { path: projectPath, action: 'remove' });
|
|
28
|
+
|
|
29
|
+
// Revalidate the page so the list updates
|
|
30
|
+
revalidatePath('/');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function clearQueueAction() {
|
|
34
|
+
clearQueue();
|
|
35
|
+
revalidatePath('/');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function killCurrentJobAction() {
|
|
39
|
+
killCurrentJob();
|
|
40
|
+
revalidatePath('/');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getConfigAction(projectPath: string): Promise<string | null> {
|
|
44
|
+
return getProjectConfigRaw(projectPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function saveConfigAction(projectPath: string, content: string): Promise<boolean> {
|
|
48
|
+
const success = saveProjectConfig(projectPath, content);
|
|
49
|
+
if (success) {
|
|
50
|
+
revalidatePath('/');
|
|
51
|
+
}
|
|
52
|
+
return success;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function triggerDocUpdate(projectPath: string) {
|
|
56
|
+
enqueueJob('doc:update', {
|
|
57
|
+
projectPath,
|
|
58
|
+
triggerType: 'manual',
|
|
59
|
+
incremental: true
|
|
60
|
+
});
|
|
61
|
+
revalidatePath('/');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function killAIActivityAction(activityId: number) {
|
|
65
|
+
killAIActivity(activityId);
|
|
66
|
+
revalidatePath('/');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function killAllRunningAIActivitiesAction() {
|
|
70
|
+
const count = killAllRunningAIActivities();
|
|
71
|
+
revalidatePath('/');
|
|
72
|
+
return count;
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getRecentAIActivity } from '../../../lib/db';
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic';
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
const activities = getRecentAIActivity(30);
|
|
9
|
+
return NextResponse.json(activities);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('Failed to fetch AI activity:', error);
|
|
12
|
+
return NextResponse.json([], { status: 500 });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
body {
|
|
7
|
+
@apply bg-paper-100 text-black;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Documentation Markdown Styles */
|
|
12
|
+
.markdown-content {
|
|
13
|
+
@apply text-gray-800 leading-relaxed;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.markdown-content h1 {
|
|
17
|
+
@apply text-2xl font-bold text-gray-900 border-b border-gray-200 pb-2 mb-4 mt-6;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.markdown-content h2 {
|
|
21
|
+
@apply text-xl font-bold text-gray-800 border-b border-gray-100 pb-1.5 mb-3 mt-5;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.markdown-content h3 {
|
|
25
|
+
@apply text-lg font-semibold text-gray-800 mb-2 mt-4;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.markdown-content h4 {
|
|
29
|
+
@apply text-base font-semibold text-gray-700 mb-2 mt-3;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.markdown-content p {
|
|
33
|
+
@apply mb-3 text-gray-700;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.markdown-content ul {
|
|
37
|
+
@apply list-disc list-inside mb-3 space-y-1 text-gray-700;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.markdown-content ol {
|
|
41
|
+
@apply list-decimal list-inside mb-3 space-y-1 text-gray-700;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.markdown-content li {
|
|
45
|
+
@apply ml-2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.markdown-content a {
|
|
49
|
+
@apply text-blue-600 hover:text-blue-800 underline;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.markdown-content code {
|
|
53
|
+
@apply bg-gray-100 text-pink-600 px-1.5 py-0.5 rounded text-sm font-mono;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.markdown-content pre {
|
|
57
|
+
@apply bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto mb-4 text-sm;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.markdown-content pre code {
|
|
61
|
+
@apply bg-transparent text-gray-100 p-0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.markdown-content blockquote {
|
|
65
|
+
@apply border-l-4 border-blue-400 pl-4 italic text-gray-600 my-4 bg-blue-50 py-2 rounded-r;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.markdown-content hr {
|
|
69
|
+
@apply border-t border-gray-200 my-6;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.markdown-content table {
|
|
73
|
+
@apply w-full border-collapse mb-4 text-sm;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.markdown-content th {
|
|
77
|
+
@apply bg-gray-100 border border-gray-200 px-3 py-2 text-left font-semibold text-gray-700;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.markdown-content td {
|
|
81
|
+
@apply border border-gray-200 px-3 py-2 text-gray-600;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.markdown-content tr:nth-child(even) {
|
|
85
|
+
@apply bg-gray-50;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.markdown-content img {
|
|
89
|
+
@apply max-w-full h-auto rounded-lg shadow-sm my-4;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.markdown-content strong {
|
|
93
|
+
@apply font-semibold text-gray-900;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.markdown-content em {
|
|
97
|
+
@apply italic;
|
|
98
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { MermaidInit } from '../components/MermaidInit'
|
|
3
|
+
import './globals.css'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'Chronicle Dashboard',
|
|
7
|
+
description: 'Living Documentation Agent',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({
|
|
11
|
+
children,
|
|
12
|
+
}: {
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<script
|
|
19
|
+
src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"
|
|
20
|
+
async
|
|
21
|
+
/>
|
|
22
|
+
</head>
|
|
23
|
+
<body className="bg-paper-100 text-black font-mono antialiased overflow-hidden h-screen">
|
|
24
|
+
<MermaidInit />
|
|
25
|
+
{children}
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getStats, getRecentFiles, getJobs, getProjects, getProjectDocs, getProjectConfig, getDocStage, getDocVersions, getActiveDocVersion, getThoughtLogsForVersion, getRecentAIActivity } from '../lib/db';
|
|
2
|
+
import { AutoRefresh } from '../components/AutoRefresh';
|
|
3
|
+
import { ProjectList } from '../components/ProjectList';
|
|
4
|
+
import { FileList } from '../components/FileList';
|
|
5
|
+
import { JobQueueWrapper } from '../components/JobQueueWrapper';
|
|
6
|
+
import { ProjectContentArea } from '../components/ProjectContentArea';
|
|
7
|
+
import { HeaderWithThinking } from '../components/HeaderWithThinking';
|
|
8
|
+
import clsx from 'clsx';
|
|
9
|
+
|
|
10
|
+
export const dynamic = 'force-dynamic';
|
|
11
|
+
|
|
12
|
+
export default function Home({ searchParams }: { searchParams: { projectId?: string } }) {
|
|
13
|
+
let stats, recentFiles, jobs, projects, docs: any[] = [];
|
|
14
|
+
let triggerConfig: any = null;
|
|
15
|
+
let docStage: any = null;
|
|
16
|
+
let docVersions: any[] = [];
|
|
17
|
+
let activeVersionId: number | null = null;
|
|
18
|
+
let thoughtLogs: any[] = [];
|
|
19
|
+
let aiActivities: any[] = [];
|
|
20
|
+
const activeProjectId = searchParams.projectId;
|
|
21
|
+
let activeProjectTitle = 'Recent Files';
|
|
22
|
+
let projectPath: string = '';
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
projects = getProjects();
|
|
26
|
+
|
|
27
|
+
if (activeProjectId) {
|
|
28
|
+
const project = projects.find(p => String(p.id) === activeProjectId);
|
|
29
|
+
if (project) {
|
|
30
|
+
projectPath = project.path;
|
|
31
|
+
activeProjectTitle = project.path.split('/').pop() || 'Project Files';
|
|
32
|
+
docs = getProjectDocs(project.path);
|
|
33
|
+
const config = getProjectConfig(project.path);
|
|
34
|
+
if (config) {
|
|
35
|
+
triggerConfig = config.triggers;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fetch doc stage (only show if there's an active job)
|
|
39
|
+
docStage = getDocStage(project.id, project.path);
|
|
40
|
+
|
|
41
|
+
// Fetch doc versions and thought logs
|
|
42
|
+
docVersions = getDocVersions(project.id);
|
|
43
|
+
const activeVersion = getActiveDocVersion(project.id);
|
|
44
|
+
if (activeVersion) {
|
|
45
|
+
activeVersionId = activeVersion.id;
|
|
46
|
+
thoughtLogs = getThoughtLogsForVersion(activeVersion.id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
recentFiles = getRecentFiles(50, projectPath || undefined);
|
|
52
|
+
jobs = getJobs(50);
|
|
53
|
+
aiActivities = getRecentAIActivity(30);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-50 text-gray-900 font-mono">
|
|
57
|
+
<div className="text-center p-8 border border-red-200 bg-red-50 rounded">
|
|
58
|
+
<h1 className="text-xl font-bold mb-2 text-red-700">⚠️ Connection Error</h1>
|
|
59
|
+
<p className="text-red-500 text-sm">Could not connect to Daemon database.</p>
|
|
60
|
+
<p className="text-xs text-gray-500 mt-4 font-mono">{String(error)}</p>
|
|
61
|
+
<div className="mt-4">
|
|
62
|
+
<AutoRefresh />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<main className="h-screen w-screen flex flex-col bg-white text-gray-900 font-mono overflow-hidden">
|
|
71
|
+
<AutoRefresh />
|
|
72
|
+
|
|
73
|
+
{/* Top Header with Thinking Platform */}
|
|
74
|
+
<HeaderWithThinking initialActivities={aiActivities} />
|
|
75
|
+
|
|
76
|
+
{/* Main Workspace */}
|
|
77
|
+
<div className="flex-1 flex overflow-hidden">
|
|
78
|
+
{/* Sidebar: Projects */}
|
|
79
|
+
<aside className="w-64 border-r border-gray-200 bg-gray-50 flex-none overflow-hidden flex flex-col">
|
|
80
|
+
<ProjectList projects={projects} activeProjectId={activeProjectId} />
|
|
81
|
+
</aside>
|
|
82
|
+
|
|
83
|
+
{/* Main Content: Tabs */}
|
|
84
|
+
<section className="flex-1 flex flex-col overflow-hidden bg-white relative">
|
|
85
|
+
<ProjectContentArea
|
|
86
|
+
docs={docs}
|
|
87
|
+
files={recentFiles}
|
|
88
|
+
projectName={activeProjectTitle}
|
|
89
|
+
projectPath={projectPath}
|
|
90
|
+
hasProject={!!activeProjectId}
|
|
91
|
+
triggerConfig={triggerConfig}
|
|
92
|
+
docStage={docStage}
|
|
93
|
+
isPaused={activeProjectId ? projects.find(p => String(p.id) === activeProjectId)?.is_paused === 1 : false}
|
|
94
|
+
docVersions={docVersions}
|
|
95
|
+
activeVersionId={activeVersionId}
|
|
96
|
+
thoughtLogs={thoughtLogs}
|
|
97
|
+
/>
|
|
98
|
+
</section>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Footer / Job Queue Drawer */}
|
|
102
|
+
<footer className="border-t border-gray-200 flex-none overflow-hidden">
|
|
103
|
+
<JobQueueWrapper jobs={jobs} />
|
|
104
|
+
</footer>
|
|
105
|
+
</main>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
export function AutoRefresh() {
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const interval = setInterval(() => {
|
|
11
|
+
router.refresh();
|
|
12
|
+
}, 2000); // 2s refresh
|
|
13
|
+
|
|
14
|
+
return () => clearInterval(interval);
|
|
15
|
+
}, [router]);
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, memo } from 'react';
|
|
4
|
+
import { FileText, FolderOpen, Folder, ChevronRight, ChevronDown, Activity } from 'lucide-react';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
import { VersionSelector } from './VersionSelector';
|
|
7
|
+
import { ThinkingPanel } from './ThinkingPanel';
|
|
8
|
+
|
|
9
|
+
interface DocFile {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
content: string;
|
|
13
|
+
type: 'root' | 'docs';
|
|
14
|
+
relativePath?: string; // Path relative to docs folder
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FolderNode {
|
|
18
|
+
name: string;
|
|
19
|
+
path: string;
|
|
20
|
+
children: (FolderNode | DocFile)[];
|
|
21
|
+
isFolder: true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface DocVersion {
|
|
25
|
+
id: number;
|
|
26
|
+
version_number: number;
|
|
27
|
+
trigger_type: string;
|
|
28
|
+
commit_hash: string | null;
|
|
29
|
+
status: 'draft' | 'pending_review' | 'active' | 'archived';
|
|
30
|
+
created_at: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ThoughtLog {
|
|
34
|
+
doc_path: string;
|
|
35
|
+
thinking_summary: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Props {
|
|
39
|
+
docs: DocFile[];
|
|
40
|
+
projectName: string;
|
|
41
|
+
docStage?: { currentStage: number; totalStages: number } | null;
|
|
42
|
+
versions?: DocVersion[];
|
|
43
|
+
currentVersionId?: number | null;
|
|
44
|
+
thoughtLogs?: ThoughtLog[];
|
|
45
|
+
onVersionChange?: (versionId: number) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Build folder tree from flat file list
|
|
49
|
+
function buildFolderTree(docs: DocFile[]): (FolderNode | DocFile)[] {
|
|
50
|
+
const folderDocs = docs.filter(d => d.type === 'docs');
|
|
51
|
+
const rootDocs = docs.filter(d => d.type === 'root');
|
|
52
|
+
|
|
53
|
+
const folderMap = new Map<string, FolderNode>();
|
|
54
|
+
const tree: (FolderNode | DocFile)[] = [...rootDocs];
|
|
55
|
+
|
|
56
|
+
// Sort by path depth (shallower first)
|
|
57
|
+
folderDocs.sort((a, b) => a.path.split('/').length - b.path.split('/').length);
|
|
58
|
+
|
|
59
|
+
for (const doc of folderDocs) {
|
|
60
|
+
const parts = doc.relativePath?.split('/') || doc.path.split('/docs/').pop()?.split('/') || [];
|
|
61
|
+
|
|
62
|
+
if (parts.length === 1) {
|
|
63
|
+
// Direct child of docs/
|
|
64
|
+
tree.push(doc);
|
|
65
|
+
} else {
|
|
66
|
+
// Nested in subfolder
|
|
67
|
+
const folderPath = parts.slice(0, -1).join('/');
|
|
68
|
+
|
|
69
|
+
// Ensure parent folders exist
|
|
70
|
+
let currentPath = '';
|
|
71
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
72
|
+
const folderName = parts[i];
|
|
73
|
+
const newPath = currentPath ? `${currentPath}/${folderName}` : folderName;
|
|
74
|
+
|
|
75
|
+
if (!folderMap.has(newPath)) {
|
|
76
|
+
const folder: FolderNode = {
|
|
77
|
+
name: folderName,
|
|
78
|
+
path: newPath,
|
|
79
|
+
children: [],
|
|
80
|
+
isFolder: true
|
|
81
|
+
};
|
|
82
|
+
folderMap.set(newPath, folder);
|
|
83
|
+
|
|
84
|
+
if (currentPath) {
|
|
85
|
+
const parent = folderMap.get(currentPath);
|
|
86
|
+
if (parent) parent.children.push(folder);
|
|
87
|
+
} else {
|
|
88
|
+
tree.push(folder);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
currentPath = newPath;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add file to its folder
|
|
95
|
+
const parentFolder = folderMap.get(folderPath);
|
|
96
|
+
if (parentFolder) {
|
|
97
|
+
parentFolder.children.push(doc);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return tree;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function FolderItem({ node, selectedPath, onSelect, depth = 0 }: {
|
|
106
|
+
node: FolderNode | DocFile;
|
|
107
|
+
selectedPath: string | null;
|
|
108
|
+
onSelect: (doc: DocFile) => void;
|
|
109
|
+
depth?: number;
|
|
110
|
+
}) {
|
|
111
|
+
const [expanded, setExpanded] = useState(true);
|
|
112
|
+
|
|
113
|
+
if ('isFolder' in node) {
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
<button
|
|
117
|
+
onClick={() => setExpanded(!expanded)}
|
|
118
|
+
className="w-full flex items-center gap-1 p-1.5 rounded text-left text-xs hover:bg-gray-100 text-gray-600"
|
|
119
|
+
style={{ paddingLeft: `${(depth * 12) + 6}px` }}
|
|
120
|
+
>
|
|
121
|
+
{expanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
|
|
122
|
+
{expanded ? <FolderOpen size={12} className="text-amber-500" /> : <Folder size={12} className="text-amber-500" />}
|
|
123
|
+
<span className="truncate">{node.name}/</span>
|
|
124
|
+
</button>
|
|
125
|
+
{expanded && (
|
|
126
|
+
<div>
|
|
127
|
+
{node.children.map((child, i) => (
|
|
128
|
+
<FolderItem
|
|
129
|
+
key={'path' in child ? child.path : i}
|
|
130
|
+
node={child}
|
|
131
|
+
selectedPath={selectedPath}
|
|
132
|
+
onSelect={onSelect}
|
|
133
|
+
depth={depth + 1}
|
|
134
|
+
/>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<button
|
|
144
|
+
onClick={() => onSelect(node)}
|
|
145
|
+
className={clsx(
|
|
146
|
+
"w-full flex items-center gap-2 p-1.5 rounded text-left text-xs transition-colors",
|
|
147
|
+
selectedPath === node.path
|
|
148
|
+
? "bg-blue-100 text-blue-700"
|
|
149
|
+
: "hover:bg-gray-100 text-gray-700"
|
|
150
|
+
)}
|
|
151
|
+
style={{ paddingLeft: `${(depth * 12) + 6}px` }}
|
|
152
|
+
>
|
|
153
|
+
<FileText size={12} />
|
|
154
|
+
<span className="truncate">{node.name}</span>
|
|
155
|
+
</button>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const DocContent = memo(function DocContent({ content, path }: { content: string; path: string }) {
|
|
160
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (contentRef.current) {
|
|
164
|
+
// Try to render with mermaid if available
|
|
165
|
+
if (typeof window !== 'undefined' && (window as any).mermaid) {
|
|
166
|
+
(window as any).mermaid.run({
|
|
167
|
+
nodes: contentRef.current.querySelectorAll('.mermaid')
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}, [content]);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div className="prose prose-sm max-w-none" ref={contentRef}>
|
|
175
|
+
<div className="text-[10px] text-gray-400 mb-2 font-mono">{path}</div>
|
|
176
|
+
<div
|
|
177
|
+
className="markdown-content text-sm leading-relaxed"
|
|
178
|
+
dangerouslySetInnerHTML={{ __html: content }}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export function DocumentationViewer({
|
|
185
|
+
docs,
|
|
186
|
+
projectName,
|
|
187
|
+
docStage,
|
|
188
|
+
versions = [],
|
|
189
|
+
currentVersionId = null,
|
|
190
|
+
thoughtLogs = [],
|
|
191
|
+
onVersionChange
|
|
192
|
+
}: Props) {
|
|
193
|
+
const [selectedDoc, setSelectedDoc] = useState<DocFile | null>(docs[0] || null);
|
|
194
|
+
|
|
195
|
+
// Get thinking summary for selected doc
|
|
196
|
+
const currentThought = selectedDoc
|
|
197
|
+
? thoughtLogs.find(t => selectedDoc.path.includes(t.doc_path) || t.doc_path.includes(selectedDoc.name))?.thinking_summary
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
const folderTree = buildFolderTree(docs);
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div className="flex h-full">
|
|
204
|
+
{/* Doc Sidebar */}
|
|
205
|
+
<div className="w-52 border-r border-gray-200 bg-gray-50 flex-none overflow-hidden flex flex-col">
|
|
206
|
+
<div className="p-2 border-b border-gray-200 bg-gray-100 flex items-center justify-between">
|
|
207
|
+
<h3 className="text-[10px] font-bold uppercase tracking-wider text-gray-500">Documentation</h3>
|
|
208
|
+
{versions.length > 0 && onVersionChange && (
|
|
209
|
+
<VersionSelector
|
|
210
|
+
versions={versions}
|
|
211
|
+
currentVersionId={currentVersionId}
|
|
212
|
+
onVersionChange={onVersionChange}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Stage Progress */}
|
|
218
|
+
{docStage && docStage.currentStage > 0 && (
|
|
219
|
+
<div className="p-2 border-b border-gray-200 bg-blue-50">
|
|
220
|
+
<div className="flex items-center gap-1 text-[10px] text-blue-600 mb-1">
|
|
221
|
+
<Activity size={10} />
|
|
222
|
+
<span>Stage {docStage.currentStage}/{docStage.totalStages}</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div className="h-1 bg-blue-200 rounded-full overflow-hidden">
|
|
225
|
+
<div
|
|
226
|
+
className="h-full bg-blue-500 transition-all"
|
|
227
|
+
style={{ width: `${(docStage.currentStage / docStage.totalStages) * 100}%` }}
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Folder Tree */}
|
|
234
|
+
<div className="flex-1 overflow-y-auto p-1">
|
|
235
|
+
{folderTree.map((node, i) => (
|
|
236
|
+
<FolderItem
|
|
237
|
+
key={'path' in node ? node.path : i}
|
|
238
|
+
node={node}
|
|
239
|
+
selectedPath={selectedDoc?.path || null}
|
|
240
|
+
onSelect={setSelectedDoc}
|
|
241
|
+
/>
|
|
242
|
+
))}
|
|
243
|
+
|
|
244
|
+
{docs.length === 0 && (
|
|
245
|
+
<div className="p-4 text-center text-xs text-gray-400">
|
|
246
|
+
No documentation yet.
|
|
247
|
+
<br />
|
|
248
|
+
<span className="text-[10px]">Run `chronicle init`</span>
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
{/* Doc Content */}
|
|
255
|
+
<div className="flex-1 overflow-hidden flex flex-col bg-white">
|
|
256
|
+
<div className="flex-1 overflow-auto p-4">
|
|
257
|
+
{selectedDoc ? (
|
|
258
|
+
<DocContent content={selectedDoc.content} path={selectedDoc.path} />
|
|
259
|
+
) : (
|
|
260
|
+
<div className="h-full flex items-center justify-center text-gray-400 text-sm">
|
|
261
|
+
Select a document to view
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
{/* Chain of Thought Panel */}
|
|
267
|
+
{selectedDoc && (
|
|
268
|
+
<ThinkingPanel
|
|
269
|
+
thinkingSummary={currentThought || null}
|
|
270
|
+
docPath={selectedDoc.path}
|
|
271
|
+
/>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { FileText, Calendar, Clock } from 'lucide-react';
|
|
2
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
3
|
+
|
|
4
|
+
interface FileRecord {
|
|
5
|
+
id: number;
|
|
6
|
+
path: string;
|
|
7
|
+
last_modified: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function FileList({ files, title }: { files: FileRecord[], title?: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col h-full bg-white">
|
|
13
|
+
<div className="px-4 py-3 border-b border-gray-100 bg-white flex items-center justify-between flex-none">
|
|
14
|
+
<h2 className="text-xs font-bold uppercase tracking-wider text-gray-500 flex items-center gap-2">
|
|
15
|
+
<FileText size={14} />
|
|
16
|
+
<span>{title || 'Recent Files'}</span>
|
|
17
|
+
</h2>
|
|
18
|
+
<span className="bg-gray-100 text-gray-600 px-1.5 py-0.5 rounded text-[10px] font-mono">{files.length} items</span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div className="flex-1 overflow-y-auto">
|
|
22
|
+
<table className="w-full text-left border-collapse">
|
|
23
|
+
<thead className="text-[10px] text-gray-400 uppercase font-medium bg-gray-50/50 sticky top-0 backdrop-blur-sm z-10">
|
|
24
|
+
<tr>
|
|
25
|
+
<th className="px-4 py-2 font-medium bg-gray-50">Name</th>
|
|
26
|
+
<th className="px-4 py-2 font-medium hidden md:table-cell bg-gray-50">Location</th>
|
|
27
|
+
<th className="px-4 py-2 font-medium text-right bg-gray-50">Last Modified</th>
|
|
28
|
+
</tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody className="divide-y divide-gray-50">
|
|
31
|
+
{files.map(file => (
|
|
32
|
+
<tr key={file.id} className="group hover:bg-blue-50/50 transition-colors cursor-default">
|
|
33
|
+
<td className="px-4 py-2.5">
|
|
34
|
+
<div className="flex items-center gap-2.5">
|
|
35
|
+
<FileText size={14} className="text-gray-400 group-hover:text-blue-500 transition-colors" />
|
|
36
|
+
<span className="font-mono text-xs text-gray-700 font-medium group-hover:text-blue-700">
|
|
37
|
+
{file.path.split('/').pop()}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
</td>
|
|
41
|
+
<td className="px-4 py-2.5 hidden md:table-cell">
|
|
42
|
+
<div className="font-mono text-[10px] text-gray-400 truncate max-w-[300px]" title={file.path}>
|
|
43
|
+
{file.path}
|
|
44
|
+
</div>
|
|
45
|
+
</td>
|
|
46
|
+
<td className="px-4 py-2.5 text-right">
|
|
47
|
+
<div className="flex items-center justify-end gap-1.5 text-[10px] text-gray-400 font-mono" suppressHydrationWarning>
|
|
48
|
+
<Clock size={10} />
|
|
49
|
+
{formatDistanceToNow(file.last_modified, { addSuffix: true })}
|
|
50
|
+
</div>
|
|
51
|
+
</td>
|
|
52
|
+
</tr>
|
|
53
|
+
))}
|
|
54
|
+
{files.length === 0 && (
|
|
55
|
+
<tr>
|
|
56
|
+
<td colSpan={3}>
|
|
57
|
+
<div className="flex flex-col items-center justify-center p-12 text-gray-300 gap-2">
|
|
58
|
+
<FileText size={32} strokeWidth={1} />
|
|
59
|
+
<span className="text-xs font-mono">No files found</span>
|
|
60
|
+
</div>
|
|
61
|
+
</td>
|
|
62
|
+
</tr>
|
|
63
|
+
)}
|
|
64
|
+
</tbody>
|
|
65
|
+
</table>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|