Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions app/web_ui/src/lib/api_schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,46 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/projects/{project_id}/tasks/{task_id}/run/stream": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Run Task Stream
* @description Run a task with OpenAI-style streaming response.
*/
post: operations["run_task_stream_api_projects__project_id__tasks__task_id__run_stream_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/projects/{project_id}/tasks/{task_id}/run/stream/ai-sdk": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Run Task Ai Sdk Stream
* @description Run a task with AI SDK-style streaming response.
*/
post: operations["run_task_ai_sdk_stream_api_projects__project_id__tasks__task_id__run_stream_ai_sdk_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/projects/{project_id}/tasks/{task_id}/runs/edit_tags": {
parameters: {
query?: never;
Expand Down Expand Up @@ -6265,6 +6305,8 @@ export interface components {
} | unknown[] | null;
/** Tags */
tags?: string[] | null;
/** Task Run Id */
task_run_id?: string | null;
};
/**
* SampleApi
Expand Down Expand Up @@ -8431,6 +8473,78 @@ export interface operations {
};
};
};
run_task_stream_api_projects__project_id__tasks__task_id__run_stream_post: {
parameters: {
query?: never;
header?: never;
path: {
project_id: string;
task_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RunTaskRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
run_task_ai_sdk_stream_api_projects__project_id__tasks__task_id__run_stream_ai_sdk_post: {
parameters: {
query?: never;
header?: never;
path: {
project_id: string;
task_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RunTaskRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
edit_tags_api_projects__project_id__tasks__task_id__runs_edit_tags_post: {
parameters: {
query?: never;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,18 @@
shortcut: isMacOS() ? "Backspace" : "Delete",
})
}
// Add "Continue Conversation" button if run has a trace
if (run?.trace && run.trace.length > 0) {
buttons.push({
label: "Continue Conversation",
icon: "/images/next.svg",
handler: () => {
goto(
`/run?prior_run_id=${run_id}&project_id=${project_id}&task_id=${task_id}`,
)
},
})
}
if (list_page.length > 1) {
const index = list_page.indexOf(run_id)
if (index !== -1) {
Expand Down
145 changes: 127 additions & 18 deletions app/web_ui/src/routes/(app)/run/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import Run from "./run.svelte"
import { client } from "$lib/api_client"
import type { TaskRun, TaskRunConfig } from "$lib/types"
import type { components } from "$lib/api_schema"
import RunInputForm from "./run_input_form.svelte"
import posthog from "posthog-js"
import { onMount, tick } from "svelte"
import RunConfigComponent from "$lib/ui/run_config_component/run_config_component.svelte"
import SavedRunConfigurationsDropdown from "$lib/ui/run_config_component/saved_run_configs_dropdown.svelte"
import { isMcpRunConfig } from "$lib/types"
import { page } from "$app/stores"
import TraceComponent from "$lib/ui/trace/trace.svelte"

let run_error: KilnError | null = null
let submitting = false
Expand All @@ -40,17 +42,56 @@
$: input_schema = $current_task?.input_json_schema
$: pending_tool_id = $page.url.searchParams.get("tool_id")
$: pending_run_config_id = $page.url.searchParams.get("run_config_id")
// Use a local variable to track the latest run in the conversation
let conversation_prior_run_id: string | null = null
$: prior_run_id =
$page.url.searchParams.get("prior_run_id") || conversation_prior_run_id

$: subtitle = $current_task ? "Task: " + $current_task.name : ""

onMount(() => {
// Load prior run data if continuing a conversation
let prior_run: TaskRun | null = null
let loading_prior_run = false
let prior_run_error: KilnError | null = null
let prior_run_loaded = false

// Reactive load of prior run - watches for when all required values are available
$: if (!prior_run_loaded && prior_run_id && project_id && task_id) {
load_prior_run()
}

onMount(async () => {
const model_override = $page.url.searchParams.get("model")
if (model_override) {
model = model_override
selected_run_config_id = "custom"
}
})

async function load_prior_run() {
loading_prior_run = true
try {
const { data, error } = await client.GET(
"/api/projects/{project_id}/tasks/{task_id}/runs/{run_id}",
{
params: {
path: { project_id, task_id, run_id: prior_run_id! },
},
},
)
if (error) {
throw error
}
prior_run = data
prior_run_loaded = true
} catch (error) {
prior_run_error = createKilnError(error)
console.warn("Failed to load prior run:", error)
} finally {
loading_prior_run = false
}
}

async function run_task() {
try {
submitting = true
Expand All @@ -72,27 +113,38 @@
run_config_component.set_model_dropdown_error("Required")
throw new Error("You must select a model before running")
}
const {
data, // only present if 2XX response
error: fetch_error, // only present if 4XX or 5XX response
} = await client.POST("/api/projects/{project_id}/tasks/{task_id}/run", {
params: {
path: {
project_id: project_id,
task_id: task_id,

let data: TaskRun | undefined
let fetch_error: components["schemas"]["HTTPValidationError"] | null =
null

// Always use the regular run endpoint, passing task_run_id if continuing conversation
const regular_result = await client.POST(
"/api/projects/{project_id}/tasks/{task_id}/run",
{
params: {
path: {
project_id: project_id,
task_id: task_id,
},
},
body: {
run_config_properties: run_config_properties,
plaintext_input: input_form.get_plaintext_input_data(),
// @ts-expect-error - let the server verify the type. TS isn't ideal for runtime type checking.
structured_input: input_form.get_structured_input_data(),
tags: ["manual_run"],
task_run_id: prior_run_id,
},
},
body: {
run_config_properties: run_config_properties,
plaintext_input: input_form.get_plaintext_input_data(),
// @ts-expect-error - let the server verify the type. TS isn't ideal for runtime type checking.
structured_input: input_form.get_structured_input_data(),
tags: ["manual_run"],
},
})
)
data = regular_result.data ?? undefined
fetch_error = regular_result.error ?? null

if (fetch_error) {
throw fetch_error
}

if (is_mcp_run) {
posthog.capture("run_mcp_tool_directly")
} else {
Expand All @@ -111,7 +163,18 @@
).length,
})
}
response = data

response = data ?? null

// Update the conversation tracking to point to the latest run
conversation_prior_run_id = response?.id ?? null

// Update prior_run to the new response so trace remains available
prior_run = response
prior_run_loaded = true

// Clear the input form to allow user to continue the conversation
input_form.clear_input()
} catch (e) {
run_error = createKilnError(e)
} finally {
Expand Down Expand Up @@ -191,6 +254,46 @@
bind:subtitle
action_buttons={[{ label: "Clear All", handler: clear_all }]}
>
{#if prior_run && !loading_prior_run}
<div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div class="flex items-center gap-2">
<svg
class="w-5 h-5 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
<span class="font-medium text-blue-800">Continuing Conversation</span>
</div>
<p class="mt-2 text-sm text-blue-700">
You're continuing from a previous run. The conversation history will
be included in the context.
<a
href={`/dataset/${project_id}/${task_id}/${prior_run_id}/run`}
class="link link-primary ml-1"
target="_blank"
>
View prior run</a
>
</p>
</div>
{:else if loading_prior_run}
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg">
<span class="loading loading-spinner loading-sm"></span>
<span class="ml-2 text-gray-600">Loading prior run...</span>
</div>
{:else if prior_run_error}
<div class="mb-6 p-4 bg-error/10 border border-error/20 rounded-lg">
<p class="text-error text-sm">{prior_run_error.getMessage()}</p>
</div>
{/if}
<div class="flex flex-col xl:flex-row gap-8 xl:gap-16">
<div class="grow">
<div class="text-xl font-bold mb-4">Input</div>
Expand Down Expand Up @@ -250,6 +353,12 @@
focus_repair_on_appear={true}
/>
</div>
{:else if prior_run && prior_run.trace}
<!-- Show prior run's trace when continuing a conversation -->
<div class="mt-8 xl:mt-12 max-w-4xl">
<div class="text-xl font-bold mb-4">Previous Message Trace</div>
<TraceComponent trace={prior_run.trace} {project_id} />
</div>
{/if}
</AppPage>
</div>
Loading
Loading