Skip to content

Commit db0e94a

Browse files
authored
feat: add hierarchical prompt tree relationships
Merge contributor hierarchical prompt tree work with follow-up hardening and prompt relationship design docs.
2 parents 28935b5 + 925aed0 commit db0e94a

18 files changed

Lines changed: 1271 additions & 134 deletions

File tree

apps/desktop/src/main/ipc/prompt.ipc.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ export function registerPromptIPC(db: PromptDB, folderDb: FolderDB, rawDb: Datab
5151
return ordered;
5252
};
5353

54+
const assertPromptMoveInput = (
55+
promptId: string,
56+
newParentId: string | null,
57+
newOrder: number,
58+
) => {
59+
if (typeof promptId !== 'string' || promptId.trim().length === 0) {
60+
throw new Error('Prompt id is required');
61+
}
62+
if (
63+
newParentId !== null &&
64+
(typeof newParentId !== 'string' || newParentId.trim().length === 0)
65+
) {
66+
throw new Error('Parent prompt id must be null or a non-empty string');
67+
}
68+
if (!Number.isFinite(newOrder) || newOrder < 0) {
69+
throw new Error('Prompt order must be a non-negative number');
70+
}
71+
};
72+
5473
// Create Prompt
5574
// 创建 Prompt
5675
ipcMain.handle(IPC_CHANNELS.PROMPT_CREATE, async (_, data: CreatePromptDTO) => {
@@ -249,4 +268,11 @@ export function registerPromptIPC(db: PromptDB, folderDb: FolderDB, rawDb: Datab
249268
db.insertVersionDirect(version);
250269
return true;
251270
});
271+
272+
ipcMain.handle(IPC_CHANNELS.PROMPT_MOVE, async (_, promptId: string, newParentId: string | null, newOrder: number) => {
273+
assertPromptMoveInput(promptId, newParentId, newOrder);
274+
db.movePrompt(promptId, newParentId, newOrder);
275+
syncWorkspace();
276+
return true;
277+
});
252278
}

apps/desktop/src/preload/api/prompt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ export const promptApi = {
4040
prompts: Prompt[];
4141
versions: PromptVersion[];
4242
}) => ipcRenderer.invoke(IPC_CHANNELS.PROMPT_MIGRATE_IDB_BATCH, payload),
43+
move: (promptId: string, newParentId: string | null, newOrder: number) =>
44+
ipcRenderer.invoke(IPC_CHANNELS.PROMPT_MOVE, promptId, newParentId, newOrder),
4345
};

