From 8fa4f94beb494818f43d0233dca0e7f78a097dab Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 16 Dec 2024 22:35:59 -0700 Subject: [PATCH] chore: add files UI to tasks --- apiclient/client.go | 19 ++- apiclient/task.go | 139 ++++++++++++++++++ pkg/api/authz/thread.go | 2 +- pkg/api/handlers/assistants.go | 14 +- pkg/api/handlers/tasks.go | 62 ++++++-- pkg/api/router/router.go | 11 +- ui/user/src/lib/actions/div.ts | 6 +- ui/user/src/lib/components/Editors.svelte | 2 +- .../src/lib/components/editor/Image.svelte | 7 +- .../src/lib/components/messages/Input.svelte | 2 +- .../lib/components/messages/Message.svelte | 2 +- ui/user/src/lib/components/navbar/Logo.svelte | 11 -- ui/user/src/lib/components/tasks/Files.svelte | 83 +++++++++++ ui/user/src/lib/components/tasks/Input.svelte | 2 +- ui/user/src/lib/components/tasks/Runs.svelte | 14 +- ui/user/src/lib/components/tasks/Steps.svelte | 7 + ui/user/src/lib/icons/AssistantIcon.svelte | 14 +- ui/user/src/lib/services/chat/messages.ts | 2 +- ui/user/src/lib/services/chat/operations.ts | 34 ++++- .../src/lib/services/chat/thread.svelte.ts | 7 +- .../src/lib/services/editor/index.svelte.ts | 51 +++++-- ui/user/src/lib/stores/editor.svelte.ts | 2 + 22 files changed, 414 insertions(+), 79 deletions(-) create mode 100644 apiclient/task.go create mode 100644 ui/user/src/lib/components/tasks/Files.svelte diff --git a/apiclient/client.go b/apiclient/client.go index db798a0a..9c9bbc18 100644 --- a/apiclient/client.go +++ b/apiclient/client.go @@ -52,11 +52,22 @@ func (c *Client) putJSON(ctx context.Context, path string, obj any, headerKV ... } func (c *Client) postJSON(ctx context.Context, path string, obj any, headerKV ...string) (*http.Request, *http.Response, error) { - data, err := json.Marshal(obj) - if err != nil { - return nil, nil, err + var body io.Reader + + switch v := obj.(type) { + case string: + if v != "" { + body = strings.NewReader(v) + } + default: + data, err := json.Marshal(obj) + if err != nil { + return nil, nil, err + } + body = bytes.NewBuffer(data) + headerKV = append(headerKV, "Content-Type", "application/json") } - return c.doRequest(ctx, http.MethodPost, path, bytes.NewBuffer(data), append(headerKV, "Content-Type", "application/json")...) + return c.doRequest(ctx, http.MethodPost, path, body, headerKV...) } func (c *Client) doStream(ctx context.Context, method, path string, body io.Reader, headerKV ...string) (*http.Request, *http.Response, error) { diff --git a/apiclient/task.go b/apiclient/task.go new file mode 100644 index 00000000..b4f63253 --- /dev/null +++ b/apiclient/task.go @@ -0,0 +1,139 @@ +package apiclient + +import ( + "context" + "fmt" + "net/http" + "sort" + + "github.com/acorn-io/acorn/apiclient/types" +) + +type ListTasksOptions struct { + ThreadID string + AssistantID string +} + +func (c *Client) ListTasks(ctx context.Context, opts ListTasksOptions) (result types.TaskList, err error) { + defer func() { + sort.Slice(result.Items, func(i, j int) bool { + return result.Items[i].Metadata.Created.Time.Before(result.Items[j].Metadata.Created.Time) + }) + }() + + if opts.ThreadID == "" && opts.AssistantID == "" { + return result, fmt.Errorf("either threadID or assistantID must be provided") + } + var url string + if opts.ThreadID != "" { + url = fmt.Sprintf("/threads/%s/tasks", opts.ThreadID) + } else { + url = fmt.Sprintf("/assistants/%s/tasks", opts.AssistantID) + } + _, resp, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return result, err + } + defer resp.Body.Close() + + _, err = toObject(resp, &result) + return +} + +type UpdateTaskOptions struct { + ThreadID string + AssistantID string +} + +func (c *Client) UpdateTask(ctx context.Context, id string, manifest types.TaskManifest, opt UpdateTaskOptions) (*types.Task, error) { + var url string + if opt.ThreadID != "" { + url = fmt.Sprintf("/threads/%s/tasks/%s", opt.ThreadID, id) + } else { + url = fmt.Sprintf("/assistants/%s/tasks/%s", opt.AssistantID, id) + } + + _, resp, err := c.putJSON(ctx, url, manifest) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return toObject(resp, &types.Task{}) +} + +type CreateTaskOptions struct { + ThreadID string + AssistantID string +} + +func (c *Client) CreateTask(ctx context.Context, manifest types.TaskManifest, opt CreateTaskOptions) (*types.Task, error) { + var url string + if opt.ThreadID != "" { + url = fmt.Sprintf("/threads/%s/tasks", opt.ThreadID) + } else { + url = fmt.Sprintf("/assistants/%s/tasks", opt.AssistantID) + } + + _, resp, err := c.postJSON(ctx, url, manifest) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return toObject(resp, &types.Task{}) +} + +type ListTaskRunsOptions struct { + ThreadID string + AssistantID string +} + +func (c *Client) ListTaskRuns(ctx context.Context, taskID string, opts ListTaskRunsOptions) (result types.TaskRunList, err error) { + defer func() { + sort.Slice(result.Items, func(i, j int) bool { + return result.Items[i].Metadata.Created.Time.Before(result.Items[j].Metadata.Created.Time) + }) + }() + + if opts.ThreadID == "" && opts.AssistantID == "" { + return result, fmt.Errorf("either threadID or assistantID must be provided") + } + var url string + if opts.ThreadID != "" { + url = fmt.Sprintf("/threads/%s/tasks/%s/runs", opts.ThreadID, taskID) + } else { + url = fmt.Sprintf("/assistants/%s/tasks/%s/runs", opts.AssistantID, taskID) + } + + _, resp, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return + } + defer resp.Body.Close() + + _, err = toObject(resp, &result) + return +} + +type TaskRunOptions struct { + ThreadID string + AssistantID string +} + +func (c *Client) RunTask(ctx context.Context, taskID string, input string, opts TaskRunOptions) (*types.TaskRun, error) { + var url string + if opts.ThreadID != "" { + url = fmt.Sprintf("/threads/%s/tasks/%s/runs", opts.ThreadID, taskID) + } else { + url = fmt.Sprintf("/assistants/%s/tasks/%s/runs", opts.AssistantID, taskID) + } + + _, resp, err := c.postJSON(ctx, url, input) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return toObject(resp, &types.TaskRun{}) +} diff --git a/pkg/api/authz/thread.go b/pkg/api/authz/thread.go index 67a66a74..bd79846c 100644 --- a/pkg/api/authz/thread.go +++ b/pkg/api/authz/thread.go @@ -17,7 +17,7 @@ func authorizeThread(req *http.Request, user user.Info) bool { if req.Method == "GET" && strings.HasPrefix(req.URL.Path, "/api/threads/"+thread+"/") { return true } - if req.Method == "POST" && req.URL.Path == "/api/invoke/"+agent+"/"+"threads/"+thread { + if req.Method == "POST" && strings.HasPrefix(req.URL.Path, "/api/threads/"+thread+"/tasks/") { return true } diff --git a/pkg/api/handlers/assistants.go b/pkg/api/handlers/assistants.go index 39388b13..b7769f8e 100644 --- a/pkg/api/handlers/assistants.go +++ b/pkg/api/handlers/assistants.go @@ -238,11 +238,7 @@ func (a *AssistantHandler) Events(req api.Context) error { } func (a *AssistantHandler) Files(req api.Context) error { - var ( - id = req.PathValue("id") - ) - - thread, err := getUserThread(req, id) + thread, err := getThreadForScope(req) if err != nil { return err } @@ -258,17 +254,13 @@ func (a *AssistantHandler) Files(req api.Context) error { } func (a *AssistantHandler) GetFile(req api.Context) error { - var ( - id = req.PathValue("id") - ) - - thread, err := getUserThread(req, id) + thread, err := getThreadForScope(req) if err != nil { return err } if thread.Status.WorkspaceID == "" { - return types.NewErrNotFound("no workspace found for assistant %s", id) + return types.NewErrNotFound("no workspace found") } return getFileInWorkspace(req.Context(), req, a.gptScript, thread.Status.WorkspaceID, "files/") diff --git a/pkg/api/handlers/tasks.go b/pkg/api/handlers/tasks.go index 0b31bfe5..9f186dcf 100644 --- a/pkg/api/handlers/tasks.go +++ b/pkg/api/handlers/tasks.go @@ -506,15 +506,13 @@ func (t *TaskHandler) updateCron(req api.Context, workflow *v1.Workflow, task ty } func (t *TaskHandler) getAssistantThreadAndManifestFromRequest(req api.Context) (*v1.Agent, *v1.Thread, types.WorkflowManifest, types.TaskManifest, error) { - assistantID := req.PathValue("assistant_id") - - assistant, err := getAssistant(req, assistantID) + thread, err := getThreadForScope(req) if err != nil { return nil, nil, types.WorkflowManifest{}, types.TaskManifest{}, err } - thread, err := getUserThread(req, assistantID) - if err != nil { + var agent v1.Agent + if err := req.Get(&agent, thread.Spec.AgentName); err != nil { return nil, nil, types.WorkflowManifest{}, types.TaskManifest{}, err } @@ -523,7 +521,7 @@ func (t *TaskHandler) getAssistantThreadAndManifestFromRequest(req api.Context) return nil, nil, types.WorkflowManifest{}, types.TaskManifest{}, err } - return assistant, thread, toWorkflowManifest(assistant, thread, manifest), manifest, nil + return &agent, thread, toWorkflowManifest(&agent, thread, manifest), manifest, nil } func (t *TaskHandler) Create(req api.Context) error { @@ -659,15 +657,13 @@ func (t *TaskHandler) Get(req api.Context) error { } func (t *TaskHandler) getTask(req api.Context) (*v1.Workflow, *v1.Thread, error) { - assistantID := req.PathValue("assistant_id") - - var workflow v1.Workflow - if err := req.Get(&workflow, req.PathValue("id")); err != nil { + thread, err := getThreadForScope(req) + if err != nil { return nil, nil, err } - thread, err := getUserThread(req, assistantID) - if err != nil { + var workflow v1.Workflow + if err := req.Get(&workflow, req.PathValue("id")); err != nil { return nil, nil, err } @@ -678,10 +674,48 @@ func (t *TaskHandler) getTask(req api.Context) (*v1.Workflow, *v1.Thread, error) return &workflow, thread, nil } -func (t *TaskHandler) List(req api.Context) error { +func getThreadForScope(req api.Context) (*v1.Thread, error) { assistantID := req.PathValue("assistant_id") - thread, err := getUserThread(req, assistantID) + if assistantID != "" { + thread, err := getUserThread(req, assistantID) + if err != nil { + return nil, err + } + + taskID := req.PathValue("task_id") + runID := req.PathValue("run_id") + if taskID != "" && runID != "" { + if runID == "editor" { + runID = editorWFE(req, taskID) + } + var wfe v1.WorkflowExecution + if err := req.Get(&wfe, runID); err != nil { + return nil, err + } + if wfe.Spec.ThreadName != thread.Name { + return nil, types.NewErrHttp(http.StatusForbidden, "task run does not belong to the thread") + } + if wfe.Spec.WorkflowName != taskID { + return nil, types.NewErrNotFound("task run not found") + } + return thread, req.Get(thread, wfe.Status.ThreadName) + } + + return thread, nil + } + + threadID := req.PathValue("thread_id") + + var thread v1.Thread + if err := req.Get(&thread, threadID); err != nil { + return nil, err + } + return &thread, nil +} + +func (t *TaskHandler) List(req api.Context) error { + thread, err := getThreadForScope(req) if err != nil { return err } diff --git a/pkg/api/router/router.go b/pkg/api/router/router.go index 97d8f9b6..fe8dc324 100644 --- a/pkg/api/router/router.go +++ b/pkg/api/router/router.go @@ -56,7 +56,7 @@ func Router(services *services.Services) (http.Handler, error) { mux.HandleFunc("DELETE /api/assistants/{id}/tools/{tool}", assistants.RemoveTool) mux.HandleFunc("PUT /api/assistants/{id}/tools/{tool}", assistants.AddTool) // Assistant files - mux.HandleFunc("GET /api/assistants/{id}/files", assistants.Files) + mux.HandleFunc("GET /api/assistants/{assistant_id}/files", assistants.Files) mux.HandleFunc("GET /api/assistants/{id}/file/{file...}", assistants.GetFile) mux.HandleFunc("POST /api/assistants/{id}/files/{file...}", assistants.UploadFile) mux.HandleFunc("DELETE /api/assistants/{id}/files/{file...}", assistants.DeleteFile) @@ -67,15 +67,24 @@ func Router(services *services.Services) (http.Handler, error) { // Tasks mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks", tasks.List) + mux.HandleFunc("GET /api/threads/{thread_id}/tasks", tasks.List) mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{id}", tasks.Get) + mux.HandleFunc("GET /api/threads/{thread_id}/tasks/{id}", tasks.Get) mux.HandleFunc("POST /api/assistants/{assistant_id}/tasks", tasks.Create) + mux.HandleFunc("POST /api/threads/{thread_id}/tasks", tasks.Create) mux.HandleFunc("PUT /api/assistants/{assistant_id}/tasks/{id}", tasks.Update) + mux.HandleFunc("PUT /api/threads/{thread_id}/tasks/{id}", tasks.Update) mux.HandleFunc("DELETE /api/assistants/{assistant_id}/tasks/{id}", tasks.Delete) mux.HandleFunc("POST /api/assistants/{assistant_id}/tasks/{id}/run", tasks.Run) + mux.HandleFunc("POST /api/threads/{thread_id}/tasks/{id}/run", tasks.Run) mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{id}/runs", tasks.ListRuns) + mux.HandleFunc("GET /api/threads/{thread_id}/tasks/{id}/runs", tasks.ListRuns) mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{id}/runs/{run_id}", tasks.GetRun) + mux.HandleFunc("GET /api/threads/{thread_id}/tasks/{id}/runs/{run_id}", tasks.GetRun) mux.HandleFunc("POST /api/assistants/{assistant_id}/tasks/{id}/runs/{run_id}/abort", tasks.AbortRun) mux.HandleFunc("DELETE /api/assistants/{assistant_id}/tasks/{id}/runs/{run_id}", tasks.DeleteRun) + mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{task_id}/runs/{run_id}/files", assistants.Files) + mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{task_id}/runs/{run_id}/file/{file...}", assistants.GetFile) mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{id}/events", tasks.Events) mux.HandleFunc("POST /api/assistants/{assistant_id}/tasks/{id}/events", tasks.Abort) mux.HandleFunc("GET /api/assistants/{assistant_id}/tasks/{id}/runs/{run_id}/events", tasks.Events) diff --git a/ui/user/src/lib/actions/div.ts b/ui/user/src/lib/actions/div.ts index 2e4cd925..424da0ec 100644 --- a/ui/user/src/lib/actions/div.ts +++ b/ui/user/src/lib/actions/div.ts @@ -1,7 +1,11 @@ +import { tick } from 'svelte'; + export function autoscroll(node: HTMLElement) { const observer = new MutationObserver(() => { if (node.dataset.scroll !== 'false') { - node.scrollTop = node.scrollHeight; + tick().then(() => { + node.scrollTop = node.scrollHeight; + }); } }); diff --git a/ui/user/src/lib/components/Editors.svelte b/ui/user/src/lib/components/Editors.svelte index 204c83a0..e2ee6b06 100644 --- a/ui/user/src/lib/components/Editors.svelte +++ b/ui/user/src/lib/components/Editors.svelte @@ -78,7 +78,7 @@ }} /> {:else if file.name.toLowerCase().endsWith('.png')} - + {:else} {/if} diff --git a/ui/user/src/lib/components/editor/Image.svelte b/ui/user/src/lib/components/editor/Image.svelte index a3886bb7..e3a75979 100644 --- a/ui/user/src/lib/components/editor/Image.svelte +++ b/ui/user/src/lib/components/editor/Image.svelte @@ -1,4 +1,4 @@ - {#await src then src} - AI generated, content unknown -{/await} \ No newline at end of file + AI generated, content unknown +{/await} diff --git a/ui/user/src/lib/components/messages/Input.svelte b/ui/user/src/lib/components/messages/Input.svelte index 3733624c..a2373b5b 100644 --- a/ui/user/src/lib/components/messages/Input.svelte +++ b/ui/user/src/lib/components/messages/Input.svelte @@ -36,7 +36,7 @@ }; for (const file of editor) { - if (file.modified) { + if (file.modified && !file.taskID) { if (!input.changedFiles) { input.changedFiles = {}; } diff --git a/ui/user/src/lib/components/messages/Message.svelte b/ui/user/src/lib/components/messages/Message.svelte index bb283014..56a8215d 100644 --- a/ui/user/src/lib/components/messages/Message.svelte +++ b/ui/user/src/lib/components/messages/Message.svelte @@ -22,7 +22,7 @@ let showBubble = msg.sent; let renderMarkdown = !msg.sent && !msg.oauthURL && !msg.tool; let toolTT = popover({ - placement: 'bottom-start', + placement: 'bottom-start' }); $effect(() => { diff --git a/ui/user/src/lib/components/navbar/Logo.svelte b/ui/user/src/lib/components/navbar/Logo.svelte index 5c7ac7b7..95bda20d 100644 --- a/ui/user/src/lib/components/navbar/Logo.svelte +++ b/ui/user/src/lib/components/navbar/Logo.svelte @@ -13,17 +13,6 @@ placement: 'bottom-start' }); - function icon(a: Assistant | undefined): string { - if (!a) { - return ''; - } - - if ($darkMode) { - return (a.icons.iconDark ? a.icons.iconDark : a.icons.icon) ?? ''; - } - return a.icons.icon ?? ''; - } - function collapsedIcon(a: Assistant | undefined): string { if (!a) { return ''; diff --git a/ui/user/src/lib/components/tasks/Files.svelte b/ui/user/src/lib/components/tasks/Files.svelte new file mode 100644 index 00000000..51d432d0 --- /dev/null +++ b/ui/user/src/lib/components/tasks/Files.svelte @@ -0,0 +1,83 @@ + + +{#if files && files.items.length > 0} +
+
+

Files

+ +
+
    + {#each files.items as file} +
  • +
    + +
    +
  • + {/each} +
+
+{/if} diff --git a/ui/user/src/lib/components/tasks/Input.svelte b/ui/user/src/lib/components/tasks/Input.svelte index e55bf1d1..527fad02 100644 --- a/ui/user/src/lib/components/tasks/Input.svelte +++ b/ui/user/src/lib/components/tasks/Input.svelte @@ -103,7 +103,7 @@ {/each} {:else if task?.email}

{titlePrefix}Email

-
+
select(run.id)}> {formatTime(run.created)} - + {formatInput(run)} - + {#if run.startTime && run.endTime} {Math.round( (new Date(run.endTime).getTime() - new Date(run.startTime).getTime()) / 1000 @@ -135,7 +141,7 @@ {/if}
+ +{#if selectedRun} + +{:else if editMode} + +{/if} diff --git a/ui/user/src/lib/icons/AssistantIcon.svelte b/ui/user/src/lib/icons/AssistantIcon.svelte index 85a82bff..194d3ee9 100644 --- a/ui/user/src/lib/icons/AssistantIcon.svelte +++ b/ui/user/src/lib/icons/AssistantIcon.svelte @@ -9,12 +9,14 @@ let { id }: Props = $props(); - let assistant = $derived($assistants.find((a) => { - if (id) { - return a.id === id; - } - return a.current - })); + let assistant = $derived( + $assistants.find((a) => { + if (id) { + return a.id === id; + } + return a.current; + }) + ); function icon(a: Assistant | undefined): string { if (!a) { diff --git a/ui/user/src/lib/services/chat/messages.ts b/ui/user/src/lib/services/chat/messages.ts index ee799b47..dc1a5ed1 100644 --- a/ui/user/src/lib/services/chat/messages.ts +++ b/ui/user/src/lib/services/chat/messages.ts @@ -83,7 +83,7 @@ function getFilenameAndContent(content: string) { return { filename: '', content: '' - } + }; } function reformatWriteMessage(msg: Message, last: boolean) { diff --git a/ui/user/src/lib/services/chat/operations.ts b/ui/user/src/lib/services/chat/operations.ts index b41c3d1e..84c5e25d 100644 --- a/ui/user/src/lib/services/chat/operations.ts +++ b/ui/user/src/lib/services/chat/operations.ts @@ -46,7 +46,22 @@ export async function deleteFile(assistant: string, filename: string) { return doDelete(`/assistants/${assistant}/files/${filename}`); } -export async function getFile(assistant: string, filename: string): Promise { +export async function getFile( + assistant: string, + filename: string, + opts?: { + taskID?: string; + runID?: string; + } +): Promise { + if (opts?.taskID && opts?.runID) { + return (await doGet( + `/assistants/${assistant}/tasks/${opts.taskID}/runs/${opts.runID}/file/${filename}`, + { + blob: true + } + )) as Blob; + } return (await doGet(`/assistants/${assistant}/file/${filename}`, { blob: true })) as Blob; @@ -77,8 +92,21 @@ export async function listKnowledgeFiles(assistant: string): Promise { - const files = (await doGet(`/assistants/${assistant}/files`)) as Files; +export async function listFiles( + assistant: string, + opts?: { + taskID?: string; + runID?: string; + } +): Promise { + let files: Files; + if (opts?.taskID && opts?.runID) { + files = (await doGet( + `/assistants/${assistant}/tasks/${opts.taskID}/runs/${opts.runID}/files` + )) as Files; + } else { + files = (await doGet(`/assistants/${assistant}/files`)) as Files; + } if (!files.items) { files.items = []; } diff --git a/ui/user/src/lib/services/chat/thread.svelte.ts b/ui/user/src/lib/services/chat/thread.svelte.ts index d62fb507..733e5456 100644 --- a/ui/user/src/lib/services/chat/thread.svelte.ts +++ b/ui/user/src/lib/services/chat/thread.svelte.ts @@ -22,7 +22,6 @@ export class Thread { onClose?: () => void; } ) { - const reconnect = (): EventSource => { console.log('Message EventSource initializing'); this.replayComplete = false; @@ -40,15 +39,15 @@ export class Thread { console.log('Message EventSource closed by server'); opts?.onClose?.(); es.close(); - this.#es = reconnect() + this.#es = reconnect(); }); es.onerror = (e: Event) => { if (e.eventPhase === EventSource.CLOSED) { console.log('Message EventSource closed'); } }; - return es - } + return es; + }; this.#assistant = assistant; this.#es = reconnect(); diff --git a/ui/user/src/lib/services/editor/index.svelte.ts b/ui/user/src/lib/services/editor/index.svelte.ts index 6f87daf7..13006638 100644 --- a/ui/user/src/lib/services/editor/index.svelte.ts +++ b/ui/user/src/lib/services/editor/index.svelte.ts @@ -16,7 +16,14 @@ const editor: Editor = { }; export interface Editor { - load: (assistant: string, id: string) => Promise; + load: ( + assistant: string, + id: string, + opts?: { + taskID?: string; + runID?: string; + } + ) => Promise; remove: (name: string) => void; select: (name: string) => void; items: EditorItem[]; @@ -29,9 +36,20 @@ function hasItem(id: string): boolean { return item !== undefined; } -async function load(assistant: string, id: string) { - if (hasItem(id)) { - select(id); +async function load( + assistant: string, + id: string, + opts?: { + taskID?: string; + runID?: string; + } +) { + let fileID = id; + if (opts?.taskID && opts?.runID) { + fileID = `${opts.taskID}/${opts.runID}/${id}`; + } + if (hasItem(fileID)) { + select(fileID); visible.set(true); return; } @@ -45,7 +63,7 @@ async function load(assistant: string, id: string) { visible.set(true); return; } - await loadFile(assistant, id); + await loadFile(assistant, id, opts); visible.set(true); } @@ -87,12 +105,25 @@ async function loadTask(assistant: string, taskID: string) { } } -async function loadFile(assistant: string, file: string) { +async function loadFile( + assistant: string, + file: string, + opts?: { + taskID?: string; + runID?: string; + } +) { try { - const blob = await ChatService.getFile(assistant, file); - const contents = await blob.text() + const blob = await ChatService.getFile(assistant, file, opts); + const contents = await blob.text(); + let fileID = file; + if (opts?.taskID && opts?.runID) { + fileID = `${opts.taskID}/${opts.runID}/${file}`; + } const targetFile = { - id: file, + id: fileID, + taskID: opts?.taskID, + runID: opts?.runID, name: file, contents, blob: blob, @@ -101,7 +132,7 @@ async function loadFile(assistant: string, file: string) { selected: true }; items.push(targetFile); - select(targetFile.name); + select(targetFile.id); } catch { // ignore error } diff --git a/ui/user/src/lib/stores/editor.svelte.ts b/ui/user/src/lib/stores/editor.svelte.ts index bff78df1..2489ebf1 100644 --- a/ui/user/src/lib/stores/editor.svelte.ts +++ b/ui/user/src/lib/stores/editor.svelte.ts @@ -10,6 +10,8 @@ export interface EditorItem { selected?: boolean; task?: Task; table?: string; + taskID?: string; + runID?: string; } const items = $state([]);