idea-manager 1.6.1 → 1.7.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 +18 -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/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-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/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.js +1 -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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.js +1 -1
- 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/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/search/route.js +127 -0
- package/.next/server/app/api/search/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route.js +1 -0
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/version/route.js +1 -0
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -0
- 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 +10 -7
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +2 -2
- package/.next/static/chunks/app/_global-error/page-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/archive/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/filesystem/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/filesystem/tree/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/global-memo/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/health/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/projects/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/search/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/sync/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/update/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/api/version/route-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/app/page-9a1dc101e82c397c.js +28 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-e6a77f238d2cdbb9.js +1 -0
- package/.next/static/css/eab748b03f49c43a.css +3 -0
- package/.next/static/mxrEVQX3r5YlDPZgpDvSp/_buildManifest.js +1 -0
- package/package.json +1 -1
- package/src/app/api/search/route.ts +149 -0
- package/src/app/api/update/route.ts +52 -0
- package/src/app/api/version/route.ts +68 -0
- package/src/components/search/GlobalSearch.tsx +156 -0
- package/src/components/search/QuickCapture.tsx +208 -0
- package/src/components/tabs/TabBar.tsx +2 -0
- package/src/components/tabs/TabShell.tsx +4 -0
- package/src/components/task/CommandPalette.tsx +48 -2
- package/src/components/task/NoteEditor.tsx +16 -1
- package/src/components/task/TaskChat.tsx +31 -20
- package/src/components/task/TaskDetail.tsx +62 -2
- package/src/components/update/UpdateButton.tsx +190 -0
- package/src/components/workspace/WorkspacePanel.tsx +1 -0
- package/.next/static/chunks/app/_global-error/page-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/archive/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/filesystem/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/filesystem/tree/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/global-memo/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/health/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/projects/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/api/sync/route-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/app/page-6a511af64da7531f.js +0 -28
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-6ec0e723e471f87a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-6ec0e723e471f87a.js +0 -1
- package/.next/static/css/cc32379d0efa7d1d.css +0 -3
- package/.next/static/eQXRVHrJt1cKjgp4hKYm8/_buildManifest.js +0 -1
- /package/.next/static/{eQXRVHrJt1cKjgp4hKYm8 → mxrEVQX3r5YlDPZgpDvSp}/_ssgManifest.js +0 -0
|
@@ -19,6 +19,28 @@ const COMMANDS: { key: RefineCommand; label: string; hint: string }[] = [
|
|
|
19
19
|
{ key: 'custom', label: '직접 입력…', hint: '임의 명령을 프롬프트로 전달' },
|
|
20
20
|
];
|
|
21
21
|
|
|
22
|
+
const HISTORY_KEY = 'im-refine-custom-history';
|
|
23
|
+
const HISTORY_MAX = 8;
|
|
24
|
+
|
|
25
|
+
function loadHistory(): string[] {
|
|
26
|
+
if (typeof window === 'undefined') return [];
|
|
27
|
+
try {
|
|
28
|
+
const raw = localStorage.getItem(HISTORY_KEY);
|
|
29
|
+
if (!raw) return [];
|
|
30
|
+
const arr = JSON.parse(raw);
|
|
31
|
+
return Array.isArray(arr) ? arr.filter((v): v is string => typeof v === 'string') : [];
|
|
32
|
+
} catch { return []; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pushHistory(entry: string) {
|
|
36
|
+
if (typeof window === 'undefined') return;
|
|
37
|
+
const trimmed = entry.trim();
|
|
38
|
+
if (!trimmed) return;
|
|
39
|
+
const prev = loadHistory().filter(x => x !== trimmed);
|
|
40
|
+
const next = [trimmed, ...prev].slice(0, HISTORY_MAX);
|
|
41
|
+
try { localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); } catch { /* quota */ }
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
export default function CommandPalette({
|
|
23
45
|
open,
|
|
24
46
|
hasSelection,
|
|
@@ -33,6 +55,7 @@ export default function CommandPalette({
|
|
|
33
55
|
const [idx, setIdx] = useState(0);
|
|
34
56
|
const [customMode, setCustomMode] = useState(false);
|
|
35
57
|
const [custom, setCustom] = useState('');
|
|
58
|
+
const [history, setHistory] = useState<string[]>([]);
|
|
36
59
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
37
60
|
|
|
38
61
|
useEffect(() => {
|
|
@@ -44,7 +67,10 @@ export default function CommandPalette({
|
|
|
44
67
|
}, [open]);
|
|
45
68
|
|
|
46
69
|
useEffect(() => {
|
|
47
|
-
if (customMode)
|
|
70
|
+
if (customMode) {
|
|
71
|
+
inputRef.current?.focus();
|
|
72
|
+
setHistory(loadHistory());
|
|
73
|
+
}
|
|
48
74
|
}, [customMode]);
|
|
49
75
|
|
|
50
76
|
if (!open) return null;
|
|
@@ -61,6 +87,7 @@ export default function CommandPalette({
|
|
|
61
87
|
const submitCustom = () => {
|
|
62
88
|
const t = custom.trim();
|
|
63
89
|
if (!t) return;
|
|
90
|
+
pushHistory(t);
|
|
64
91
|
onRun('custom', t);
|
|
65
92
|
};
|
|
66
93
|
|
|
@@ -114,11 +141,30 @@ export default function CommandPalette({
|
|
|
114
141
|
onChange={(e) => setCustom(e.target.value)}
|
|
115
142
|
onKeyDown={(e) => {
|
|
116
143
|
if (e.key === 'Enter') submitCustom();
|
|
117
|
-
if (e.key === 'Escape')
|
|
144
|
+
if (e.key === 'Escape') { e.preventDefault(); setCustomMode(false); }
|
|
118
145
|
}}
|
|
119
146
|
placeholder="예: 이 부분 markdown 표로 만들어줘"
|
|
120
147
|
className="w-full bg-input border border-border rounded-md px-3 py-2 text-sm focus:border-primary focus:outline-none"
|
|
121
148
|
/>
|
|
149
|
+
{history.length > 0 && (
|
|
150
|
+
<div className="flex flex-col gap-1.5">
|
|
151
|
+
<div className="text-[10px] uppercase tracking-wider text-muted-foreground/70">최근 명령</div>
|
|
152
|
+
<div className="flex flex-wrap gap-1.5">
|
|
153
|
+
{history.map((h) => (
|
|
154
|
+
<button
|
|
155
|
+
key={h}
|
|
156
|
+
onClick={() => setCustom(h)}
|
|
157
|
+
title={h}
|
|
158
|
+
className="text-xs px-2 py-1 rounded border border-border text-muted-foreground
|
|
159
|
+
hover:text-foreground hover:border-muted-foreground transition-colors
|
|
160
|
+
max-w-[220px] truncate text-left"
|
|
161
|
+
>
|
|
162
|
+
{h}
|
|
163
|
+
</button>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
122
168
|
<div className="flex justify-end gap-2">
|
|
123
169
|
<button onClick={() => setCustomMode(false)} className="text-xs text-muted-foreground px-2 py-1">뒤로</button>
|
|
124
170
|
<button
|
|
@@ -285,18 +285,32 @@ const mdHighlight = HighlightStyle.define([
|
|
|
285
285
|
// ─────────────────────────────────────────────────────────────
|
|
286
286
|
// Component
|
|
287
287
|
// ─────────────────────────────────────────────────────────────
|
|
288
|
+
// Extract the current bullet/checkbox line text suitable for promoting to a task.
|
|
289
|
+
// Returns the "content" portion (without the `- [ ]` marker) and the line range.
|
|
290
|
+
export function getPromotableLine(view: EditorView): { content: string; from: number; to: number } | null {
|
|
291
|
+
const state = view.state;
|
|
292
|
+
const pos = state.selection.main.head;
|
|
293
|
+
const line = state.doc.lineAt(pos);
|
|
294
|
+
const m = line.text.match(/^(\s*)([-*+])\s(?:\[[ xX]\]\s)?(.*)$/);
|
|
295
|
+
if (!m) return null;
|
|
296
|
+
const content = m[3]?.trim();
|
|
297
|
+
if (!content) return null;
|
|
298
|
+
return { content, from: line.from, to: line.to };
|
|
299
|
+
}
|
|
300
|
+
|
|
288
301
|
export interface NoteEditorProps {
|
|
289
302
|
value: string;
|
|
290
303
|
onChange: (v: string) => void;
|
|
291
304
|
onBlur?: () => void;
|
|
292
305
|
onOpenCommand?: () => void;
|
|
306
|
+
onPromoteLine?: () => void;
|
|
293
307
|
placeholder?: string;
|
|
294
308
|
/** Extra text blobs (sibling tasks, brainstorm, …) to widen the autocomplete corpus. */
|
|
295
309
|
extraCorpus?: string[];
|
|
296
310
|
}
|
|
297
311
|
|
|
298
312
|
const NoteEditor = forwardRef<ReactCodeMirrorRef, NoteEditorProps>(function NoteEditor(
|
|
299
|
-
{ value, onChange, onBlur, onOpenCommand, placeholder, extraCorpus },
|
|
313
|
+
{ value, onChange, onBlur, onOpenCommand, onPromoteLine, placeholder, extraCorpus },
|
|
300
314
|
ref,
|
|
301
315
|
) {
|
|
302
316
|
// Mutable ref keeps the plugin in sync with the latest corpus without
|
|
@@ -315,6 +329,7 @@ const NoteEditor = forwardRef<ReactCodeMirrorRef, NoteEditorProps>(function Note
|
|
|
315
329
|
{ key: 'Escape', run: dismissGhost },
|
|
316
330
|
{ key: 'Enter', run: continueList },
|
|
317
331
|
{ key: 'Mod-k', run: () => { onOpenCommand?.(); return true; } },
|
|
332
|
+
{ key: 'Mod-Shift-t', run: () => { onPromoteLine?.(); return true; } },
|
|
318
333
|
])),
|
|
319
334
|
EditorView.lineWrapping,
|
|
320
335
|
EditorView.theme({
|
|
@@ -149,27 +149,38 @@ export default function TaskChat({
|
|
|
149
149
|
노트 작성을 도와드립니다. 질문하거나 "이 부분 정리해줘" 같이 요청해보세요
|
|
150
150
|
</div>
|
|
151
151
|
)}
|
|
152
|
-
{messages.filter(msg => msg.content).map((msg) =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
{messages.filter(msg => msg.content).map((msg) => {
|
|
153
|
+
const isProgress = msg.role === 'assistant' && msg.content.startsWith('[진행 중]');
|
|
154
|
+
return (
|
|
155
|
+
<div key={msg.id} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
|
|
156
|
+
{isProgress && (
|
|
157
|
+
<div className="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-warning mb-0.5 pl-1">
|
|
158
|
+
<span className="inline-block w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
|
|
159
|
+
Watch 실행 중 · 실시간 출력
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
<div className={`max-w-[90%] px-3 py-2 rounded-lg text-sm leading-relaxed ${
|
|
163
|
+
msg.role === 'user'
|
|
164
|
+
? 'bg-accent text-white rounded-br-sm whitespace-pre-wrap'
|
|
165
|
+
: isProgress
|
|
166
|
+
? 'bg-warning/10 text-foreground rounded-bl-sm chat-markdown border border-warning/30'
|
|
167
|
+
: 'bg-muted text-foreground rounded-bl-sm chat-markdown'
|
|
168
|
+
}`}>
|
|
169
|
+
{msg.role === 'assistant'
|
|
170
|
+
? <ReactMarkdown remarkPlugins={[remarkGfm]}>{msg.content}</ReactMarkdown>
|
|
171
|
+
: msg.content}
|
|
172
|
+
</div>
|
|
173
|
+
{msg.role === 'assistant' && !isProgress && (
|
|
174
|
+
<button
|
|
175
|
+
onClick={() => onInsertToNote(msg.content)}
|
|
176
|
+
className="text-xs text-muted-foreground hover:text-primary mt-0.5 px-1 transition-colors"
|
|
177
|
+
>
|
|
178
|
+
↓ 노트에 삽입
|
|
179
|
+
</button>
|
|
180
|
+
)}
|
|
162
181
|
</div>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
onClick={() => onInsertToNote(msg.content)}
|
|
166
|
-
className="text-xs text-muted-foreground hover:text-primary mt-0.5 px-1 transition-colors"
|
|
167
|
-
>
|
|
168
|
-
↓ 노트에 삽입
|
|
169
|
-
</button>
|
|
170
|
-
)}
|
|
171
|
-
</div>
|
|
172
|
-
))}
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
173
184
|
{loading && (
|
|
174
185
|
<div className="flex gap-1 px-2 py-2">
|
|
175
186
|
<div className="w-1.5 h-1.5 rounded-full bg-muted-foreground animate-bounce" style={{ animationDelay: '0ms' }} />
|
|
@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
|
4
4
|
import type { ITask, TaskStatus, ItemPriority } from '@/types';
|
|
5
5
|
import StatusFlow from './StatusFlow';
|
|
6
6
|
import TaskChat from './TaskChat';
|
|
7
|
-
import NoteEditor from './NoteEditor';
|
|
7
|
+
import NoteEditor, { getPromotableLine } from './NoteEditor';
|
|
8
8
|
import CommandPalette, { type RefineCommand } from './CommandPalette';
|
|
9
9
|
import type { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
|
10
10
|
|
|
@@ -15,6 +15,7 @@ export default function TaskDetail({
|
|
|
15
15
|
siblingTasks,
|
|
16
16
|
onUpdate,
|
|
17
17
|
onDelete,
|
|
18
|
+
onTaskPromoted,
|
|
18
19
|
onChatStateChange,
|
|
19
20
|
}: {
|
|
20
21
|
task: ITask;
|
|
@@ -24,6 +25,8 @@ export default function TaskDetail({
|
|
|
24
25
|
siblingTasks?: ITask[];
|
|
25
26
|
onUpdate: (data: Partial<ITask>) => void;
|
|
26
27
|
onDelete: () => void;
|
|
28
|
+
/** Fired after a checkbox line is promoted to a new task. Parent should refresh its task list. */
|
|
29
|
+
onTaskPromoted?: (newTask: ITask) => void;
|
|
27
30
|
onChatStateChange?: (taskId: string, state: 'idle' | 'loading' | 'done') => void;
|
|
28
31
|
}) {
|
|
29
32
|
const [title, setTitle] = useState(task.title);
|
|
@@ -31,6 +34,16 @@ export default function TaskDetail({
|
|
|
31
34
|
const [editingTitle, setEditingTitle] = useState(false);
|
|
32
35
|
const [copied, setCopied] = useState(false);
|
|
33
36
|
const [chatOpen, setChatOpen] = useState(false);
|
|
37
|
+
const chatWasManuallyToggled = useRef(false);
|
|
38
|
+
|
|
39
|
+
// Auto-open the chat panel while the task is being executed by the watcher —
|
|
40
|
+
// that's where streaming progress shows up. Don't override a manual toggle
|
|
41
|
+
// the user made in this session.
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (task.status === 'testing' && !chatOpen && !chatWasManuallyToggled.current) {
|
|
44
|
+
setChatOpen(true);
|
|
45
|
+
}
|
|
46
|
+
}, [task.status, chatOpen]);
|
|
34
47
|
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
35
48
|
const [refining, setRefining] = useState(false);
|
|
36
49
|
const [refineElapsed, setRefineElapsed] = useState(0);
|
|
@@ -230,6 +243,46 @@ export default function TaskDetail({
|
|
|
230
243
|
setUndoSnapshot(null);
|
|
231
244
|
}, [undoSnapshot, task.id, onUpdate]);
|
|
232
245
|
|
|
246
|
+
const [promoteNotice, setPromoteNotice] = useState<string | null>(null);
|
|
247
|
+
|
|
248
|
+
const promoteCheckbox = useCallback(async () => {
|
|
249
|
+
const view = editorRef.current?.view;
|
|
250
|
+
if (!view) return;
|
|
251
|
+
const line = getPromotableLine(view);
|
|
252
|
+
if (!line) {
|
|
253
|
+
setRefineError('체크박스나 불릿 목록 줄에 커서를 두고 실행하세요');
|
|
254
|
+
setTimeout(() => setRefineError(null), 3000);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const titleText = line.content.slice(0, 200);
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetch(`/api/projects/${projectId}/sub-projects/${subProjectId}/tasks`, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: { 'Content-Type': 'application/json' },
|
|
262
|
+
body: JSON.stringify({ title: titleText }),
|
|
263
|
+
});
|
|
264
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
265
|
+
const newTask = await res.json() as ITask;
|
|
266
|
+
|
|
267
|
+
// Remove the promoted line from the note (and a trailing newline, if any).
|
|
268
|
+
const doc = view.state.doc.toString();
|
|
269
|
+
const before = doc.slice(0, line.from);
|
|
270
|
+
const afterRaw = doc.slice(line.to);
|
|
271
|
+
const trimmed = afterRaw.startsWith('\n') ? afterRaw.slice(1) : afterRaw;
|
|
272
|
+
const nextDoc = before + trimmed;
|
|
273
|
+
setDescription(nextDoc);
|
|
274
|
+
onUpdate({ description: nextDoc });
|
|
275
|
+
onTaskPromoted?.(newTask);
|
|
276
|
+
|
|
277
|
+
setPromoteNotice(`→ 태스크 "${titleText}" 생성됨`);
|
|
278
|
+
setTimeout(() => setPromoteNotice(null), 3000);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
const msg = err instanceof Error ? err.message : '태스크 생성 실패';
|
|
281
|
+
setRefineError(msg);
|
|
282
|
+
setTimeout(() => setRefineError(null), 4000);
|
|
283
|
+
}
|
|
284
|
+
}, [projectId, subProjectId, onUpdate, onTaskPromoted]);
|
|
285
|
+
|
|
233
286
|
const priorities: ItemPriority[] = ['high', 'medium', 'low'];
|
|
234
287
|
|
|
235
288
|
return (
|
|
@@ -302,7 +355,7 @@ export default function TaskDetail({
|
|
|
302
355
|
{copied ? '✓ Copied' : 'Copy as Prompt'}
|
|
303
356
|
</button>
|
|
304
357
|
<button
|
|
305
|
-
onClick={() => setChatOpen(v => !v)}
|
|
358
|
+
onClick={() => { chatWasManuallyToggled.current = true; setChatOpen(v => !v); }}
|
|
306
359
|
className={`text-xs px-2 py-0.5 rounded transition-colors border ${
|
|
307
360
|
chatOpen
|
|
308
361
|
? 'bg-accent/15 text-accent border-accent/30'
|
|
@@ -336,6 +389,7 @@ export default function TaskDetail({
|
|
|
336
389
|
onChange={setDescription}
|
|
337
390
|
onBlur={saveDescription}
|
|
338
391
|
onOpenCommand={openPalette}
|
|
392
|
+
onPromoteLine={promoteCheckbox}
|
|
339
393
|
extraCorpus={extraCorpus}
|
|
340
394
|
placeholder="자유롭게 작성하세요. 배경 · 목표 · 관련 파일 · 결정사항 · 질문 · 링크 등 뭐든..."
|
|
341
395
|
/>
|
|
@@ -353,6 +407,12 @@ export default function TaskDetail({
|
|
|
353
407
|
</button>
|
|
354
408
|
</div>
|
|
355
409
|
)}
|
|
410
|
+
{promoteNotice && (
|
|
411
|
+
<div className="absolute bottom-2 right-3 text-xs px-3 py-1.5 rounded bg-success/15 text-success flex items-center gap-2 shadow-lg border border-success/30">
|
|
412
|
+
<span>✓</span>
|
|
413
|
+
<span className="truncate max-w-[50ch]">{promoteNotice}</span>
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
356
416
|
{!refining && undoSnapshot && undoSnapshot.taskId === task.id && (
|
|
357
417
|
<div className="absolute bottom-2 right-3 text-xs px-2 py-1 rounded bg-accent/15 text-foreground flex items-center gap-2 shadow-lg border border-accent/30">
|
|
358
418
|
<span className="text-accent">✓</span>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
interface VersionInfo {
|
|
6
|
+
current: string;
|
|
7
|
+
latest: string | null;
|
|
8
|
+
updateAvailable: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UpdateResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
code?: number | null;
|
|
14
|
+
signal?: string | null;
|
|
15
|
+
stdout?: string;
|
|
16
|
+
stderr?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
durationMs?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // re-check hourly
|
|
22
|
+
const DISMISS_KEY = 'im-update-dismissed';
|
|
23
|
+
|
|
24
|
+
export default function UpdateButton() {
|
|
25
|
+
const [info, setInfo] = useState<VersionInfo | null>(null);
|
|
26
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
27
|
+
const [installing, setInstalling] = useState(false);
|
|
28
|
+
const [installed, setInstalled] = useState(false);
|
|
29
|
+
const [result, setResult] = useState<UpdateResult | null>(null);
|
|
30
|
+
const dismissedRef = useRef<string | null>(null);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
dismissedRef.current = typeof window !== 'undefined' ? localStorage.getItem(DISMISS_KEY) : null;
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const fetchVersion = useCallback(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch('/api/version');
|
|
39
|
+
if (!res.ok) return;
|
|
40
|
+
const data = await res.json() as VersionInfo;
|
|
41
|
+
setInfo(data);
|
|
42
|
+
} catch { /* offline or cold — ignore */ }
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
fetchVersion();
|
|
47
|
+
const id = setInterval(fetchVersion, CHECK_INTERVAL_MS);
|
|
48
|
+
return () => clearInterval(id);
|
|
49
|
+
}, [fetchVersion]);
|
|
50
|
+
|
|
51
|
+
const isDismissed = info?.latest !== null && info?.latest === dismissedRef.current;
|
|
52
|
+
const showBadge = !!info?.updateAvailable && !isDismissed;
|
|
53
|
+
|
|
54
|
+
const install = useCallback(async () => {
|
|
55
|
+
setInstalling(true);
|
|
56
|
+
setResult(null);
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch('/api/update', { method: 'POST' });
|
|
59
|
+
const data = await res.json() as UpdateResult;
|
|
60
|
+
setResult(data);
|
|
61
|
+
if (data.ok) {
|
|
62
|
+
setInstalled(true);
|
|
63
|
+
await fetchVersion();
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
setResult({ ok: false, error: e instanceof Error ? e.message : 'Network error' });
|
|
67
|
+
} finally {
|
|
68
|
+
setInstalling(false);
|
|
69
|
+
}
|
|
70
|
+
}, [fetchVersion]);
|
|
71
|
+
|
|
72
|
+
const dismiss = useCallback(() => {
|
|
73
|
+
if (info?.latest) {
|
|
74
|
+
try { localStorage.setItem(DISMISS_KEY, info.latest); } catch { /* quota */ }
|
|
75
|
+
dismissedRef.current = info.latest;
|
|
76
|
+
}
|
|
77
|
+
setInfo(prev => prev ? { ...prev, updateAvailable: false } : prev);
|
|
78
|
+
}, [info?.latest]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
{showBadge ? (
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setModalOpen(true)}
|
|
85
|
+
title={`IM v${info?.latest} 업데이트 가능 (현재 ${info?.current})`}
|
|
86
|
+
className="text-xs px-2 py-1 rounded-md border border-success/40 bg-success/15 text-success
|
|
87
|
+
hover:bg-success/25 transition-colors flex items-center gap-1.5 mr-2"
|
|
88
|
+
>
|
|
89
|
+
<span className="inline-block w-1.5 h-1.5 rounded-full bg-success animate-pulse" />
|
|
90
|
+
v{info?.latest} 업데이트
|
|
91
|
+
</button>
|
|
92
|
+
) : info ? (
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => setModalOpen(true)}
|
|
95
|
+
title={`현재 IM v${info.current} · 업데이트 확인`}
|
|
96
|
+
className="text-[10px] px-1.5 py-0.5 rounded text-muted-foreground/60 hover:text-muted-foreground transition-colors mr-2"
|
|
97
|
+
>
|
|
98
|
+
v{info.current}
|
|
99
|
+
</button>
|
|
100
|
+
) : null}
|
|
101
|
+
|
|
102
|
+
{modalOpen && (
|
|
103
|
+
<div
|
|
104
|
+
onClick={() => !installing && setModalOpen(false)}
|
|
105
|
+
className="fixed inset-0 z-[60] flex items-center justify-center"
|
|
106
|
+
style={{ background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(3px)' }}
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
onClick={(e) => e.stopPropagation()}
|
|
110
|
+
className="bg-card border border-border rounded-xl shadow-2xl w-full max-w-md animate-dialog-in p-5 flex flex-col gap-3"
|
|
111
|
+
>
|
|
112
|
+
<div className="flex items-start justify-between">
|
|
113
|
+
<div>
|
|
114
|
+
<div className="text-sm font-semibold text-foreground">IM 업데이트</div>
|
|
115
|
+
<div className="text-xs text-muted-foreground mt-0.5">
|
|
116
|
+
현재 <span className="text-foreground font-mono">v{info?.current}</span>
|
|
117
|
+
{info?.latest && (
|
|
118
|
+
<>
|
|
119
|
+
{' → '}
|
|
120
|
+
<span className={`font-mono ${info.updateAvailable ? 'text-success' : 'text-foreground'}`}>
|
|
121
|
+
v{info.latest}
|
|
122
|
+
</span>
|
|
123
|
+
</>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{!installing && (
|
|
128
|
+
<button onClick={() => setModalOpen(false)} className="text-muted-foreground hover:text-foreground text-lg leading-none">×</button>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{installed ? (
|
|
133
|
+
<div className="flex flex-col gap-2">
|
|
134
|
+
<div className="text-sm text-success flex items-center gap-2">
|
|
135
|
+
<span>✓</span>
|
|
136
|
+
<span>설치 완료</span>
|
|
137
|
+
{result?.durationMs && (
|
|
138
|
+
<span className="text-xs text-muted-foreground">({Math.round(result.durationMs / 1000)}s)</span>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
<div className="text-xs text-muted-foreground leading-relaxed">
|
|
142
|
+
새 버전을 반영하려면 <span className="font-mono text-foreground">im start</span> 프로세스를 재시작하세요.
|
|
143
|
+
PM2로 실행 중이면 <span className="font-mono text-foreground">pm2 restart idea-manager</span>로 즉시 반영됩니다.
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
) : installing ? (
|
|
147
|
+
<div className="flex items-center gap-2 text-sm text-foreground">
|
|
148
|
+
<span className="inline-block w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
|
|
149
|
+
<span>설치 중… (최대 3분)</span>
|
|
150
|
+
</div>
|
|
151
|
+
) : info?.updateAvailable ? (
|
|
152
|
+
<>
|
|
153
|
+
<div className="text-xs text-muted-foreground leading-relaxed">
|
|
154
|
+
<span className="font-mono">npm install -g idea-manager@latest</span>를 실행해 최신 버전을 설치합니다.
|
|
155
|
+
설치가 끝나면 재시작 안내가 표시됩니다.
|
|
156
|
+
</div>
|
|
157
|
+
<div className="flex justify-end gap-2 mt-1">
|
|
158
|
+
<button onClick={dismiss} className="text-xs text-muted-foreground px-2 py-1 hover:text-foreground transition-colors">
|
|
159
|
+
이 버전 건너뜀
|
|
160
|
+
</button>
|
|
161
|
+
<button
|
|
162
|
+
onClick={install}
|
|
163
|
+
className="text-xs px-3 py-1.5 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
|
164
|
+
>
|
|
165
|
+
지금 설치
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</>
|
|
169
|
+
) : (
|
|
170
|
+
<div className="text-xs text-muted-foreground">
|
|
171
|
+
최신 버전을 사용 중입니다.
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{result && !result.ok && (
|
|
176
|
+
<div className="mt-2 flex flex-col gap-1.5">
|
|
177
|
+
<div className="text-xs text-destructive">⚠ 설치 실패{result.code !== undefined && result.code !== null ? ` (exit ${result.code})` : ''}</div>
|
|
178
|
+
{(result.stderr || result.error) && (
|
|
179
|
+
<pre className="text-[10px] bg-muted/50 border border-border rounded p-2 max-h-40 overflow-auto whitespace-pre-wrap break-words text-muted-foreground">
|
|
180
|
+
{result.stderr || result.error}
|
|
181
|
+
</pre>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -536,6 +536,7 @@ export default function WorkspacePanel({
|
|
|
536
536
|
<TaskDetail task={selectedTask} projectId={id} subProjectId={selectedSubId!}
|
|
537
537
|
siblingTasks={tasks}
|
|
538
538
|
onUpdate={handleTaskUpdate} onDelete={handleTaskDelete}
|
|
539
|
+
onTaskPromoted={(newTask) => setTasks(prev => [...prev, newTask])}
|
|
539
540
|
onChatStateChange={(taskId, state) => {
|
|
540
541
|
setChatStates(prev => ({ ...prev, [taskId]: state }));
|
|
541
542
|
}} />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-6ec0e723e471f87a.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-6ec0e723e471f87a.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,212,363,460,514,595,711,770,772,819,822,851,860,896,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
|