idea-manager 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/build-manifest.json +2 -2
- package/.next/routes-manifest.json +35 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/advisor-actions/route.js +15 -0
- package/.next/server/app/api/advisor-actions/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/archive/route.js +1 -122
- package/.next/server/app/api/archive/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/global-advisor/route.js +37 -0
- package/.next/server/app/api/global-advisor/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/global-memo/route.js +8 -0
- package/.next/server/app/api/global-memo/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/maintenance/route.js +130 -0
- package/.next/server/app/api/maintenance/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/projects/[id]/advisor/route.js +22 -11
- package/.next/server/app/api/projects/[id]/advisor/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/apply-distribute/route.js +2 -8
- package/.next/server/app/api/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/auto-distribute/route.js +126 -3
- package/.next/server/app/api/projects/[id]/auto-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/brainstorm/route.js +124 -1
- package/.next/server/app/api/projects/[id]/brainstorm/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/git-sync/route.js +124 -1
- package/.next/server/app/api/projects/[id]/git-sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/route.js +124 -1
- package/.next/server/app/api/projects/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route.js +8 -0
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.js +1 -7
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.js +8 -0
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route.js +2 -8
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.js +1 -122
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route.js +124 -0
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route.js +1 -122
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/route.js +8 -0
- package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route.js +124 -1
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/search/route.js +8 -0
- package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sync/route.js +8 -0
- package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/move/route.js +15 -0
- package/.next/server/app/api/tasks/[taskId]/move/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js +12 -12
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +18 -13
- package/.next/server/chunks/{117.js → 697.js} +16 -2
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +2 -2
- package/.next/static/KREG104cVn2mBTMPTDTvH/_buildManifest.js +1 -0
- package/.next/static/chunks/374-23189d7e246ad164.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/advisor-actions/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/archive/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/filesystem/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/filesystem/tree/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/global-advisor/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/global-memo/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/health/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/maintenance/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/advisor/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/projects/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/search/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/sync/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/tasks/[taskId]/move/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/update/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/api/version/route-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/app/page-9117037f2947f4f6.js +28 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f051f234bea7bddd.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f051f234bea7bddd.js +1 -0
- package/.next/static/css/e9071b58a99b47e4.css +3 -0
- package/package.json +1 -1
- package/src/app/api/advisor-actions/route.ts +52 -0
- package/src/app/api/global-advisor/route.ts +50 -0
- package/src/app/api/maintenance/route.ts +36 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route.ts +24 -0
- package/src/app/api/tasks/[taskId]/move/route.ts +30 -0
- package/src/components/advisor/ActionBlock.tsx +124 -0
- package/src/components/advisor/AdvisorChat.tsx +175 -0
- package/src/components/advisor/GlobalAdvisorLayer.tsx +38 -0
- package/src/components/dashboard/DashboardPanel.tsx +2 -0
- package/src/components/memo/GlobalMemoLayer.tsx +81 -0
- package/src/components/tabs/TabBar.tsx +2 -0
- package/src/components/tabs/TabShell.tsx +6 -0
- package/src/components/task/NoteEditor.tsx +137 -0
- package/src/components/task/ProjectTree.tsx +105 -57
- package/src/components/task/TaskChat.tsx +4 -0
- package/src/components/task/TaskDetail.tsx +182 -1
- package/src/components/ui/AiActivityIndicator.tsx +66 -0
- package/src/components/ui/ShortcutOverlay.tsx +108 -0
- package/src/components/workspace/ProjectAdvisor.tsx +17 -181
- package/src/components/workspace/WorkspacePanel.tsx +75 -3
- package/src/hooks/useAiActivity.ts +6 -0
- package/src/lib/advisor-actions/parse.ts +59 -0
- package/src/lib/ai/global-context.ts +114 -0
- package/src/lib/ai/project-context.ts +22 -2
- package/src/lib/ai-activity.ts +33 -0
- package/src/lib/db/queries/global-conversations.ts +31 -0
- package/src/lib/db/queries/tasks.ts +3 -1
- package/src/lib/db/schema.ts +8 -0
- package/src/types/advisor-actions.ts +25 -0
- package/.next/static/chunks/374-769431701aab500f.js +0 -1
- package/.next/static/chunks/app/_global-error/page-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/archive/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/filesystem/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/filesystem/tree/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/global-memo/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/health/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/advisor/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/projects/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/search/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/sync/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/update/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/api/version/route-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/app/page-e935ee928da68ca2.js +0 -28
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-3ff8f59aaa75b8f8.js +0 -1
- package/.next/static/css/e4c7cd5a570312d9.css +0 -3
- package/.next/static/pxqzEiwniZAUDOUTb5SnX/_buildManifest.js +0 -1
- /package/.next/static/{pxqzEiwniZAUDOUTb5SnX → KREG104cVn2mBTMPTDTvH}/_ssgManifest.js +0 -0
|
@@ -70,6 +70,7 @@ export default function WorkspacePanel({
|
|
|
70
70
|
const [showFileTree, setShowFileTree] = useState(false);
|
|
71
71
|
const [showAutoDistribute, setShowAutoDistribute] = useState(false);
|
|
72
72
|
const [chatStates, setChatStates] = useState<Record<string, 'idle' | 'loading' | 'done'>>({});
|
|
73
|
+
const [deleteToast, setDeleteToast] = useState<{ taskId: string; title: string; timer: ReturnType<typeof setTimeout> } | null>(null);
|
|
73
74
|
const syncingRef = useRef(false);
|
|
74
75
|
|
|
75
76
|
// Resizable panel widths
|
|
@@ -156,6 +157,16 @@ export default function WorkspacePanel({
|
|
|
156
157
|
|
|
157
158
|
const selectedTask = tasks.find(t => t.id === selectedTaskId) ?? null;
|
|
158
159
|
|
|
160
|
+
// Refresh when advisor actions are applied (from any advisor — project or global)
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const refresh = () => { loadSubProjects(); if (selectedSubId) {
|
|
163
|
+
fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks`)
|
|
164
|
+
.then(r => r.json()).then(setTasks).catch(() => {});
|
|
165
|
+
}};
|
|
166
|
+
window.addEventListener('advisor-action-applied', refresh);
|
|
167
|
+
return () => window.removeEventListener('advisor-action-applied', refresh);
|
|
168
|
+
}, [id, selectedSubId, loadSubProjects]);
|
|
169
|
+
|
|
159
170
|
const handleCreateSubProject = async () => {
|
|
160
171
|
if (!newSubName.trim()) return;
|
|
161
172
|
const res = await fetch(`/api/projects/${id}/sub-projects`, {
|
|
@@ -244,10 +255,34 @@ export default function WorkspacePanel({
|
|
|
244
255
|
});
|
|
245
256
|
};
|
|
246
257
|
|
|
247
|
-
const handleTaskDelete = (taskId?: string) => {
|
|
258
|
+
const handleTaskDelete = async (taskId?: string) => {
|
|
248
259
|
const tid = taskId || selectedTaskId;
|
|
249
|
-
if (!tid) return;
|
|
250
|
-
|
|
260
|
+
if (!tid || !selectedSubId) return;
|
|
261
|
+
const task = tasks.find(t => t.id === tid);
|
|
262
|
+
if (!task) return;
|
|
263
|
+
|
|
264
|
+
// Immediate archive (soft delete) + undo toast
|
|
265
|
+
await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks/${tid}?mode=archive`, { method: 'DELETE' });
|
|
266
|
+
setTasks(prev => prev.filter(t => t.id !== tid));
|
|
267
|
+
if (selectedTaskId === tid) setSelectedTaskId(null);
|
|
268
|
+
loadSubProjects();
|
|
269
|
+
|
|
270
|
+
if (deleteToast?.timer) clearTimeout(deleteToast.timer);
|
|
271
|
+
const timer = setTimeout(() => setDeleteToast(null), 30000);
|
|
272
|
+
setDeleteToast({ taskId: tid, title: task.title, timer });
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const undoTaskDelete = async () => {
|
|
276
|
+
if (!deleteToast) return;
|
|
277
|
+
clearTimeout(deleteToast.timer);
|
|
278
|
+
await fetch(`/api/archive?action=restore&taskId=${deleteToast.taskId}`, { method: 'POST' });
|
|
279
|
+
// Re-fetch tasks to get the restored one
|
|
280
|
+
if (selectedSubId) {
|
|
281
|
+
const res = await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks`);
|
|
282
|
+
if (res.ok) setTasks(await res.json());
|
|
283
|
+
}
|
|
284
|
+
loadSubProjects();
|
|
285
|
+
setDeleteToast(null);
|
|
251
286
|
};
|
|
252
287
|
|
|
253
288
|
const handleConfirmAction = async (mode?: 'archive' | 'permanent') => {
|
|
@@ -540,6 +575,18 @@ export default function WorkspacePanel({
|
|
|
540
575
|
onCreateTask={handleCreateTask} onStatusChange={handleTaskStatusChange}
|
|
541
576
|
onTodayToggle={handleTaskTodayToggle} onDeleteTask={handleTaskDelete}
|
|
542
577
|
onReorderSubs={handleReorderSubs}
|
|
578
|
+
onReorderTasks={async (orderedIds) => {
|
|
579
|
+
if (!selectedSubId) return;
|
|
580
|
+
setTasks(prev => {
|
|
581
|
+
const map = new Map(prev.map(t => [t.id, t]));
|
|
582
|
+
return orderedIds.map(id => map.get(id)!).filter(Boolean);
|
|
583
|
+
});
|
|
584
|
+
await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks/reorder`, {
|
|
585
|
+
method: 'POST',
|
|
586
|
+
headers: { 'Content-Type': 'application/json' },
|
|
587
|
+
body: JSON.stringify({ orderedIds }),
|
|
588
|
+
});
|
|
589
|
+
}}
|
|
543
590
|
onAutoDistribute={() => setShowAutoDistribute(true)}
|
|
544
591
|
chatStates={chatStates} />
|
|
545
592
|
</div>
|
|
@@ -554,6 +601,11 @@ export default function WorkspacePanel({
|
|
|
554
601
|
siblingTasks={tasks}
|
|
555
602
|
onUpdate={handleTaskUpdate} onDelete={handleTaskDelete}
|
|
556
603
|
onTaskPromoted={(newTask) => setTasks(prev => [...prev, newTask])}
|
|
604
|
+
onTaskMoved={() => {
|
|
605
|
+
// Task moved away — remove from current list and deselect
|
|
606
|
+
setTasks(prev => prev.filter(t => t.id !== selectedTaskId));
|
|
607
|
+
setSelectedTaskId(null);
|
|
608
|
+
}}
|
|
557
609
|
onChatStateChange={(taskId, state) => {
|
|
558
610
|
setChatStates(prev => ({ ...prev, [taskId]: state }));
|
|
559
611
|
}} />
|
|
@@ -623,9 +675,29 @@ export default function WorkspacePanel({
|
|
|
623
675
|
{showAdvisor && (
|
|
624
676
|
<ProjectAdvisor
|
|
625
677
|
projectId={id}
|
|
678
|
+
projectName={project.name}
|
|
626
679
|
onClose={() => setShowAdvisor(false)}
|
|
627
680
|
/>
|
|
628
681
|
)}
|
|
682
|
+
{deleteToast && (
|
|
683
|
+
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-card border border-border rounded-lg shadow-xl px-4 py-2.5 flex items-center gap-3 animate-dialog-in">
|
|
684
|
+
<span className="text-xs text-muted-foreground truncate max-w-[200px]">
|
|
685
|
+
"{deleteToast.title}" 삭제됨
|
|
686
|
+
</span>
|
|
687
|
+
<button
|
|
688
|
+
onClick={undoTaskDelete}
|
|
689
|
+
className="text-xs px-2 py-1 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
|
690
|
+
>
|
|
691
|
+
되돌리기
|
|
692
|
+
</button>
|
|
693
|
+
<button
|
|
694
|
+
onClick={() => { clearTimeout(deleteToast.timer); setDeleteToast(null); }}
|
|
695
|
+
className="text-xs text-muted-foreground hover:text-foreground"
|
|
696
|
+
>
|
|
697
|
+
×
|
|
698
|
+
</button>
|
|
699
|
+
</div>
|
|
700
|
+
)}
|
|
629
701
|
</div>
|
|
630
702
|
);
|
|
631
703
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { getAiActivities, subscribeAiActivity, type AiActivity } from '@/lib/ai-activity';
|
|
3
|
+
|
|
4
|
+
export function useAiActivity(): AiActivity[] {
|
|
5
|
+
return useSyncExternalStore(subscribeAiActivity, getAiActivities, getAiActivities);
|
|
6
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AdvisorAction } from '@/types/advisor-actions';
|
|
2
|
+
|
|
3
|
+
const ACTION_BLOCK_RE = /```action\s*\n([\s\S]*?)```/g;
|
|
4
|
+
|
|
5
|
+
export interface ParsedContent {
|
|
6
|
+
segments: ({ type: 'markdown'; text: string } | { type: 'actions'; actions: AdvisorAction[] })[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function validateAction(obj: unknown): AdvisorAction | null {
|
|
10
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
11
|
+
const a = obj as Record<string, unknown>;
|
|
12
|
+
if (a.type === 'create_task') {
|
|
13
|
+
if (typeof a.subProjectId !== 'string' || typeof a.title !== 'string') return null;
|
|
14
|
+
return {
|
|
15
|
+
type: 'create_task',
|
|
16
|
+
subProjectId: a.subProjectId,
|
|
17
|
+
projectId: typeof a.projectId === 'string' ? a.projectId : undefined,
|
|
18
|
+
title: a.title,
|
|
19
|
+
description: typeof a.description === 'string' ? a.description : undefined,
|
|
20
|
+
priority: ['high', 'medium', 'low'].includes(a.priority as string) ? a.priority as 'high' | 'medium' | 'low' : undefined,
|
|
21
|
+
status: typeof a.status === 'string' ? (a.status as string) : undefined,
|
|
22
|
+
} as AdvisorAction;
|
|
23
|
+
}
|
|
24
|
+
if (a.type === 'update_task') {
|
|
25
|
+
if (typeof a.taskId !== 'string' || !a.changes || typeof a.changes !== 'object') return null;
|
|
26
|
+
return { type: 'update_task', taskId: a.taskId, changes: a.changes as Record<string, unknown> } as AdvisorAction;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseAdvisorContent(content: string): ParsedContent {
|
|
32
|
+
const segments: ParsedContent['segments'] = [];
|
|
33
|
+
let lastIndex = 0;
|
|
34
|
+
|
|
35
|
+
for (const match of content.matchAll(ACTION_BLOCK_RE)) {
|
|
36
|
+
const before = content.slice(lastIndex, match.index);
|
|
37
|
+
if (before.trim()) segments.push({ type: 'markdown', text: before });
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const raw = JSON.parse(match[1]);
|
|
41
|
+
const arr = Array.isArray(raw) ? raw : [raw];
|
|
42
|
+
const valid = arr.map(validateAction).filter((a): a is AdvisorAction => a !== null);
|
|
43
|
+
if (valid.length > 0) {
|
|
44
|
+
segments.push({ type: 'actions', actions: valid });
|
|
45
|
+
} else {
|
|
46
|
+
segments.push({ type: 'markdown', text: match[0] });
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
segments.push({ type: 'markdown', text: match[0] });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const remaining = content.slice(lastIndex);
|
|
56
|
+
if (remaining.trim()) segments.push({ type: 'markdown', text: remaining });
|
|
57
|
+
|
|
58
|
+
return { segments };
|
|
59
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { listProjects } from '../db/queries/projects';
|
|
2
|
+
import { getSubProjects } from '../db/queries/sub-projects';
|
|
3
|
+
import { getTasksByProject } from '../db/queries/tasks';
|
|
4
|
+
import { getBrainstorm } from '../db/queries/brainstorms';
|
|
5
|
+
import type { ITask, TaskStatus } from '../../types';
|
|
6
|
+
|
|
7
|
+
const ACTION_INSTRUCTIONS = `
|
|
8
|
+
## Actions
|
|
9
|
+
사용자가 요청하면 태스크 생성/수정을 제안할 수 있습니다. \`\`\`action 블록을 사용하세요.
|
|
10
|
+
사용자가 명시적으로 요청하지 않으면 action 블록을 넣지 마세요.
|
|
11
|
+
|
|
12
|
+
형식:
|
|
13
|
+
\`\`\`action
|
|
14
|
+
[
|
|
15
|
+
{"type":"create_task","subProjectId":"<sub_id값>","projectId":"<project_id값>","title":"제목","priority":"high|medium|low"},
|
|
16
|
+
{"type":"update_task","taskId":"<task_id값>","changes":{"status":"done"}}
|
|
17
|
+
]
|
|
18
|
+
\`\`\`
|
|
19
|
+
|
|
20
|
+
규칙:
|
|
21
|
+
- subProjectId, taskId, projectId는 아래 컨텍스트의 [sub_id:...], [task_id:...], [project_id:...] 값을 정확히 사용
|
|
22
|
+
- action 블록 앞에 항상 무엇을 제안하는지 설명
|
|
23
|
+
- 사용자가 승인해야 실행됨
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const MAX_BRAINSTORM = 1500;
|
|
27
|
+
const NOTE_LIMIT = 120;
|
|
28
|
+
const MAX_HISTORY_MESSAGES = 20;
|
|
29
|
+
|
|
30
|
+
function truncate(s: string | null | undefined, max: number): string {
|
|
31
|
+
if (!s) return '';
|
|
32
|
+
if (s.length <= max) return s;
|
|
33
|
+
return s.slice(0, max) + '…';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const STATUS_ICON: Record<string, string> = {
|
|
37
|
+
idea: 'idea', doing: 'DOING', writing: 'writing', submitted: 'submitted',
|
|
38
|
+
testing: 'testing', done: 'done', problem: 'PROBLEM',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function buildGlobalAdvisorPrompt(): string {
|
|
42
|
+
const projects = listProjects();
|
|
43
|
+
|
|
44
|
+
const parts: string[] = [];
|
|
45
|
+
parts.push('당신은 사용자의 전체 워크스페이스를 조망하는 AI 어드바이저입니다.');
|
|
46
|
+
parts.push('여러 프로젝트의 현황을 파악하고, 우선순위·방향·빠진 부분·크로스-프로젝트 이슈 등을 논의합니다.');
|
|
47
|
+
parts.push('한국어로 간결하게 답하세요. 긴 설교 금지.');
|
|
48
|
+
parts.push(ACTION_INSTRUCTIONS);
|
|
49
|
+
|
|
50
|
+
parts.push('=== ALL WORKSPACES ===\n');
|
|
51
|
+
|
|
52
|
+
let totalTasks = 0;
|
|
53
|
+
let totalDone = 0;
|
|
54
|
+
let totalProblem = 0;
|
|
55
|
+
|
|
56
|
+
for (const project of projects) {
|
|
57
|
+
const subs = getSubProjects(project.id);
|
|
58
|
+
const allTasks = getTasksByProject(project.id);
|
|
59
|
+
const brainstorm = getBrainstorm(project.id);
|
|
60
|
+
|
|
61
|
+
totalTasks += allTasks.length;
|
|
62
|
+
totalDone += allTasks.filter(t => t.status === 'done').length;
|
|
63
|
+
totalProblem += allTasks.filter(t => t.status === 'problem').length;
|
|
64
|
+
|
|
65
|
+
const counts: Record<string, number> = {};
|
|
66
|
+
const todayTasks: string[] = [];
|
|
67
|
+
const problemTasks: string[] = [];
|
|
68
|
+
for (const t of allTasks) {
|
|
69
|
+
counts[t.status] = (counts[t.status] ?? 0) + 1;
|
|
70
|
+
if (t.is_today) todayTasks.push(t.title);
|
|
71
|
+
if (t.status === 'problem') problemTasks.push(t.title);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lines: string[] = [];
|
|
75
|
+
lines.push(`## ${project.name} [project_id:${project.id}]`);
|
|
76
|
+
if (project.description) lines.push(project.description);
|
|
77
|
+
const statsStr = Object.entries(counts).map(([k, v]) => `${k}:${v}`).join(' / ');
|
|
78
|
+
lines.push(`태스크 ${allTasks.length}개 (${statsStr})`);
|
|
79
|
+
if (todayTasks.length) lines.push(`Today: ${todayTasks.join(', ')}`);
|
|
80
|
+
if (problemTasks.length) lines.push(`문제: ${problemTasks.join(', ')}`);
|
|
81
|
+
|
|
82
|
+
if (brainstorm?.content) {
|
|
83
|
+
lines.push(`\n브레인스토밍 요약: ${truncate(brainstorm.content, MAX_BRAINSTORM)}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Show active (non-done, non-archived) tasks by sub-project
|
|
87
|
+
for (const sub of subs) {
|
|
88
|
+
const subTasks = allTasks.filter(t => t.sub_project_id === sub.id && t.status !== 'done');
|
|
89
|
+
if (!subTasks.length) continue;
|
|
90
|
+
lines.push(`\n### ${sub.name} [sub_id:${sub.id}]`);
|
|
91
|
+
for (const t of subTasks) {
|
|
92
|
+
const note = truncate(t.description, NOTE_LIMIT);
|
|
93
|
+
const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
|
|
94
|
+
const flagStr = flags ? ` (${flags})` : '';
|
|
95
|
+
lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}** [task_id:${t.id}]${flagStr}${note ? ' — ' + note : ''}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
parts.push(lines.join('\n'));
|
|
100
|
+
parts.push('');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
parts.push('---');
|
|
104
|
+
parts.push(`전체: ${projects.length}개 워크스페이스, ${totalTasks}개 태스크 (완료 ${totalDone}, 문제 ${totalProblem})`);
|
|
105
|
+
|
|
106
|
+
return parts.join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function trimHistory(
|
|
110
|
+
messages: { role: string; content: string }[],
|
|
111
|
+
): { role: string; content: string }[] {
|
|
112
|
+
if (messages.length <= MAX_HISTORY_MESSAGES) return messages;
|
|
113
|
+
return messages.slice(-MAX_HISTORY_MESSAGES);
|
|
114
|
+
}
|
|
@@ -4,6 +4,25 @@ import { getTasksByProject } from '../db/queries/tasks';
|
|
|
4
4
|
import { getBrainstorm } from '../db/queries/brainstorms';
|
|
5
5
|
import type { ITask, TaskStatus } from '../../types';
|
|
6
6
|
|
|
7
|
+
const ACTION_INSTRUCTIONS = `
|
|
8
|
+
## Actions
|
|
9
|
+
사용자가 요청하면 태스크 생성/수정을 제안할 수 있습니다. \`\`\`action 블록을 사용하세요.
|
|
10
|
+
사용자가 명시적으로 요청하지 않으면 action 블록을 넣지 마세요.
|
|
11
|
+
|
|
12
|
+
형식:
|
|
13
|
+
\`\`\`action
|
|
14
|
+
[
|
|
15
|
+
{"type":"create_task","subProjectId":"<sub_id값>","title":"제목","priority":"high|medium|low","status":"idea"},
|
|
16
|
+
{"type":"update_task","taskId":"<task_id값>","changes":{"status":"done"}}
|
|
17
|
+
]
|
|
18
|
+
\`\`\`
|
|
19
|
+
|
|
20
|
+
규칙:
|
|
21
|
+
- subProjectId, taskId는 아래 컨텍스트의 [sub_id:...], [task_id:...] 값을 정확히 사용
|
|
22
|
+
- action 블록 앞에 항상 무엇을 제안하는지 설명
|
|
23
|
+
- 사용자가 승인해야 실행됨 (자동 실행 아님)
|
|
24
|
+
`;
|
|
25
|
+
|
|
7
26
|
const MAX_BRAINSTORM = 4000;
|
|
8
27
|
const NOTE_LIMIT_ACTIVE = 500;
|
|
9
28
|
const NOTE_LIMIT_DEFAULT = 200;
|
|
@@ -49,7 +68,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
|
|
|
49
68
|
continue;
|
|
50
69
|
}
|
|
51
70
|
const lines: string[] = [];
|
|
52
|
-
lines.push(`### ${sub.name}`);
|
|
71
|
+
lines.push(`### ${sub.name} [sub_id:${sub.id}]`);
|
|
53
72
|
if (sub.description) lines.push(sub.description);
|
|
54
73
|
lines.push(`태스크 ${tasks.length}개:`);
|
|
55
74
|
for (const t of tasks) {
|
|
@@ -58,7 +77,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
|
|
|
58
77
|
const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
|
|
59
78
|
const flagStr = flags ? ` (${flags})` : '';
|
|
60
79
|
const noteStr = note ? ` — ${note}` : '';
|
|
61
|
-
lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}
|
|
80
|
+
lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}** [task_id:${t.id}]${flagStr}${noteStr}`);
|
|
62
81
|
}
|
|
63
82
|
subSections.push(lines.join('\n'));
|
|
64
83
|
}
|
|
@@ -86,6 +105,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
|
|
|
86
105
|
parts.push(`당신은 프로젝트 "${project.name}"의 어드바이저입니다.`);
|
|
87
106
|
parts.push(`사용자가 프로젝트 방향, 우선순위, 빠진 부분, 다음 단계 등을 논의하면 프로젝트 전체 맥락을 바탕으로 간결하게 답합니다.`);
|
|
88
107
|
parts.push(`태스크를 언급할 때는 정확한 제목을 쓰세요. 한국어로 답하세요. 긴 설교는 금지.`);
|
|
108
|
+
parts.push(ACTION_INSTRUCTIONS);
|
|
89
109
|
|
|
90
110
|
if (project.ai_context) {
|
|
91
111
|
parts.push(`\nProject AI Policy:\n${project.ai_context}`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AiActivity {
|
|
2
|
+
id: string;
|
|
3
|
+
type: 'refine' | 'task-chat' | 'project-advisor' | 'global-advisor' | 'watch';
|
|
4
|
+
label: string;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Module-level store — survives React component unmounts.
|
|
9
|
+
let activities: AiActivity[] = [];
|
|
10
|
+
let listeners: Set<() => void> = new Set();
|
|
11
|
+
|
|
12
|
+
function notify() {
|
|
13
|
+
for (const fn of listeners) fn();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function registerAiActivity(activity: AiActivity) {
|
|
17
|
+
activities = [...activities, activity];
|
|
18
|
+
notify();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function unregisterAiActivity(id: string) {
|
|
22
|
+
activities = activities.filter(a => a.id !== id);
|
|
23
|
+
notify();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getAiActivities(): AiActivity[] {
|
|
27
|
+
return activities;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function subscribeAiActivity(listener: () => void): () => void {
|
|
31
|
+
listeners.add(listener);
|
|
32
|
+
return () => { listeners.delete(listener); };
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getDb } from '../index';
|
|
2
|
+
import { generateId } from '../../utils/id';
|
|
3
|
+
import type { IProjectConversation } from '../../../types';
|
|
4
|
+
|
|
5
|
+
// Reuses IProjectConversation shape — project_id is always '__global__'.
|
|
6
|
+
const GLOBAL_ID = '__global__';
|
|
7
|
+
|
|
8
|
+
export function getGlobalConversations(limit = 50): IProjectConversation[] {
|
|
9
|
+
const db = getDb();
|
|
10
|
+
return db.prepare(
|
|
11
|
+
`SELECT * FROM global_conversations ORDER BY created_at DESC LIMIT ?`
|
|
12
|
+
).all(limit).reverse() as IProjectConversation[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function addGlobalConversation(
|
|
16
|
+
role: 'assistant' | 'user' | 'system',
|
|
17
|
+
content: string,
|
|
18
|
+
): IProjectConversation {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const id = generateId();
|
|
21
|
+
const now = new Date().toISOString();
|
|
22
|
+
db.prepare(
|
|
23
|
+
'INSERT INTO global_conversations (id, project_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)'
|
|
24
|
+
).run(id, GLOBAL_ID, role, content, now);
|
|
25
|
+
return db.prepare('SELECT * FROM global_conversations WHERE id = ?').get(id) as IProjectConversation;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clearGlobalConversations(): void {
|
|
29
|
+
const db = getDb();
|
|
30
|
+
db.prepare('DELETE FROM global_conversations').run();
|
|
31
|
+
}
|
|
@@ -116,6 +116,7 @@ export function updateTask(id: string, data: {
|
|
|
116
116
|
priority?: ItemPriority;
|
|
117
117
|
is_today?: boolean;
|
|
118
118
|
sort_order?: number;
|
|
119
|
+
project_id?: string;
|
|
119
120
|
sub_project_id?: string;
|
|
120
121
|
tags?: string[];
|
|
121
122
|
}): ITask | undefined {
|
|
@@ -127,7 +128,7 @@ export function updateTask(id: string, data: {
|
|
|
127
128
|
db.prepare(`
|
|
128
129
|
UPDATE tasks SET
|
|
129
130
|
title = ?, description = ?, status = ?, priority = ?,
|
|
130
|
-
is_today = ?, sort_order = ?, sub_project_id = ?, tags = ?, updated_at = ?
|
|
131
|
+
is_today = ?, sort_order = ?, project_id = ?, sub_project_id = ?, tags = ?, updated_at = ?
|
|
131
132
|
WHERE id = ?
|
|
132
133
|
`).run(
|
|
133
134
|
data.title ?? row.title,
|
|
@@ -136,6 +137,7 @@ export function updateTask(id: string, data: {
|
|
|
136
137
|
data.priority ?? row.priority,
|
|
137
138
|
data.is_today !== undefined ? (data.is_today ? 1 : 0) : row.is_today,
|
|
138
139
|
data.sort_order ?? row.sort_order,
|
|
140
|
+
data.project_id ?? row.project_id,
|
|
139
141
|
data.sub_project_id ?? row.sub_project_id,
|
|
140
142
|
data.tags ? JSON.stringify(data.tags) : row.tags,
|
|
141
143
|
now,
|
package/src/lib/db/schema.ts
CHANGED
|
@@ -96,6 +96,14 @@ export function initSchema(db: any): void {
|
|
|
96
96
|
`);
|
|
97
97
|
|
|
98
98
|
db.exec(`
|
|
99
|
+
CREATE TABLE IF NOT EXISTS global_conversations (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
project_id TEXT NOT NULL DEFAULT '__global__',
|
|
102
|
+
role TEXT NOT NULL CHECK(role IN ('assistant','user','system')),
|
|
103
|
+
content TEXT NOT NULL,
|
|
104
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
105
|
+
);
|
|
106
|
+
|
|
99
107
|
CREATE TABLE IF NOT EXISTS project_conversations (
|
|
100
108
|
id TEXT PRIMARY KEY,
|
|
101
109
|
project_id TEXT NOT NULL,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TaskStatus } from './index';
|
|
2
|
+
|
|
3
|
+
export interface CreateTaskAction {
|
|
4
|
+
type: 'create_task';
|
|
5
|
+
subProjectId: string;
|
|
6
|
+
projectId?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
priority?: 'high' | 'medium' | 'low';
|
|
10
|
+
status?: TaskStatus;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UpdateTaskAction {
|
|
14
|
+
type: 'update_task';
|
|
15
|
+
taskId: string;
|
|
16
|
+
changes: {
|
|
17
|
+
status?: TaskStatus;
|
|
18
|
+
priority?: 'high' | 'medium' | 'low';
|
|
19
|
+
description?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
is_today?: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AdvisorAction = CreateTaskAction | UpdateTaskAction;
|