Prompt gallery (#957)
- [x] show prompt instead of app in autocomplete - [x] use proper array/list for db (tags) - [x] don't do <dyad-prompt> - replace inline
This commit is contained in:
@@ -61,6 +61,9 @@ import { FileUploadsState } from "../utils/file_uploads_state";
|
||||
import { OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
|
||||
import { extractMentionedAppsCodebases } from "../utils/mention_apps";
|
||||
import { parseAppMentions } from "@/shared/parse_mention_apps";
|
||||
import { prompts as promptsTable } from "../../db/schema";
|
||||
import { inArray } from "drizzle-orm";
|
||||
import { replacePromptReference } from "../utils/replacePromptReference";
|
||||
|
||||
type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>;
|
||||
|
||||
@@ -274,6 +277,26 @@ export function registerChatStreamHandlers() {
|
||||
|
||||
// Add user message to database with attachment info
|
||||
let userPrompt = req.prompt + (attachmentInfo ? attachmentInfo : "");
|
||||
// Inline referenced prompt contents for mentions like @prompt:<id>
|
||||
try {
|
||||
const matches = Array.from(userPrompt.matchAll(/@prompt:(\d+)/g));
|
||||
if (matches.length > 0) {
|
||||
const ids = Array.from(new Set(matches.map((m) => Number(m[1]))));
|
||||
const referenced = await db
|
||||
.select()
|
||||
.from(promptsTable)
|
||||
.where(inArray(promptsTable.id, ids));
|
||||
if (referenced.length > 0) {
|
||||
const promptsMap: Record<number, string> = {};
|
||||
for (const p of referenced) {
|
||||
promptsMap[p.id] = p.content;
|
||||
}
|
||||
userPrompt = replacePromptReference(userPrompt, promptsMap);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Failed to inline referenced prompts:", e);
|
||||
}
|
||||
if (req.selectedComponent) {
|
||||
let componentSnippet = "[component snippet not available]";
|
||||
try {
|
||||
|
||||
91
src/ipc/handlers/prompt_handlers.ts
Normal file
91
src/ipc/handlers/prompt_handlers.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { IpcMainInvokeEvent } from "electron";
|
||||
import log from "electron-log";
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import { db } from "@/db";
|
||||
import { prompts } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
CreatePromptParamsDto,
|
||||
PromptDto,
|
||||
UpdatePromptParamsDto,
|
||||
} from "../ipc_types";
|
||||
|
||||
const logger = log.scope("prompt_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
|
||||
export function registerPromptHandlers() {
|
||||
handle("prompts:list", async (): Promise<PromptDto[]> => {
|
||||
const rows = db.select().from(prompts).all();
|
||||
return rows.map((r) => ({
|
||||
id: r.id!,
|
||||
title: r.title,
|
||||
description: r.description ?? null,
|
||||
content: r.content,
|
||||
createdAt: r.createdAt,
|
||||
updatedAt: r.updatedAt,
|
||||
}));
|
||||
});
|
||||
|
||||
handle(
|
||||
"prompts:create",
|
||||
async (
|
||||
_e: IpcMainInvokeEvent,
|
||||
params: CreatePromptParamsDto,
|
||||
): Promise<PromptDto> => {
|
||||
const { title, description, content } = params;
|
||||
if (!title || !content) {
|
||||
throw new Error("Title and content are required");
|
||||
}
|
||||
const result = db
|
||||
.insert(prompts)
|
||||
.values({
|
||||
title,
|
||||
description: description ?? null,
|
||||
content,
|
||||
})
|
||||
.run();
|
||||
|
||||
const id = Number(result.lastInsertRowid);
|
||||
const row = db.select().from(prompts).where(eq(prompts.id, id)).get();
|
||||
if (!row) throw new Error("Failed to fetch created prompt");
|
||||
return {
|
||||
id: row.id!,
|
||||
title: row.title,
|
||||
description: row.description ?? null,
|
||||
content: row.content,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"prompts:update",
|
||||
async (
|
||||
_e: IpcMainInvokeEvent,
|
||||
params: UpdatePromptParamsDto,
|
||||
): Promise<void> => {
|
||||
const { id, title, description, content } = params;
|
||||
if (!id) throw new Error("Prompt id is required");
|
||||
if (!title || !content) throw new Error("Title and content are required");
|
||||
const now = new Date();
|
||||
db.update(prompts)
|
||||
.set({
|
||||
title,
|
||||
description: description ?? null,
|
||||
content,
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(prompts.id, id))
|
||||
.run();
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"prompts:delete",
|
||||
async (_e: IpcMainInvokeEvent, id: number): Promise<void> => {
|
||||
if (!id) throw new Error("Prompt id is required");
|
||||
db.delete(prompts).where(eq(prompts.id, id)).run();
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -58,6 +58,9 @@ import type {
|
||||
RevertVersionResponse,
|
||||
RevertVersionParams,
|
||||
RespondToAppInputParams,
|
||||
PromptDto,
|
||||
CreatePromptParamsDto,
|
||||
UpdatePromptParamsDto,
|
||||
} from "./ipc_types";
|
||||
import type { Template } from "../shared/templates";
|
||||
import type { AppChatContext, ProposalResult } from "@/lib/schemas";
|
||||
@@ -1069,4 +1072,21 @@ export class IpcClient {
|
||||
public async getTemplates(): Promise<Template[]> {
|
||||
return this.ipcRenderer.invoke("get-templates");
|
||||
}
|
||||
|
||||
// --- Prompts Library ---
|
||||
public async listPrompts(): Promise<PromptDto[]> {
|
||||
return this.ipcRenderer.invoke("prompts:list");
|
||||
}
|
||||
|
||||
public async createPrompt(params: CreatePromptParamsDto): Promise<PromptDto> {
|
||||
return this.ipcRenderer.invoke("prompts:create", params);
|
||||
}
|
||||
|
||||
public async updatePrompt(params: UpdatePromptParamsDto): Promise<void> {
|
||||
await this.ipcRenderer.invoke("prompts:update", params);
|
||||
}
|
||||
|
||||
public async deletePrompt(id: number): Promise<void> {
|
||||
await this.ipcRenderer.invoke("prompts:delete", id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { registerProblemsHandlers } from "./handlers/problems_handlers";
|
||||
import { registerAppEnvVarsHandlers } from "./handlers/app_env_vars_handlers";
|
||||
import { registerTemplateHandlers } from "./handlers/template_handlers";
|
||||
import { registerPortalHandlers } from "./handlers/portal_handlers";
|
||||
import { registerPromptHandlers } from "./handlers/prompt_handlers";
|
||||
|
||||
export function registerIpcHandlers() {
|
||||
// Register all IPC handlers by category
|
||||
@@ -61,4 +62,5 @@ export function registerIpcHandlers() {
|
||||
registerAppEnvVarsHandlers();
|
||||
registerTemplateHandlers();
|
||||
registerPortalHandlers();
|
||||
registerPromptHandlers();
|
||||
}
|
||||
|
||||
@@ -353,6 +353,26 @@ export interface UploadFileToCodebaseResult {
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
// --- Prompts ---
|
||||
export interface PromptDto {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string | null;
|
||||
content: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreatePromptParamsDto {
|
||||
title: string;
|
||||
description?: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface UpdatePromptParamsDto extends CreatePromptParamsDto {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface FileAttachment {
|
||||
file: File;
|
||||
type: "upload-to-codebase" | "chat-context";
|
||||
|
||||
16
src/ipc/utils/replacePromptReference.ts
Normal file
16
src/ipc/utils/replacePromptReference.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function replacePromptReference(
|
||||
userPrompt: string,
|
||||
promptsById: Record<number | string, string>,
|
||||
): string {
|
||||
if (typeof userPrompt !== "string" || userPrompt.length === 0)
|
||||
return userPrompt;
|
||||
|
||||
return userPrompt.replace(
|
||||
/@prompt:(\d+)/g,
|
||||
(_match: string, idStr: string) => {
|
||||
const idNum = Number(idStr);
|
||||
const replacement = promptsById[idNum] ?? promptsById[idStr];
|
||||
return replacement !== undefined ? replacement : _match;
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user