apps/desktop/src/renderer/components/layout/MainContent.tsx

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ const SkillManager = lazy(() => import('../skill/SkillManager').then(m => ({ def
2121
const RulesManager = lazy(() => import('../rules/RulesManager').then(m => ({ default: m.RulesManager })));
2222
const EditPromptModal = lazy(() => import('../prompt/EditPromptModal').then(m => ({ default: m.EditPromptModal })));
2323
const PromptQuickRewriteDialog = lazy(() => import('../prompt/PromptQuickRewriteDialog').then(m => ({ default: m.PromptQuickRewriteDialog })));
24-
const PromptTableView = lazy(() => import('../prompt/PromptTableView').then(m => ({ default: m.PromptTableView })));
2524
const PromptGalleryView = lazy(() => import('../prompt/PromptGalleryView').then(m => ({ default: m.PromptGalleryView })));
2625
const PromptKanbanView = lazy(() => import('../prompt/PromptKanbanView').then(m => ({ default: m.PromptKanbanView })));
26+
const PromptListView = lazy(() => import('../prompt/PromptListView').then(m => ({ default: m.PromptListView })));
2727
const AiTestModal = lazy(() => import('../prompt/AiTestModal').then(m => ({ default: m.AiTestModal })));
2828
const PromptDetailModal = lazy(() => import('../prompt/PromptDetailModal').then(m => ({ default: m.PromptDetailModal })));
2929
const VariableInputModal = lazy(() => import('../prompt/VariableInputModal').then(m => ({ default: m.VariableInputModal })));
@@ -367,6 +367,7 @@ function PromptSkillMainContent() {
367367
const sortOrder = usePromptStore((state) => state.sortOrder);
368368
const viewMode = usePromptStore((state) => state.viewMode);
369369
const incrementUsageCount = usePromptStore((state) => state.incrementUsageCount);
370+
const movePrompt = usePromptStore((state) => state.movePrompt);
370371
// Resizable prompt-list pane width (#119)
371372
const promptListPaneWidth = useUIStore((state) => state.promptListPaneWidth);
372373
const setPromptListPaneWidth = useUIStore(
@@ -1928,23 +1929,16 @@ function PromptSkillMainContent() {
19281929
</Suspense>
19291930
) : (
19301931
<>
1931-
{/* List view mode */}
1932-
{/* 列表视图模式 */}
1932+
{/* Gallery view */}
1933+
{/* Gallery 视图 */}
19331934
<div
1934-
className={getViewClass('list')}
1935+
className={getViewClass('gallery')}
19351936
>
1936-
1937-
1938-
{/* Top: sort + view switch */}
1939-
{/* 顶部:排序 + 视图切换 */}
19401937
<PromptListHeader count={sortedPrompts.length} />
1941-
1942-
{/* Table view */}
1943-
{/* 表格视图 */}
1944-
<div className="flex-1 overflow-hidden">
1938+
{viewMode === 'gallery' && (
19451939
<Suspense fallback={loadingFallback}>
1946-
<PromptTableView
1947-
prompts={sortedPrompts}
1940+
<PromptGalleryView
1941+
prompts={visiblePrompts}
19481942
highlightTerms={highlightTerms}
19491943
onSelect={(id) => selectPrompt(id)}
19501944
onToggleFavorite={toggleFavorite}
@@ -1954,25 +1948,21 @@ function PromptSkillMainContent() {
19541948
onAiTest={handleAiTestFromTable}
19551949
onVersionHistory={handleVersionHistory}
19561950
onViewDetail={handleViewDetail}
1957-
aiResults={aiResponseCache}
1958-
onBatchFavorite={handleBatchFavorite}
1959-
onBatchMove={handleBatchMove}
1960-
onBatchDelete={handleBatchDelete}
19611951
onContextMenu={handleContextMenu}
19621952
/>
19631953
</Suspense>
1964-
</div>
1954+
)}
19651955
</div>
19661956

1967-
{/* Gallery view */}
1968-
{/* Gallery 视图 */}
1957+
{/* Kanban view */}
1958+
{/* 看板视图 */}
19691959
<div
1970-
className={getViewClass('gallery')}
1960+
className={getViewClass('kanban')}
19711961
>
19721962
<PromptListHeader count={sortedPrompts.length} />
1973-
{viewMode === 'gallery' && (
1963+
{viewMode === 'kanban' && (
19741964
<Suspense fallback={loadingFallback}>
1975-
<PromptGalleryView
1965+
<PromptKanbanView
19761966
prompts={visiblePrompts}
19771967
highlightTerms={highlightTerms}
19781968
onSelect={(id) => selectPrompt(id)}
@@ -1989,26 +1979,23 @@ function PromptSkillMainContent() {
19891979
)}
19901980
</div>
19911981

1992-
{/* Kanban view */}
1993-
{/* 看板视图 */}
1982+
{/* List view mode: hierarchical list with drag-and-drop */}
1983+
{/* 列表视图模式:分层列表支持拖拽 */}
19941984
<div
1995-
className={getViewClass('kanban')}
1985+
className={getViewClass('list')}
19961986
>
19971987
<PromptListHeader count={sortedPrompts.length} />
1998-
{viewMode === 'kanban' && (
1988+
{viewMode === 'list' && (
19991989
<Suspense fallback={loadingFallback}>
2000-
<PromptKanbanView
1990+
<PromptListView
20011991
prompts={visiblePrompts}
2002-
highlightTerms={highlightTerms}
1992+
selectedId={selectedId}
1993+
selectedIds={selectedIds}
20031994
onSelect={(id) => selectPrompt(id)}
20041995
onToggleFavorite={toggleFavorite}
20051996
onCopy={handleCopyPrompt}
2006-
onEdit={(prompt) => setEditingPrompt(prompt)}
2007-
onDelete={handleDeletePrompt}
2008-
onAiTest={handleAiTestFromTable}
2009-
onVersionHistory={handleVersionHistory}
2010-
onViewDetail={handleViewDetail}
20111997
onContextMenu={handleContextMenu}
1998+
onMovePrompt={movePrompt}
20121999
/>
20132000
</Suspense>
20142001
)}

0 commit comments

Comments
 (0)