Skip to content

Commit 395ce8b

Browse files
authored
Merge pull request #1190 from Kiln-AI/sfierro/prompts-clone
Clone prompt button
2 parents b87afdd + c67044e commit 395ce8b

File tree

7 files changed

+285
-148
lines changed

7 files changed

+285
-148
lines changed

app/web_ui/src/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type DockerModelRunnerConnection =
3636
components["schemas"]["DockerModelRunnerConnection"]
3737
export type RunSummary = components["schemas"]["RunSummary"]
3838
export type PromptResponse = components["schemas"]["PromptResponse"]
39+
export type ApiPrompt = components["schemas"]["ApiPrompt"]
3940
export type ChatStrategy = components["schemas"]["ChatStrategy"]
4041
export type EvalOutputScore = components["schemas"]["EvalOutputScore"]
4142
export type EvalTemplateId = components["schemas"]["EvalTemplateId"]

app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/+page.svelte

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
prompts_by_task_composite_id,
1212
} from "$lib/stores/prompts_store"
1313
import { onMount } from "svelte"
14-
import type { Task } from "$lib/types"
14+
import type { Task, ApiPrompt } from "$lib/types"
1515
import { createKilnError, KilnError } from "$lib/utils/error_handlers"
1616
import { getPromptType } from "./prompt_generators/prompt_generators"
1717
import InfoTooltip from "$lib/ui/info_tooltip.svelte"
@@ -88,6 +88,12 @@
8888
goto(`/prompts/${project_id}/${task_id}/edit_base_prompt`)
8989
}
9090
91+
function handleClonePrompt(prompt: ApiPrompt) {
92+
goto(
93+
`/prompts/${project_id}/${task_id}/clone/${encodeURIComponent(prompt.id)}`,
94+
)
95+
}
96+
9197
type TableColumn = {
9298
key: string
9399
label: string
@@ -303,6 +309,13 @@
303309
Set as Base Prompt
304310
</button>
305311
</li>
312+
<li>
313+
<button
314+
on:click={() => handleClonePrompt(prompt)}
315+
>
316+
Clone
317+
</button>
318+
</li>
306319
</ul>
307320
</Float>
308321
</div>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script lang="ts">
2+
import AppPage from "../../../../../app_page.svelte"
3+
import { page } from "$app/stores"
4+
import { KilnError, createKilnError } from "$lib/utils/error_handlers"
5+
import {
6+
load_task_prompts,
7+
prompts_by_task_composite_id,
8+
} from "$lib/stores/prompts_store"
9+
import { get_task_composite_id } from "$lib/stores"
10+
import { onMount } from "svelte"
11+
import PromptForm from "../../prompt_form.svelte"
12+
13+
$: project_id = $page.params.project_id!
14+
$: task_id = $page.params.task_id!
15+
$: prompt_id = $page.params.prompt_id!
16+
17+
let initial_prompt_name = ""
18+
let initial_prompt = ""
19+
let initial_chain_of_thought_instructions: string | null = null
20+
let loading = true
21+
let loading_error: KilnError | null = null
22+
23+
onMount(async () => {
24+
try {
25+
await load_task_prompts(project_id, task_id)
26+
const task_prompts =
27+
$prompts_by_task_composite_id[
28+
get_task_composite_id(project_id, task_id)
29+
]
30+
const source_prompt = task_prompts?.prompts.find(
31+
(p) => p.id === prompt_id,
32+
)
33+
34+
if (!source_prompt) {
35+
throw new KilnError("Source prompt not found.")
36+
}
37+
38+
initial_prompt_name = `Copy of ${source_prompt.name}`
39+
initial_prompt = source_prompt.prompt
40+
initial_chain_of_thought_instructions =
41+
source_prompt.chain_of_thought_instructions || null
42+
} catch (e) {
43+
loading_error = createKilnError(e)
44+
} finally {
45+
loading = false
46+
}
47+
})
48+
</script>
49+
50+
<div class="max-w-[1400px]">
51+
<AppPage
52+
title="Clone Prompt"
53+
sub_subtitle="Read the Docs"
54+
sub_subtitle_link="https://docs.kiln.tech/docs/prompts"
55+
breadcrumbs={[
56+
{
57+
label: "Optimize",
58+
href: `/optimize/${project_id}/${task_id}`,
59+
},
60+
{
61+
label: "Prompts",
62+
href: `/prompts/${project_id}/${task_id}`,
63+
},
64+
]}
65+
>
66+
{#if loading}
67+
<div class="w-full min-h-[50vh] flex justify-center items-center">
68+
<div class="loading loading-spinner loading-lg"></div>
69+
</div>
70+
{:else if loading_error}
71+
<div class="text-error text-sm">
72+
{loading_error.getMessage() || "An unknown error occurred"}
73+
</div>
74+
{:else}
75+
<PromptForm
76+
{project_id}
77+
{task_id}
78+
clone_mode={true}
79+
{initial_prompt_name}
80+
{initial_prompt}
81+
{initial_chain_of_thought_instructions}
82+
/>
83+
{/if}
84+
</AppPage>
85+
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const prerender = false
Lines changed: 25 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,31 @@
11
<script lang="ts">
22
import AppPage from "../../../../app_page.svelte"
3-
import { current_task, load_available_prompts } from "$lib/stores"
3+
import { current_task } from "$lib/stores"
44
import { page } from "$app/stores"
5-
import FormContainer from "$lib/utils/form_container.svelte"
6-
import FormElement from "$lib/utils/form_element.svelte"
75
import { client } from "$lib/api_client"
86
import { createKilnError, KilnError } from "$lib/utils/error_handlers"
9-
import { goto } from "$app/navigation"
10-
import posthog from "posthog-js"
117
import { onMount } from "svelte"
128
import { prompt_generator_categories } from "../prompt_generators/prompt_generators"
139
import { generate_memorable_name } from "$lib/utils/name_generator"
10+
import PromptForm from "../prompt_form.svelte"
1411
1512
$: project_id = $page.params.project_id!
1613
$: task_id = $page.params.task_id!
1714
1815
let generator_name = ""
19-
let prompt_name = generate_memorable_name()
20-
let prompt = ""
21-
let is_chain_of_thought = false
22-
let chain_of_thought_instructions =
23-
"Think step by step, explaining your reasoning."
24-
let create_error: KilnError | null = null
25-
let create_loading = false
26-
let warn_before_unload = false
16+
let initial_prompt_name = generate_memorable_name()
17+
let initial_prompt = ""
18+
let loading_error: KilnError | null = null
2719
2820
let generator_id: string | null = null
29-
let loading_generator = false
21+
let loading = true
3022
let is_custom = true
3123
32-
let initial_prompt_name = ""
33-
let initial_prompt = ""
34-
let initial_loaded = false
35-
3624
onMount(async () => {
3725
generator_id = $page.url.searchParams.get("generator_id") || null
3826
is_custom = !generator_id
3927
4028
if (generator_id) {
41-
loading_generator = true
4229
try {
4330
const { data: prompt_response, error: get_error } = await client.GET(
4431
"/api/projects/{project_id}/tasks/{task_id}/gen_prompt/{prompt_id}",
@@ -55,95 +42,31 @@
5542
if (get_error) {
5643
throw get_error
5744
}
58-
prompt = prompt_response.prompt
45+
initial_prompt = prompt_response.prompt
5946
6047
const template = prompt_generator_categories
6148
.flatMap((c) => c.templates)
6249
.find((t) => t.generator_id === generator_id)
6350
generator_name = template?.name || generator_id
6451
} catch (e) {
65-
create_error = createKilnError(e)
66-
} finally {
67-
loading_generator = false
52+
loading_error = createKilnError(e)
6853
}
6954
} else {
7055
generator_name = "Custom"
7156
7257
if ($current_task?.instruction) {
73-
prompt = $current_task.instruction
58+
initial_prompt = $current_task.instruction
7459
}
7560
}
7661
77-
initial_prompt_name = prompt_name
78-
initial_prompt = prompt
79-
initial_loaded = true
62+
loading = false
8063
})
81-
82-
async function create_prompt() {
83-
try {
84-
create_loading = true
85-
create_error = null
86-
const { data, error } = await client.POST(
87-
"/api/projects/{project_id}/tasks/{task_id}/prompts",
88-
{
89-
params: {
90-
path: {
91-
project_id,
92-
task_id,
93-
},
94-
},
95-
body: {
96-
generator_id: generator_id,
97-
name: prompt_name,
98-
prompt: prompt,
99-
chain_of_thought_instructions:
100-
is_custom && is_chain_of_thought
101-
? chain_of_thought_instructions
102-
: null,
103-
},
104-
},
105-
)
106-
if (error) {
107-
throw error
108-
}
109-
if (!data || !data.id) {
110-
throw new Error("Invalid response from server")
111-
}
112-
posthog.capture("create_prompt", {
113-
is_chain_of_thought: is_chain_of_thought,
114-
from_generator: generator_id,
115-
})
116-
117-
await load_available_prompts(true)
118-
119-
warn_before_unload = false
120-
const from = $page.url.searchParams.get("from")
121-
if (from === "optimize") {
122-
goto(
123-
`/optimize/${project_id}/${task_id}/run_config/create?prompt_id=${encodeURIComponent(`id::${data.id}`)}`,
124-
)
125-
} else {
126-
goto(`/prompts/${project_id}/${task_id}/saved/id::${data.id}`)
127-
}
128-
} catch (e) {
129-
create_error = createKilnError(e)
130-
} finally {
131-
create_loading = false
132-
}
133-
}
134-
135-
$: if (initial_loaded) {
136-
warn_before_unload =
137-
prompt !== initial_prompt ||
138-
prompt_name !== initial_prompt_name ||
139-
(is_custom && is_chain_of_thought)
140-
}
14164
</script>
14265

14366
<div class="max-w-[1400px]">
14467
<AppPage
14568
title="Create Prompt"
146-
subtitle={`${generator_name}`}
69+
subtitle={generator_name}
14770
sub_subtitle="Read the Docs"
14871
sub_subtitle_link="https://docs.kiln.tech/docs/prompts"
14972
breadcrumbs={[
@@ -157,60 +80,24 @@
15780
},
15881
]}
15982
>
160-
{#if loading_generator}
83+
{#if loading}
16184
<div class="w-full min-h-[50vh] flex justify-center items-center">
16285
<div class="loading loading-spinner loading-lg"></div>
16386
</div>
164-
{:else}
165-
<div class="max-w-[800px]">
166-
<FormContainer
167-
submit_label="Create Prompt"
168-
on:submit={create_prompt}
169-
bind:error={create_error}
170-
bind:submitting={create_loading}
171-
{warn_before_unload}
172-
>
173-
<FormElement
174-
label="Prompt Name"
175-
id="prompt_name"
176-
bind:value={prompt_name}
177-
description="A name to identify this prompt."
178-
max_length={120}
179-
/>
180-
181-
<FormElement
182-
label="Prompt"
183-
id="prompt"
184-
bind:value={prompt}
185-
inputType="textarea"
186-
height="large"
187-
description="A prompt to use for this task."
188-
info_description="Model prompt such as 'You are a helpful assistant.'. This prompt is specific to this task. To use this prompt after creation, select it from the prompts dropdown."
189-
/>
190-
{#if is_custom}
191-
<FormElement
192-
label="Chain of Thought"
193-
id="is_chain_of_thought"
194-
bind:value={is_chain_of_thought}
195-
description="Should this prompt use chain of thought?"
196-
inputType="select"
197-
select_options={[
198-
[false, "Disabled"],
199-
[true, "Enabled"],
200-
]}
201-
/>
202-
{#if is_chain_of_thought}
203-
<FormElement
204-
label="Chain of Thought Instructions"
205-
id="chain_of_thought_instructions"
206-
bind:value={chain_of_thought_instructions}
207-
inputType="textarea"
208-
description="Instructions for the model's 'thinking' prior to answering. Required for chain of thought prompting."
209-
/>
210-
{/if}
211-
{/if}
212-
</FormContainer>
87+
{:else if loading_error}
88+
<div class="text-error text-sm">
89+
{loading_error.getMessage() || "An unknown error occurred"}
21390
</div>
91+
{:else}
92+
<PromptForm
93+
{project_id}
94+
{task_id}
95+
{generator_id}
96+
show_chain_of_thought={is_custom}
97+
{initial_prompt_name}
98+
{initial_prompt}
99+
redirect_from={$page.url.searchParams.get("from")}
100+
/>
214101
{/if}
215102
</AppPage>
216103
</div>

0 commit comments

Comments
 (0)