@alexkroman1/aai-cli 5.14.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
- package/dist/_agent.d.ts +18 -0
- package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
- package/dist/_api-client.d.ts +39 -0
- package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
- package/dist/_bundler.d.ts +43 -5
- package/dist/_cli-common.d.ts +67 -3
- package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
- package/dist/_deploy.d.ts +2 -5
- package/dist/_dev-restart.d.ts +6 -1
- package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
- package/dist/_dev-server.d.ts +30 -2
- package/dist/_e2e-test-utils.d.ts +80 -0
- package/dist/_fault-mode.d.ts +175 -0
- package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
- package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
- package/dist/_output.d.ts +9 -2
- package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
- package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
- package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
- package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
- package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
- package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
- package/dist/_utils.d.ts +18 -2
- package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
- package/dist/_vite-env.d.ts +30 -0
- package/dist/build-BO1Ni6oJ.mjs +62 -0
- package/dist/build.d.ts +19 -0
- package/dist/cli-workflow.d.ts +10 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +406 -224
- package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
- package/dist/delete.d.ts +2 -8
- package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
- package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
- package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
- package/dist/eject.d.ts +30 -3
- package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
- package/dist/init.d.ts +11 -0
- package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +498 -91
- package/dist/scaffold/package.json +11 -8
- package/dist/scaffold/pnpm-workspace.yaml +8 -0
- package/dist/scaffold/server.mjs +53 -57
- package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
- package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
- package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
- package/dist/templates/code-interpreter/agent.ts +0 -2
- package/dist/templates/dispatch-center/agent.test.ts +17 -19
- package/dist/templates/dispatch-center/agent.ts +2 -29
- package/dist/templates/dispatch-center/client.tsx +33 -16
- package/dist/templates/dispatch-center/shared.ts +53 -19
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
- package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
- package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
- package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
- package/dist/templates/embedded-assets/agent.test.ts +83 -0
- package/dist/templates/embedded-assets/agent.ts +1 -48
- package/dist/templates/embedded-assets/shared.ts +17 -0
- package/dist/templates/embedded-assets/system-prompt.md +13 -0
- package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
- package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
- package/dist/templates/health-assistant/agent.test.ts +169 -0
- package/dist/templates/health-assistant/agent.ts +1 -166
- package/dist/templates/health-assistant/fda.ts +100 -0
- package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
- package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
- package/dist/templates/infocom-adventure/agent.test.ts +205 -0
- package/dist/templates/infocom-adventure/agent.ts +3 -106
- package/dist/templates/infocom-adventure/client.tsx +28 -12
- package/dist/templates/infocom-adventure/shared.ts +28 -11
- package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
- package/dist/templates/link-digest/agent.test.ts +223 -0
- package/dist/templates/link-digest/agent.ts +79 -0
- package/dist/templates/link-digest/client.tsx +146 -0
- package/dist/templates/link-digest/workflows/digest.ts +248 -0
- package/dist/templates/math-buddy/agent.ts +0 -2
- package/dist/templates/night-owl/agent.test.ts +80 -0
- package/dist/templates/night-owl/agent.ts +1 -68
- package/dist/templates/night-owl/system-prompt.md +10 -0
- package/dist/templates/night-owl/tools/recommend.ts +64 -0
- package/dist/templates/personal-finance/agent.ts +0 -2
- package/dist/templates/pizza-ordering/agent.test.ts +52 -47
- package/dist/templates/pizza-ordering/agent.ts +9 -158
- package/dist/templates/pizza-ordering/shared.ts +35 -12
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
- package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
- package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
- package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
- package/dist/templates/plan-and-execute/agent.test.ts +411 -0
- package/dist/templates/plan-and-execute/agent.ts +23 -0
- package/dist/templates/plan-and-execute/client.tsx +115 -0
- package/dist/templates/plan-and-execute/graph.ts +178 -0
- package/dist/templates/plan-and-execute/prompts.ts +110 -0
- package/dist/templates/plan-and-execute/shared.ts +161 -0
- package/dist/templates/plan-and-execute/system-prompt.md +18 -0
- package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
- package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
- package/dist/templates/recap-workflow/agent.test.ts +762 -0
- package/dist/templates/recap-workflow/agent.ts +95 -0
- package/dist/templates/recap-workflow/shared.ts +43 -0
- package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
- package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
- package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
- package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
- package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
- package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
- package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
- package/dist/templates/redline/agent.test.ts +222 -0
- package/dist/templates/redline/agent.ts +89 -0
- package/dist/templates/redline/client.tsx +184 -0
- package/dist/templates/redline/workflows/prompts.ts +68 -0
- package/dist/templates/redline/workflows/redline.ts +258 -0
- package/dist/templates/research-workflow/agent.test.ts +525 -0
- package/dist/templates/research-workflow/agent.ts +94 -0
- package/dist/templates/research-workflow/shared.ts +35 -0
- package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
- package/dist/templates/research-workflow/tools/request_research.ts +33 -0
- package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
- package/dist/templates/research-workflow/tools/research_status.ts +40 -0
- package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
- package/dist/templates/research-workflow/workflows/research.ts +558 -0
- package/dist/templates/retail/agent.test.ts +236 -172
- package/dist/templates/retail/agent.ts +5 -42
- package/dist/templates/retail/client.tsx +33 -11
- package/dist/templates/retail/registry.test.ts +31 -16
- package/dist/templates/retail/resolve.ts +28 -64
- package/dist/templates/retail/shared.test.ts +4 -3
- package/dist/templates/retail/shared.ts +24 -7
- package/dist/templates/retail/store.test.ts +12 -10
- package/dist/templates/retail/store.ts +69 -30
- package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
- package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
- package/dist/templates/retail/tools/get_item_details.ts +5 -7
- package/dist/templates/retail/tools/get_order_details.ts +5 -7
- package/dist/templates/retail/tools/get_product_details.ts +4 -7
- package/dist/templates/retail/tools/get_user_details.ts +4 -7
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
- package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
- package/dist/templates/retail/tools/modify_user_address.ts +4 -9
- package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +152 -29
- package/dist/templates/solo-rpg/agent.ts +3 -21
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +19 -11
- package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
- package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
- package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
- package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
- package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
- package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
- package/dist/templates/support-line/agent.test.ts +343 -0
- package/dist/templates/support-line/agent.ts +27 -0
- package/dist/templates/support-line/client.tsx +122 -0
- package/dist/templates/support-line/graph.ts +224 -0
- package/dist/templates/support-line/knowledge.json +65 -0
- package/dist/templates/support-line/prompts.ts +95 -0
- package/dist/templates/support-line/shared.ts +240 -0
- package/dist/templates/support-line/system-prompt.md +17 -0
- package/dist/templates/support-line/tools/answer_question.ts +72 -0
- package/dist/templates/support-line/tools/list_topics.ts +22 -0
- package/dist/templates/support-line/tools/log_ticket.ts +30 -0
- package/dist/templates/transcription-workflow/agent.test.ts +563 -0
- package/dist/templates/transcription-workflow/agent.ts +92 -0
- package/dist/templates/transcription-workflow/client.tsx +234 -0
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
- package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
- package/dist/templates/travel-concierge/agent.test.ts +338 -0
- package/dist/templates/travel-concierge/agent.ts +29 -0
- package/dist/templates/travel-concierge/client.tsx +161 -0
- package/dist/templates/travel-concierge/routing.ts +138 -0
- package/dist/templates/travel-concierge/shared.ts +547 -0
- package/dist/templates/travel-concierge/system-prompt.md +24 -0
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
- package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
- package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
- package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
- package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
- package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
- package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
- package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
- package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
- package/dist/templates/web-researcher/agent.ts +0 -2
- package/dist/templates/web-researcher/system-prompt.md +10 -0
- package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
- package/dist/typecheck.mjs +17 -15
- package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
- package/dist/worker-bundler.d.ts +14 -0
- package/dist/worker-bundler.mjs +1 -108
- package/dist/workflow-B-O97AqI.mjs +125 -0
- package/dist/workflow-bundler.d.ts +133 -0
- package/dist/workflow.d.ts +67 -0
- package/package.json +13 -9
- package/dist/build-DQJ1ZSm9.mjs +0 -39
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* A WORKFLOW APP that really transcribes: point it at a recording and it comes
|
|
4
|
+
* back with the text.
|
|
5
|
+
*
|
|
6
|
+
* `link-digest` is the template to read first: it owns the shape —
|
|
7
|
+
* `workflowApp()`, no session, no tools, a form that starts a run and a page
|
|
8
|
+
* that watches it — and none of that is restated here. What this one adds is a
|
|
9
|
+
* workflow that does real, rate-limited, provider-shaped work:
|
|
10
|
+
* `workflows/transcribe.ts` splits the recording into pieces the sync API will
|
|
11
|
+
* accept, transcribes every piece in its own step, and stitches the results back
|
|
12
|
+
* together. Its module doc is where the argument lives.
|
|
13
|
+
*
|
|
14
|
+
* ## What it needs
|
|
15
|
+
*
|
|
16
|
+
* - **`ASSEMBLYAI_API_KEY` in the agent env** — `.env` under `aai dev`,
|
|
17
|
+
* `aai secret put ASSEMBLYAI_API_KEY` once deployed. `requiredEnv` below is
|
|
18
|
+
* what makes a deploy check for it rather than letting the first run find out.
|
|
19
|
+
* A step reads it with `requireStepEnv`; see `@alexkroman1/aai/utils`.
|
|
20
|
+
* - **Storage** (`aai storage enable`, or `DATABASE_URL` under `aai dev`) — runs
|
|
21
|
+
* live there.
|
|
22
|
+
* - **A linear-PCM WAV.** The cutting is arithmetic over byte offsets, which is
|
|
23
|
+
* only possible on uncompressed audio; `workflows/wav.ts` says so in more
|
|
24
|
+
* detail, and an unsupported file fails the run by name with the `ffmpeg`
|
|
25
|
+
* line that fixes it.
|
|
26
|
+
*
|
|
27
|
+
* ## The recording is UPLOADED, and the run carries its id
|
|
28
|
+
*
|
|
29
|
+
* A workflow's input is journaled and replayed on every resume, so a
|
|
30
|
+
* recording's BYTES cannot live in it — they would be re-read for the life of
|
|
31
|
+
* the run, and the run API's own body cap is 64 KB besides. So the file goes to
|
|
32
|
+
* `POST /workflows/uploads` (the browser does this for you: `uploads` below is
|
|
33
|
+
* what makes `<WorkflowFields>` render a file picker, and `useWorkflowSubmit`
|
|
34
|
+
* stores the file before starting the run), the input carries the returned id,
|
|
35
|
+
* and each step reads exactly the window it needs with `readUpload` — which is
|
|
36
|
+
* what keeps sixty steps from moving the same recording sixty times.
|
|
37
|
+
*
|
|
38
|
+
* None of that is this template's code. Uploads are the SDK's, for the reason
|
|
39
|
+
* every workflow app hits this wall on its first form.
|
|
40
|
+
*
|
|
41
|
+
* ## It is scriptable, which is the other half of having an API
|
|
42
|
+
*
|
|
43
|
+
* The page is one caller. Two requests do the same thing from a shell — upload,
|
|
44
|
+
* then start a run naming the id `wait` holds open until it finishes:
|
|
45
|
+
*
|
|
46
|
+
* ```sh
|
|
47
|
+
* ID=$(curl -s -X POST "https://<your-agent>/workflows/uploads?name=standup.wav" \
|
|
48
|
+
* -H 'content-type: audio/wav' --data-binary @standup.wav | jq -r .id)
|
|
49
|
+
*
|
|
50
|
+
* curl -X POST https://<your-agent>/workflows/runs \
|
|
51
|
+
* -H 'content-type: application/json' \
|
|
52
|
+
* -d "{\"workflow\":\"transcribe\",\"wait\":30000,\"input\":{
|
|
53
|
+
* \"recording\":\"$ID\"}}"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { workflow, workflowApp } from "@alexkroman1/aai";
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
import { transcribeFlow } from "./workflows/transcribe.ts";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The declaration: schema, description, and the directive body.
|
|
63
|
+
*
|
|
64
|
+
* Exported so `WorkflowOutputOf<typeof transcribe>` names the output type in one
|
|
65
|
+
* place — including from `client.tsx`, where `import type` is erased and so
|
|
66
|
+
* bundles nothing server-side.
|
|
67
|
+
*/
|
|
68
|
+
export const transcribe = workflow({
|
|
69
|
+
description: "Transcribe a recording by splitting it into chunks the sync API accepts",
|
|
70
|
+
// ONE field, and the form is exactly as long as the schema — which is the
|
|
71
|
+
// reason `client.tsx` has no field markup in it. A language picker used to sit
|
|
72
|
+
// beside this and is gone: the model detects the language, so the control was
|
|
73
|
+
// asking a person to answer a question the service answers better.
|
|
74
|
+
input: z.object({
|
|
75
|
+
// A plain string, because an upload id is what the run really receives. What
|
|
76
|
+
// makes it a file picker rather than a text box is the `uploads` line below.
|
|
77
|
+
recording: z.string().describe("A linear-PCM WAV recording (16-bit or 8-bit, any rate)"),
|
|
78
|
+
}),
|
|
79
|
+
// The one line that makes the form take a file: `<WorkflowFields>` renders a
|
|
80
|
+
// picker for this property, `useWorkflowSubmit` stores the chosen file, and
|
|
81
|
+
// the steps read it back with `readUpload`.
|
|
82
|
+
uploads: ["recording"],
|
|
83
|
+
run: transcribeFlow,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export default workflowApp({
|
|
87
|
+
name: "Transcription Desk",
|
|
88
|
+
workflows: { transcribe },
|
|
89
|
+
// Checked at deploy time, so a missing key is a warning naming it rather than
|
|
90
|
+
// a run that fails on its second step.
|
|
91
|
+
requiredEnv: ["ASSEMBLYAI_API_KEY"],
|
|
92
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Copyright 2026 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* The transcription desk's page: a form, a progress log, and a transcript.
|
|
4
|
+
*
|
|
5
|
+
* `link-digest` is the smaller example and the one to read first — it shows the
|
|
6
|
+
* primitives raw: `createWorkflowApi()` to start a run, `useWorkflowRun()` to
|
|
7
|
+
* watch it, and a hand-written `<form>` with its own `useState`. This page is
|
|
8
|
+
* the same thing with the two pieces that hand-rolling gets tedious:
|
|
9
|
+
*
|
|
10
|
+
* - **`useWorkflowSubmit`** is `api.start` plus `useWorkflowRun` plus the four
|
|
11
|
+
* pieces of state between them (the run id, the in-flight submit, whether the
|
|
12
|
+
* RUN is still going, and which of the two failed).
|
|
13
|
+
* - **`<Form>` and the field components** collect the values off the DOM, typed
|
|
14
|
+
* — a number field yields a number, a checkbox a boolean, an empty optional
|
|
15
|
+
* field nothing at all — which matters because those values go straight into
|
|
16
|
+
* the workflow's input where a zod schema is waiting.
|
|
17
|
+
*
|
|
18
|
+
* ## The form is DECLARED, not written
|
|
19
|
+
*
|
|
20
|
+
* There is no field markup here at all. `<WorkflowFields>` renders a control per
|
|
21
|
+
* SCALAR property of the workflow's own input schema, which it reads from
|
|
22
|
+
* `GET /workflows` — so the file picker exists because `agent.ts` declares
|
|
23
|
+
* `recording`, `.describe()` is what labels it, and adding a second scalar there
|
|
24
|
+
* adds a second control here with no edit. (A language picker used to sit beside
|
|
25
|
+
* it, and went with the schema field: the model detects the language, so the
|
|
26
|
+
* control asked a person to answer a question the service answers better.)
|
|
27
|
+
*
|
|
28
|
+
* The FILE half is the same mechanism one step further: `recording` is a string
|
|
29
|
+
* in the schema (it carries an upload id) and appears in the workflow's
|
|
30
|
+
* `uploads` list, which is what turns it into a file picker — and
|
|
31
|
+
* `useWorkflowSubmit` stores the chosen file through `POST /workflows/uploads`
|
|
32
|
+
* before it starts the run. A run input is journaled and replayed, so bytes can
|
|
33
|
+
* never travel in one; this page contains no upload code because the SDK owns
|
|
34
|
+
* that.
|
|
35
|
+
*
|
|
36
|
+
* That works because this workflow's input is scalars all the way down. A page
|
|
37
|
+
* whose schema has an object or array property writes those fields itself, in
|
|
38
|
+
* the same `<Form>` — every field in `@alexkroman1/aai-ui` is a plain named
|
|
39
|
+
* control, so declared and hand-written ones mix freely.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
43
|
+
import type { WorkflowOutputOf } from "@alexkroman1/aai";
|
|
44
|
+
import {
|
|
45
|
+
Form,
|
|
46
|
+
isTerminal,
|
|
47
|
+
page,
|
|
48
|
+
SubmitButton,
|
|
49
|
+
useWorkflowRuns,
|
|
50
|
+
useWorkflowSubmit,
|
|
51
|
+
WorkflowFields,
|
|
52
|
+
WorkflowProgress,
|
|
53
|
+
type WorkflowRun,
|
|
54
|
+
} from "@alexkroman1/aai-ui";
|
|
55
|
+
import { useEffect, useState } from "react";
|
|
56
|
+
import type { transcribe } from "./agent.ts";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* What a finished run reports.
|
|
60
|
+
*
|
|
61
|
+
* Derived from the workflow declaration rather than restated — `import type` is
|
|
62
|
+
* erased, so naming `transcribe` here bundles none of the agent, the SDK, or the
|
|
63
|
+
* workflow body into this page.
|
|
64
|
+
*/
|
|
65
|
+
type Transcript = WorkflowOutputOf<typeof transcribe>;
|
|
66
|
+
|
|
67
|
+
/** The workflow this page drives. Matches the key in `workflowApp({ workflows })`. */
|
|
68
|
+
const WORKFLOW = "transcribe";
|
|
69
|
+
|
|
70
|
+
/** Most past runs the history list shows. */
|
|
71
|
+
const HISTORY_LIMIT = 10;
|
|
72
|
+
|
|
73
|
+
function TranscriptionDesk() {
|
|
74
|
+
const { submit, run, pending, error, reset } = useWorkflowSubmit<Transcript>(WORKFLOW);
|
|
75
|
+
const history = useWorkflowRuns<Transcript>(WORKFLOW, { limit: HISTORY_LIMIT });
|
|
76
|
+
// Which past run the reader is looking at, if any. Its own state rather than
|
|
77
|
+
// a route, because a workflow app is one page and a run id is not a place.
|
|
78
|
+
const [openId, setOpenId] = useState<string | undefined>(undefined);
|
|
79
|
+
|
|
80
|
+
// The list is read once and re-read on demand (see `useWorkflowRuns`), and
|
|
81
|
+
// this is the "on demand": the moment the run this page started settles, the
|
|
82
|
+
// history it is missing from is stale. `run.status` rather than `run` — the
|
|
83
|
+
// watch re-reads on an interval, and depending on the object would refetch
|
|
84
|
+
// the whole list every poll.
|
|
85
|
+
const settled = run && isTerminal(run) ? run.runId : undefined;
|
|
86
|
+
const refresh = history.refresh;
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (settled) refresh();
|
|
89
|
+
}, [settled, refresh]);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<main className="mx-auto flex max-w-2xl flex-col gap-8 p-8">
|
|
93
|
+
<header className="flex flex-col gap-1">
|
|
94
|
+
<h1 className="text-2xl font-medium">Transcription Desk</h1>
|
|
95
|
+
<p className="text-sm opacity-70">
|
|
96
|
+
Upload a WAV recording. It is split into chunks, transcribed chunk by chunk, and stitched
|
|
97
|
+
back together by a durable workflow — so you can close this tab and come back to it.
|
|
98
|
+
</p>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
{/* No mapping: the collected values already match the input schema. */}
|
|
102
|
+
<Form onSubmit={(values) => submit(values)} error={error}>
|
|
103
|
+
{/* The NAME, so the schema is fetched here rather than by this page. */}
|
|
104
|
+
<WorkflowFields workflow={WORKFLOW} />
|
|
105
|
+
<SubmitButton pending={pending}>Transcribe</SubmitButton>
|
|
106
|
+
</Form>
|
|
107
|
+
|
|
108
|
+
{run && <RunPanel run={run} onClear={reset} />}
|
|
109
|
+
|
|
110
|
+
<History
|
|
111
|
+
runs={history.runs}
|
|
112
|
+
error={history.error}
|
|
113
|
+
openId={openId}
|
|
114
|
+
onOpen={(runId) => setOpenId((current) => (current === runId ? undefined : runId))}
|
|
115
|
+
/>
|
|
116
|
+
</main>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Every recent run, newest first, with its transcript one click away.
|
|
122
|
+
*
|
|
123
|
+
* This is what a durable workflow with an HTTP API is FOR, and the page used to
|
|
124
|
+
* squander it: a run id is the whole handle — no session, no cookie — so
|
|
125
|
+
* `GET /workflows/runs` can answer "what has this desk transcribed" for any tab,
|
|
126
|
+
* any machine, days later. What stood here instead was a text box asking the
|
|
127
|
+
* reader to paste an id they would have had to write down, which is the same
|
|
128
|
+
* information behind a worse door.
|
|
129
|
+
*/
|
|
130
|
+
function History({
|
|
131
|
+
runs,
|
|
132
|
+
error,
|
|
133
|
+
openId,
|
|
134
|
+
onOpen,
|
|
135
|
+
}: {
|
|
136
|
+
runs: WorkflowRun<Transcript>[];
|
|
137
|
+
error: string | undefined;
|
|
138
|
+
openId: string | undefined;
|
|
139
|
+
onOpen: (runId: string) => void;
|
|
140
|
+
}) {
|
|
141
|
+
return (
|
|
142
|
+
<section className="flex flex-col gap-3 border-t pt-6">
|
|
143
|
+
<h2 className="text-sm font-medium uppercase tracking-[1.2px]">Previous runs</h2>
|
|
144
|
+
{error !== undefined && <p className="text-sm text-red-600">{error}</p>}
|
|
145
|
+
{runs.length === 0 && error === undefined && (
|
|
146
|
+
<p className="text-sm opacity-60">Nothing transcribed yet.</p>
|
|
147
|
+
)}
|
|
148
|
+
<ul className="flex flex-col">
|
|
149
|
+
{runs.map((entry) => (
|
|
150
|
+
<li key={entry.runId} className="border-b last:border-b-0">
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
onClick={() => onOpen(entry.runId)}
|
|
154
|
+
className="flex w-full items-baseline justify-between gap-4 py-2 text-left text-sm"
|
|
155
|
+
>
|
|
156
|
+
<span className="truncate">{title(entry)}</span>
|
|
157
|
+
<span className="shrink-0 text-xs opacity-60">{STATUS_LINE[entry.status]}</span>
|
|
158
|
+
</button>
|
|
159
|
+
{openId === entry.runId && <RunPanel run={entry} />}
|
|
160
|
+
</li>
|
|
161
|
+
))}
|
|
162
|
+
</ul>
|
|
163
|
+
</section>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* One line naming a past run.
|
|
169
|
+
*
|
|
170
|
+
* The FILE where there is one — `mergeTranscript` puts the recording's own name
|
|
171
|
+
* in the output for exactly this — falling back to the id, which is all a run
|
|
172
|
+
* that failed before it read the upload ever had.
|
|
173
|
+
*/
|
|
174
|
+
function title(run: WorkflowRun<Transcript>): string {
|
|
175
|
+
if (run.status === "completed") return run.output.source;
|
|
176
|
+
return run.runId;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The run's status, its narration, and its transcript once there is one. */
|
|
180
|
+
function RunPanel({ run, onClear }: { run: WorkflowRun<Transcript>; onClear?: () => void }) {
|
|
181
|
+
return (
|
|
182
|
+
<section className="flex flex-col gap-3 rounded-md border p-5">
|
|
183
|
+
<div className="flex items-baseline justify-between gap-4">
|
|
184
|
+
<h2 className="text-sm font-medium uppercase tracking-[1.2px]">
|
|
185
|
+
{STATUS_LINE[run.status]}
|
|
186
|
+
</h2>
|
|
187
|
+
{onClear && (
|
|
188
|
+
<button type="button" onClick={onClear} className="text-xs underline opacity-60">
|
|
189
|
+
Clear
|
|
190
|
+
</button>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* The run's own narration, oldest first — the complement of `STATUS_LINE`
|
|
195
|
+
below, and the reason both exist: the status is `running` for the whole
|
|
196
|
+
fan-out, so a sixty-segment recording and a one-segment recording look
|
|
197
|
+
identical while they run. These lines come from the run itself
|
|
198
|
+
(`report()` in `workflows/transcribe.ts`), and they REPLAY, so looking a
|
|
199
|
+
finished run up in the panel below shows how it got there. */}
|
|
200
|
+
<WorkflowProgress runId={run.runId} />
|
|
201
|
+
|
|
202
|
+
{/* Discriminated on `status`, so `output` and `error` are reachable
|
|
203
|
+
without a cast — the reason a snapshot is a union rather than a flat
|
|
204
|
+
object with optional fields. */}
|
|
205
|
+
{run.status === "completed" && (
|
|
206
|
+
<>
|
|
207
|
+
<p className="text-xs opacity-60">
|
|
208
|
+
{run.output.segments} segments · {Math.round(run.output.durationMs / 1000)}s ·{" "}
|
|
209
|
+
{run.output.words} words
|
|
210
|
+
</p>
|
|
211
|
+
<pre className="whitespace-pre-wrap text-sm leading-relaxed">{run.output.transcript}</pre>
|
|
212
|
+
</>
|
|
213
|
+
)}
|
|
214
|
+
{run.status === "failed" && <p className="text-red-600">{run.error}</p>}
|
|
215
|
+
</section>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* One line describing where a run has got to.
|
|
221
|
+
*
|
|
222
|
+
* A `Record` keyed by the status union rather than a switch, so a status added
|
|
223
|
+
* to the SDK is a compile error here instead of falling through a `default:`
|
|
224
|
+
* into whichever line was last.
|
|
225
|
+
*/
|
|
226
|
+
const STATUS_LINE: Record<WorkflowRun["status"], string> = {
|
|
227
|
+
pending: "Queued",
|
|
228
|
+
running: "Transcribing…",
|
|
229
|
+
completed: "Transcript ready",
|
|
230
|
+
failed: "Failed",
|
|
231
|
+
cancelled: "Cancelled",
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
page({ name: "Transcription Desk", component: TranscriptionDesk });
|