idea-manager 0.4.0 → 0.5.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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/src/app/projects/[id]/page.tsx +0 -4
  3. package/src/components/brainstorm/Editor.tsx +1 -84
  4. package/src/lib/ai/client.ts +0 -123
  5. package/src/lib/db/schema.ts +0 -70
  6. package/src/types/index.ts +0 -90
  7. package/src/app/api/projects/[id]/cleanup/route.ts +0 -32
  8. package/src/app/api/projects/[id]/conversations/route.ts +0 -50
  9. package/src/app/api/projects/[id]/items/[itemId]/prompt/route.ts +0 -51
  10. package/src/app/api/projects/[id]/items/[itemId]/refine/route.ts +0 -36
  11. package/src/app/api/projects/[id]/items/[itemId]/route.ts +0 -95
  12. package/src/app/api/projects/[id]/items/route.ts +0 -67
  13. package/src/app/api/projects/[id]/memos/route.ts +0 -18
  14. package/src/app/api/projects/[id]/scan/route.ts +0 -73
  15. package/src/app/api/projects/[id]/scan/stream/route.ts +0 -112
  16. package/src/app/api/projects/[id]/structure/route.ts +0 -59
  17. package/src/app/api/projects/[id]/structure/stream/route.ts +0 -157
  18. package/src/components/ScanPanel.tsx +0 -743
  19. package/src/components/brainstorm/MemoPin.tsx +0 -117
  20. package/src/components/tree/CardView.tsx +0 -206
  21. package/src/components/tree/ItemDetail.tsx +0 -196
  22. package/src/components/tree/LockToggle.tsx +0 -23
  23. package/src/components/tree/RefinePopover.tsx +0 -157
  24. package/src/components/tree/StatusBadge.tsx +0 -32
  25. package/src/components/tree/TreeNode.tsx +0 -227
  26. package/src/components/tree/TreeView.tsx +0 -304
  27. package/src/lib/ai/chat-responder.ts +0 -71
  28. package/src/lib/ai/cleanup.ts +0 -87
  29. package/src/lib/ai/prompter.ts +0 -78
  30. package/src/lib/ai/refiner.ts +0 -128
  31. package/src/lib/ai/structurer.ts +0 -403
  32. package/src/lib/db/queries/context.ts +0 -76
  33. package/src/lib/db/queries/conversations.ts +0 -46
  34. package/src/lib/db/queries/items.ts +0 -268
  35. package/src/lib/db/queries/memos.ts +0 -66
  36. package/src/lib/db/queries/prompts.ts +0 -68
  37. package/src/lib/scanner.ts +0 -573
  38. package/src/lib/task-store.ts +0 -97
