Skip to content

Commit a2ce78c

Browse files
committed
prompt editorial workflow fixes
1 parent c28ebd8 commit a2ce78c

File tree

21 files changed

+1010
-678
lines changed

21 files changed

+1010
-678
lines changed

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22.13.1
1+
22.14.0

src/app/actions/prompts.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { createClient } from '@/lib/supabase/server';
44
import { Database } from '@/app/__generated__/supabase.types';
5+
import { compareSemanticVersions, incrementVersion } from '@/utils/versioning';
6+
import { SEMVER_PATTERN } from '@/app/constants';
57

68
type CreatePromptVersionOptions = {
79
promptId: number;
@@ -261,3 +263,202 @@ export async function createPrompt(options: CreatePromptOptions) {
261263
throw error;
262264
}
263265
}
266+
267+
type CreatePromptWithDraftVersionOptions = {
268+
name: string;
269+
projectId: number;
270+
};
271+
272+
export async function createPromptWithDraftVersion(
273+
options: CreatePromptWithDraftVersionOptions
274+
) {
275+
const { name, projectId } = options;
276+
const supabase = await createClient();
277+
278+
try {
279+
// Generate a slug from the name
280+
const slug = name
281+
.toLowerCase()
282+
.replace(/\s+/g, '-')
283+
.replace(/[^a-z0-9-]/g, '');
284+
285+
// Create a new prompt
286+
const { data: promptData, error: promptError } = await supabase
287+
.from('prompts')
288+
.insert({
289+
name,
290+
project_id: projectId,
291+
slug,
292+
})
293+
.select('id, uuid')
294+
.single();
295+
296+
if (promptError || !promptData) {
297+
throw new Error('Failed to create prompt: ' + promptError?.message);
298+
}
299+
300+
const newPromptId = promptData.id;
301+
const newPromptUuid = promptData.uuid;
302+
303+
// Create a new draft version
304+
const { data: versionData, error: versionError } = await supabase
305+
.from('prompt_versions')
306+
.insert({
307+
prompt_id: newPromptId,
308+
content: '',
309+
config: { model: 'openrouter/auto' },
310+
status: 'DRAFT',
311+
version: '0.0.1',
312+
})
313+
.select('id, uuid')
314+
.single();
315+
316+
if (versionError || !versionData) {
317+
// If we failed to create a version, delete the prompt to avoid orphans
318+
await supabase.from('prompts').delete().eq('id', newPromptId);
319+
throw new Error(
320+
'Failed to create prompt version: ' + versionError?.message
321+
);
322+
}
323+
324+
return {
325+
promptUuid: newPromptUuid,
326+
versionUuid: versionData.uuid,
327+
};
328+
} catch (error) {
329+
console.error('Error creating prompt with draft version:', error);
330+
throw error;
331+
}
332+
}
333+
334+
type CreateDraftVersionOptions = {
335+
promptId: number;
336+
promptUuid: string;
337+
latestVersion: string;
338+
customVersion?: string;
339+
versionType?: 'major' | 'minor' | 'patch';
340+
};
341+
342+
export async function createDraftVersion(options: CreateDraftVersionOptions) {
343+
const {
344+
promptId,
345+
promptUuid,
346+
latestVersion,
347+
customVersion,
348+
versionType = 'patch',
349+
} = options;
350+
const supabase = await createClient();
351+
352+
try {
353+
// First, get ALL versions for this prompt to find existing versions
354+
const { data: allVersions, error: versionsError } = await supabase
355+
.from('prompt_versions')
356+
.select('version')
357+
.eq('prompt_id', promptId);
358+
359+
if (versionsError) {
360+
throw new Error('Failed to fetch versions: ' + versionsError.message);
361+
}
362+
363+
// Determine the new version number
364+
let newVersion: string;
365+
366+
if (customVersion) {
367+
// If a custom version is provided, use it but verify it doesn't already exist
368+
const versionExists = allVersions?.some(
369+
(v) => v.version === customVersion
370+
);
371+
if (versionExists) {
372+
throw new Error(
373+
`Version ${customVersion} already exists for this prompt`
374+
);
375+
}
376+
377+
// Validate semantic versioning pattern
378+
if (!SEMVER_PATTERN.test(customVersion)) {
379+
throw new Error(
380+
'Version must follow semantic versioning (e.g., 1.0.0)'
381+
);
382+
}
383+
384+
newVersion = customVersion;
385+
} else {
386+
// Find the highest version number
387+
let highestVersion = latestVersion;
388+
if (allVersions && allVersions.length > 0) {
389+
highestVersion = allVersions.reduce((highest, current) => {
390+
return compareSemanticVersions(current.version, highest) > 0
391+
? current.version
392+
: highest;
393+
}, latestVersion);
394+
}
395+
396+
// Calculate new version number based on the highest version
397+
newVersion = incrementVersion(highestVersion, versionType);
398+
}
399+
400+
// Get latest version to copy content
401+
const { data: latestVersionData, error: latestVersionError } =
402+
await supabase
403+
.from('prompt_versions')
404+
.select('content, config, prompt_variables(*)')
405+
.eq('prompt_id', promptId)
406+
.eq('version', latestVersion)
407+
.single();
408+
409+
if (latestVersionError || !latestVersionData) {
410+
throw new Error(
411+
'Failed to find latest version: ' + latestVersionError?.message
412+
);
413+
}
414+
415+
// Create new draft version
416+
const { data: newVersionData, error: newVersionError } = await supabase
417+
.from('prompt_versions')
418+
.insert({
419+
prompt_id: promptId,
420+
content: latestVersionData.content,
421+
config: latestVersionData.config,
422+
status: 'DRAFT',
423+
version: newVersion,
424+
})
425+
.select('id, uuid')
426+
.single();
427+
428+
if (newVersionError || !newVersionData) {
429+
throw new Error(
430+
'Failed to create new version: ' + newVersionError?.message
431+
);
432+
}
433+
434+
// Copy variables from the latest version
435+
if (
436+
latestVersionData.prompt_variables &&
437+
latestVersionData.prompt_variables.length > 0
438+
) {
439+
const variablesToInsert = latestVersionData.prompt_variables.map(
440+
(variable: any) => ({
441+
prompt_version_id: newVersionData.id,
442+
name: variable.name,
443+
type: variable.type,
444+
required: variable.required,
445+
})
446+
);
447+
448+
const { error: variablesError } = await supabase
449+
.from('prompt_variables')
450+
.insert(variablesToInsert);
451+
452+
if (variablesError) {
453+
throw new Error(
454+
'Failed to copy prompt variables: ' + variablesError.message
455+
);
456+
}
457+
}
458+
459+
return { versionUuid: newVersionData.uuid };
460+
} catch (error) {
461+
console.error('Error creating draft version:', error);
462+
throw error;
463+
}
464+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
compilePrompt,
3+
getMissingVariables,
4+
getPromptVersionByUuid,
5+
} from '@/lib/prompts';
6+
import { createClient } from '@/lib/supabase/server';
7+
import { NextResponse } from 'next/server';
8+
9+
type RequestBody = {
10+
variables: Record<string, string | number | boolean>;
11+
};
12+
13+
export async function POST(
14+
request: Request,
15+
{ params }: { params: Promise<{ promptVersionUuid: string }> }
16+
) {
17+
const { promptVersionUuid } = await params;
18+
19+
if (!promptVersionUuid) {
20+
return NextResponse.json(
21+
{ error: 'Prompt Version UUID is required' },
22+
{ status: 400 }
23+
);
24+
}
25+
26+
const supabase = await createClient();
27+
28+
const {
29+
data: { user },
30+
} = await supabase.auth.getUser();
31+
32+
if (!user) {
33+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
34+
}
35+
36+
// Fetch the prompt from Supabase
37+
const promptVersion = await getPromptVersionByUuid(promptVersionUuid);
38+
39+
if (!promptVersion) {
40+
return NextResponse.json(
41+
{ error: 'Prompt version not found' },
42+
{ status: 404 }
43+
);
44+
}
45+
46+
const variables = promptVersion.prompt_variables || [];
47+
48+
let body: RequestBody;
49+
try {
50+
body = await request.json();
51+
} catch (error) {
52+
return NextResponse.json(
53+
{ error: 'Invalid request body' },
54+
{ status: 400 }
55+
);
56+
}
57+
58+
const missingVariables = getMissingVariables(variables, body.variables);
59+
60+
if (missingVariables.length > 0) {
61+
return NextResponse.json(
62+
{
63+
error: 'Missing required variables',
64+
missingVariables,
65+
},
66+
{ status: 400 }
67+
);
68+
}
69+
70+
const compiledPrompt = compilePrompt(promptVersion.content, body.variables);
71+
72+
return NextResponse.json({ compiledPrompt }, { status: 200 });
73+
}

0 commit comments

Comments
 (0)