@@ -1,32 +0,0 @@
1
- 'use client';
2
-
3
- interface StatusBadgeProps {
4
- status: string;
5
- onStatusChange: (status: string) => void;
6
- disabled?: boolean;
7
- }
8
-
9
- const statusConfig: Record<string, { icon: string; label: string; next: string }> = {
10
- pending: { icon: '\u{23F3}', label: '대기', next: 'in_progress' },
11
- in_progress: { icon: '\u{1F504}', label: '진행 중', next: 'done' },
12
- done: { icon: '\u{2705}', label: '완료', next: 'pending' },
13
- };
14
-
15
- export default function StatusBadge({ status, onStatusChange, disabled }: StatusBadgeProps) {
16
- const config = statusConfig[status] || statusConfig.pending;
17
-
18
- return (
19
- <button
20
- onClick={(e) => {
21
- e.stopPropagation();
22
- onStatusChange(config.next);
23
- }}
24
- disabled={disabled}
25
- className="status-badge"
26
- title={`${config.label} → 클릭하여 변경`}
27
- >
28
- <span>{config.icon}</span>
29
- <span className="status-badge-label">{config.label}</span>
30
- </button>
31
- );
32
- }
@@ -1,227 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import LockToggle from './LockToggle';
5
- import StatusBadge from './StatusBadge';
6
- import ItemDetail from './ItemDetail';
7
- import RefinePopover from './RefinePopover';
8
-
9
- interface IItemTree {
10
- id: string;
11
- project_id?: string;
12
- title: string;
13
- description: string;
14
- item_type: string;
15
- priority: string;
16
- status: string;
17
- is_locked: boolean;
18
- is_pinned: boolean;
19
- children: IItemTree[];
20
- }
21
-
22
- interface TreeNodeProps {
23
- item: IItemTree;
24
- depth: number;
25
- projectId: string;
26
- onItemUpdate: (itemId: string, data: Record<string, unknown>) => void;
27
- onItemDelete: (itemId: string) => void;
28
- onTreeRefresh: (tree: IItemTree[]) => void;
29
- selectMode?: boolean;
30
- selected?: Set<string>;
31
- onToggleSelect?: (id: string) => void;
32
- defaultExpanded?: boolean;
33
- }
34
-
35
- const typeConfig: Record<string, { icon: string; color: string; label: string }> = {
36
- feature: { icon: '\u{1F4E6}', color: 'var(--primary)', label: '기능' },
37
- task: { icon: '\u{2705}', color: 'var(--success)', label: '작업' },
38
- bug: { icon: '\u{1F41B}', color: 'var(--destructive)', label: '버그' },
39
- idea: { icon: '\u{1F4A1}', color: 'var(--warning)', label: '아이디어' },
40
- note: { icon: '\u{1F4DD}', color: 'var(--muted-foreground)', label: '메모' },
41
- };
42
-
43
- function countDescendantStatus(item: IItemTree): { total: number; done: number } {
44
- let total = 0;
45
- let done = 0;
46
- for (const child of item.children) {
47
- total++;
48
- if (child.status === 'done') done++;
49
- const sub = countDescendantStatus(child);
50
- total += sub.total;
51
- done += sub.done;
52
- }
53
- return { total, done };
54
- }
55
-
56
- export default function TreeNode({ item, depth, projectId, onItemUpdate, onItemDelete, onTreeRefresh, selectMode, selected, onToggleSelect, defaultExpanded }: TreeNodeProps) {
57
- const [expanded, setExpanded] = useState(defaultExpanded ?? depth < 2);
58
- const [showDetail, setShowDetail] = useState(false);
59
- const [showRefine, setShowRefine] = useState(false);
60
- const hasChildren = item.children.length > 0;
61
- const baseCfg = typeConfig[item.item_type] || typeConfig.note;
62
- const cfg = item.status === 'done'
63
- ? { icon: '\u{2705}', color: 'var(--success)', label: baseCfg.label }
64
- : item.status === 'in_progress'
65
- ? { ...baseCfg, color: 'var(--primary)' }
66
- : baseCfg;
67
- const childStats = hasChildren ? countDescendantStatus(item) : null;
68
-
69
- const handleDelete = (e: React.MouseEvent) => {
70
- e.stopPropagation();
71
- onItemDelete(item.id);
72
- };
73
-
74
- const handlePinToggle = (e: React.MouseEvent) => {
75
- e.stopPropagation();
76
- onItemUpdate(item.id, { is_pinned: !item.is_pinned });
77
- };
78
-
79
- const handleRefineToggle = (e: React.MouseEvent) => {
80
- e.stopPropagation();
81
- setShowRefine(!showRefine);
82
- };
83
-
84
- const isDone = item.status === 'done';
85
-
86
- return (
87
- <div className="select-none">
88
- <div
89
- className={`tree-node-row group ${isDone ? 'opacity-50' : ''}`}
90
- style={{
91
- paddingLeft: `${depth * 20 + 8}px`,
92
- borderLeft: depth === 0 ? `3px solid hsl(${cfg.color})` : undefined,
93
- }}
94
- onClick={() => selectMode ? onToggleSelect?.(item.id) : setShowDetail(!showDetail)}
95
- >
96
- {selectMode && (
97
- <input
98
- type="checkbox"
99
- checked={selected?.has(item.id) ?? false}
100
- onChange={() => onToggleSelect?.(item.id)}
101
- onClick={(e) => e.stopPropagation()}
102
- className="w-3.5 h-3.5 flex-shrink-0 accent-accent"
103
- />
104
- )}
105
-
106
- {hasChildren ? (
107
- <button
108
- onClick={(e) => {
109
- e.stopPropagation();
110
- setExpanded(!expanded);
111
- }}
112
- className="text-muted-foreground hover:text-foreground text-[10px] w-4 flex-shrink-0 transition-transform"
113
- style={{ transform: expanded ? 'rotate(0deg)' : 'rotate(-90deg)' }}
114
- >
115
-
116
- </button>
117
- ) : (
118
- <span className="w-4 flex-shrink-0 text-center text-muted-foreground/30 text-[10px]">·</span>
119
- )}
120
-
121
- {/* Main content area */}
122
- <div className="flex-1 min-w-0 py-0.5">
123
- <div className="flex items-center gap-1.5">
124
- <span className="text-sm flex-shrink-0">{cfg.icon}</span>
125
- <span className={`text-sm truncate ${isDone ? 'line-through' : ''}`}>
126
- {item.title}
127
- </span>
128
- {item.is_pinned && (
129
- <span className="text-[10px] flex-shrink-0" title="고정됨">📌</span>
130
- )}
131
- {/* Child progress */}
132
- {childStats && childStats.total > 0 && (
133
- <span className="tree-progress-badge flex-shrink-0">
134
- {childStats.done}/{childStats.total}
135
- </span>
136
- )}
137
- </div>
138
- {/* Description subtitle */}
139
- {item.description && !showDetail && (
140
- <p className="text-[11px] text-muted-foreground/50 truncate mt-0.5 leading-tight">
141
- {item.description}
142
- </p>
143
- )}
144
- </div>
145
-
146
- {/* Right side controls */}
147
- <div className="flex items-center gap-1 flex-shrink-0">
148
- {/* Hover actions */}
149
- <div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
150
- <LockToggle
151
- isLocked={item.is_locked}
152
- onToggle={(locked) => onItemUpdate(item.id, { is_locked: locked })}
153
- />
154
- <button onClick={handlePinToggle} className="tree-icon-btn" title={item.is_pinned ? '고정 해제' : '고정'}>
155
- {item.is_pinned ? '📌' : '📍'}
156
- </button>
157
- <button onClick={handleRefineToggle} className="tree-action-btn" title="다듬기">
158
- 다듬기
159
- </button>
160
- <button onClick={handleDelete} className="tree-action-btn tree-action-btn-danger" title="삭제">
161
-
162
- </button>
163
- </div>
164
-
165
- {/* Always visible: priority + status */}
166
- <span className="tree-priority-dot" style={{
167
- background: item.priority === 'high' ? 'hsl(var(--destructive))'
168
- : item.priority === 'medium' ? 'hsl(var(--warning))'
169
- : 'hsl(var(--success))'
170
- }} title={item.priority} />
171
- <StatusBadge
172
- status={item.status}
173
- onStatusChange={(status) => onItemUpdate(item.id, { status })}
174
- />
175
- </div>
176
- </div>
177
-
178
- {showRefine && (
179
- <div style={{ marginLeft: `${depth * 20 + 28}px` }}>
180
- <RefinePopover
181
- itemId={item.id}
182
- projectId={projectId}
183
- title={item.title}
184
- description={item.description}
185
- onClose={() => setShowRefine(false)}
186
- onItemUpdate={onItemUpdate}
187
- onTreeRefresh={onTreeRefresh}
188
- />
189
- </div>
190
- )}
191
-
192
- {showDetail && (
193
- <ItemDetail
194
- itemId={item.id}
195
- projectId={projectId}
196
- title={item.title}
197
- description={item.description}
198
- itemType={item.item_type}
199
- priority={item.priority}
200
- status={item.status}
201
- isLocked={item.is_locked}
202
- depth={depth}
203
- />
204
- )}
205
-
206
- {expanded && hasChildren && (
207
- <div className={depth === 0 ? 'tree-children-group' : ''}>
208
- {item.children.map((child) => (
209
- <TreeNode
210
- key={child.id}
211
- item={child}
212
- depth={depth + 1}
213
- projectId={projectId}
214
- onItemUpdate={onItemUpdate}
215
- onItemDelete={onItemDelete}
216
- onTreeRefresh={onTreeRefresh}
217
- selectMode={selectMode}
218
- selected={selected}
219
- onToggleSelect={onToggleSelect}
220
- defaultExpanded={defaultExpanded}
221
- />
222
- ))}
223
- </div>
224
- )}
225
- </div>
226
- );
227
- }
@@ -1,304 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import TreeNode from './TreeNode';
5
- import CardView from './CardView';
6
-
7
- interface IItemTree {
8
- id: string;
9
- title: string;
10
- description: string;
11
- item_type: string;
12
- priority: string;
13
- status: string;
14
- is_locked: boolean;
15
- is_pinned: boolean;
16
- children: IItemTree[];
17
- }
18
-
19
- interface TreeViewProps {
20
- items: IItemTree[];
21
- loading: boolean;
22
- projectId: string;
23
- onItemUpdate: (itemId: string, data: Record<string, unknown>) => void;
24
- onItemDelete: (itemId: string) => void;
25
- onBulkDelete: (itemIds: string[] | 'all') => void;
26
- onBulkStatus: (status: string) => void;
27
- onTreeRefresh: (tree: IItemTree[]) => void;
28
- onCleanup?: () => void;
29
- cleaning?: boolean;
30
- }
31
-
32
- type ViewMode = 'tree' | 'card';
33
-
34
- function collectIds(item: IItemTree): string[] {
35
- return [item.id, ...item.children.flatMap(collectIds)];
36
- }
37
-
38
- function filterDone(items: IItemTree[]): IItemTree[] {
39
- return items
40
- .filter(item => item.status !== 'done')
41
- .map(item => ({
42
- ...item,
43
- children: filterDone(item.children),
44
- }));
45
- }
46
-
47
- export default function TreeView({ items, loading, projectId, onItemUpdate, onItemDelete, onBulkDelete, onBulkStatus, onTreeRefresh, onCleanup, cleaning }: TreeViewProps) {
48
- const [selectMode, setSelectMode] = useState(false);
49
- const [selected, setSelected] = useState<Set<string>>(new Set());
50
- const [hideDone, setHideDone] = useState(() => {
51
- if (typeof window !== 'undefined') {
52
- return localStorage.getItem('im-hide-done') !== 'false';
53
- }
54
- return true;
55
- });
56
-
57
- useEffect(() => {
58
- localStorage.setItem('im-hide-done', String(hideDone));
59
- }, [hideDone]);
60
- const [viewMode, setViewMode] = useState<ViewMode>('card');
61
- const [collapseAll, setCollapseAll] = useState(false);
62
- const [collapseKey, setCollapseKey] = useState(0);
63
-
64
- const toggleSelect = (id: string) => {
65
- setSelected(prev => {
66
- const next = new Set(prev);
67
- if (next.has(id)) {
68
- next.delete(id);
69
- } else {
70
- next.add(id);
71
- }
72
- return next;
73
- });
74
- };
75
-
76
- const handleDeleteSelected = () => {
77
- if (selected.size === 0) return;
78
- const ids = Array.from(selected);
79
- onBulkDelete(ids);
80
- setSelected(new Set());
81
- setSelectMode(false);
82
- };
83
-
84
- const handleDeleteAll = () => {
85
- onBulkDelete('all');
86
- setSelected(new Set());
87
- setSelectMode(false);
88
- };
89
-
90
- const handleSelectAll = () => {
91
- const allIds = items.flatMap(collectIds);
92
- setSelected(new Set(allIds));
93
- };
94
-
95
- const totalCount = items.reduce((sum, item) => sum + 1 + countChildren(item), 0);
96
- const doneCount = countByStatus(items, 'done');
97
- const displayItems = hideDone ? filterDone(items) : items;
98
-
99
- return (
100
- <div className="flex flex-col h-full">
101
- <div className="flex items-center justify-between px-4 py-2 border-b border-border">
102
- <div className="flex items-center gap-2">
103
- <h2 className="text-sm font-medium text-muted-foreground">구조화 뷰</h2>
104
- {totalCount > 0 && (
105
- <span className="text-xs text-muted-foreground/60">{totalCount}</span>
106
- )}
107
- {doneCount > 0 && (
108
- <button
109
- onClick={() => setHideDone(!hideDone)}
110
- className={`text-xs px-1.5 py-0.5 rounded transition-colors ${
111
- hideDone
112
- ? 'bg-accent/15 text-accent'
113
- : 'text-muted-foreground/50 hover:text-muted-foreground'
114
- }`}
115
- title={hideDone ? '완료 항목 표시' : '완료 항목 숨기기'}
116
- >
117
- {hideDone ? `+${doneCount} 숨김` : `${doneCount} 완료`}
118
- </button>
119
- )}
120
- {loading && (
121
- <span className="text-xs text-accent animate-pulse">AI 분석 중...</span>
122
- )}
123
- {cleaning && !loading && (
124
- <span className="text-xs text-muted-foreground animate-pulse">정리 중...</span>
125
- )}
126
- </div>
127
- <div className="flex items-center gap-2">
128
- {/* View mode toggle */}
129
- <div className="view-toggle">
130
- <button
131
- onClick={() => setViewMode('card')}
132
- className={`view-toggle-btn ${viewMode === 'card' ? 'view-toggle-btn-active' : ''}`}
133
- >
134
- 카드
135
- </button>
136
- <button
137
- onClick={() => setViewMode('tree')}
138
- className={`view-toggle-btn ${viewMode === 'tree' ? 'view-toggle-btn-active' : ''}`}
139
- >
140
- 트리
141
- </button>
142
- </div>
143
-
144
- {items.length > 0 && viewMode === 'tree' && (
145
- <div className="flex items-center gap-1">
146
- {selectMode ? (
147
- <>
148
- <button
149
- onClick={handleSelectAll}
150
- className="text-xs px-2 py-1 text-muted-foreground hover:text-foreground rounded transition-colors"
151
- >
152
- 전체선택
153
- </button>
154
- <button
155
- onClick={handleDeleteSelected}
156
- disabled={selected.size === 0}
157
- className="text-xs px-2 py-1 text-destructive hover:bg-destructive/10 rounded transition-colors disabled:opacity-30"
158
- >
159
- 선택삭제 ({selected.size})
160
- </button>
161
- <button
162
- onClick={() => { setSelectMode(false); setSelected(new Set()); }}
163
- className="text-xs px-2 py-1 text-muted-foreground hover:text-foreground rounded transition-colors"
164
- >
165
- 취소
166
- </button>
167
- </>
168
- ) : (
169
- <>
170
- {onCleanup && (
171
- <button
172
- onClick={onCleanup}
173
- disabled={cleaning || loading}
174
- className="text-xs px-2 py-1 text-accent hover:bg-accent/10 rounded transition-colors disabled:opacity-30"
175
- >
176
- 정리
177
- </button>
178
- )}
179
- <button
180
- onClick={() => { setCollapseAll(!collapseAll); setCollapseKey(k => k + 1); }}
181
- className="text-xs px-2 py-1 text-muted-foreground hover:text-foreground rounded transition-colors"
182
- title={collapseAll ? '전체 펼치기' : '전체 접기'}
183
- >
184
- {collapseAll ? '펼치기' : '접기'}
185
- </button>
186
- <button
187
- onClick={() => onBulkStatus('done')}
188
- className="text-xs px-2 py-1 text-success hover:bg-success/10 rounded transition-colors"
189
- >
190
- 전체완료
191
- </button>
192
- <button
193
- onClick={() => setSelectMode(true)}
194
- className="text-xs px-2 py-1 text-muted-foreground hover:text-foreground rounded transition-colors"
195
- >
196
- 선택
197
- </button>
198
- <button
199
- onClick={handleDeleteAll}
200
- className="text-xs px-2 py-1 text-destructive hover:bg-destructive/10 rounded transition-colors"
201
- >
202
- 전체삭제
203
- </button>
204
- </>
205
- )}
206
- </div>
207
- )}
208
-
209
- {items.length > 0 && viewMode === 'card' && (
210
- <div className="flex items-center gap-1">
211
- {onCleanup && (
212
- <button
213
- onClick={onCleanup}
214
- disabled={cleaning || loading}
215
- className="text-xs px-2 py-1 text-accent hover:bg-accent/10 rounded transition-colors disabled:opacity-30"
216
- >
217
- 정리
218
- </button>
219
- )}
220
- <button
221
- onClick={() => onBulkStatus('done')}
222
- className="text-xs px-2 py-1 text-success hover:bg-success/10 rounded transition-colors"
223
- >
224
- 전체완료
225
- </button>
226
- <button
227
- onClick={handleDeleteAll}
228
- className="text-xs px-2 py-1 text-destructive hover:bg-destructive/10 rounded transition-colors"
229
- >
230
- 전체삭제
231
- </button>
232
- </div>
233
- )}
234
- </div>
235
- </div>
236
-
237
- <div className="flex-1 overflow-auto">
238
- {displayItems.length === 0 && !loading ? (
239
- <div className="flex flex-col items-center justify-center h-full text-muted-foreground text-sm p-4">
240
- {items.length > 0 && hideDone ? (
241
- <>
242
- <div className="text-4xl mb-3">&#x2705;</div>
243
- <p className="mb-2">모든 항목이 완료되었습니다</p>
244
- <button
245
- onClick={() => setHideDone(false)}
246
- className="text-xs text-accent hover:underline"
247
- >
248
- 완료 항목 보기
249
- </button>
250
- </>
251
- ) : (
252
- <>
253
- <div className="text-4xl mb-3">&#x1F5C2;</div>
254
- <p className="mb-2">아직 구조화된 항목이 없습니다</p>
255
- <p className="text-xs text-center">
256
- 왼쪽 패널에서 아이디어를 입력해보세요.
257
- <br />
258
- 입력을 멈추면 3초 후 AI가 자동으로 구조화합니다.
259
- </p>
260
- </>
261
- )}
262
- </div>
263
- ) : viewMode === 'card' ? (
264
- <CardView
265
- items={displayItems}
266
- onItemUpdate={onItemUpdate}
267
- onItemDelete={onItemDelete}
268
- />
269
- ) : (
270
- <div className="p-2">
271
- {displayItems.map((item) => (
272
- <TreeNode
273
- key={`${item.id}-${collapseKey}`}
274
- item={item}
275
- depth={0}
276
- projectId={projectId}
277
- onItemUpdate={onItemUpdate}
278
- onItemDelete={onItemDelete}
279
- onTreeRefresh={onTreeRefresh}
280
- selectMode={selectMode}
281
- selected={selected}
282
- onToggleSelect={toggleSelect}
283
- defaultExpanded={!collapseAll}
284
- />
285
- ))}
286
- </div>
287
- )}
288
- </div>
289
- </div>
290
- );
291
- }
292
-
293
- function countChildren(item: IItemTree): number {
294
- return item.children.reduce((sum, child) => sum + 1 + countChildren(child), 0);
295
- }
296
-
297
- function countByStatus(items: IItemTree[], status: string): number {
298
- let count = 0;
299
- for (const item of items) {
300
- if (item.status === status) count++;
301
- count += countByStatus(item.children, status);
302
- }
303
- return count;
304
- }
@@ -1,71 +0,0 @@
1
- import { runStructureWithQuestions, type IStructuredItem } from './client';
2
- import { replaceItems } from '../db/queries/items';
3
- import { getRecentConversations, addMessage } from '../db/queries/conversations';
4
- import { getBrainstorm } from '../db/queries/brainstorms';
5
- import { getProjectContextSummary } from '../db/queries/context';
6
- import { resolveMemos, createMemosFromQuestions } from '../db/queries/memos';
7
- import type { IItemTree, IMemo, IConversation } from '@/types';
8
-
9
- export async function handleChatResponse(
10
- projectId: string,
11
- brainstormId: string,
12
- userMessage: string,
13
- ): Promise<{ items: IItemTree[]; memos: IMemo[]; messages: IConversation[] }> {
14
- // Save user message
15
- const userMsg = addMessage(projectId, 'user', userMessage);
16
-
17
- // Load brainstorm content
18
- const brainstorm = getBrainstorm(projectId);
19
- if (!brainstorm || !brainstorm.content.trim()) {
20
- return { items: [], memos: [], messages: [userMsg] };
21
- }
22
-
23
- // Resolve old memos before generating new ones
24
- resolveMemos(projectId);
25
-
26
- // Load full conversation history (limited to 20)
27
- const history = getRecentConversations(projectId, 20);
28
- const historyForAi = history.map(h => ({
29
- role: h.role,
30
- content: h.content,
31
- }));
32
-
33
- // AI call with updated conversation context + project docs
34
- const projectContext = getProjectContextSummary(projectId) || undefined;
35
- const result = await runStructureWithQuestions(brainstorm.content, historyForAi, projectContext);
36
-
37
- // Replace items in DB
38
- const dbItems = mapToDbFormat(result.items as IStructuredItem[]);
39
- const tree = replaceItems(projectId, brainstormId, dbItems);
40
-
41
- // Build AI response + new memos
42
- const newMessages: IConversation[] = [userMsg];
43
- let memos: IMemo[] = [];
44
-
45
- if (result.questions.length > 0) {
46
- const messageContent = result.questions
47
- .map((q, i) => `${i + 1}. ${q.question}`)
48
- .join('\n');
49
-
50
- const aiMsg = addMessage(projectId, 'assistant', messageContent);
51
- newMessages.push(aiMsg);
52
- memos = createMemosFromQuestions(projectId, aiMsg.id, result.questions);
53
- } else {
54
- // Even without questions, acknowledge the refinement
55
- const aiMsg = addMessage(projectId, 'assistant', '답변을 반영하여 구조를 업데이트했습니다.');
56
- newMessages.push(aiMsg);
57
- }
58
-
59
- return { items: tree, memos, messages: newMessages };
60
- }
61
-
62
- function mapToDbFormat(items: IStructuredItem[]): Parameters<typeof replaceItems>[2] {
63
- return items.map((item) => ({
64
- parent_id: null,
65
- title: item.title,
66
- description: item.description,
67
- item_type: item.item_type,
68
- priority: item.priority,
69
- children: item.children ? mapToDbFormat(item.children) : undefined,
70
- }));
71
- }