@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.
Files changed (231) hide show
  1. package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
  2. package/dist/_agent.d.ts +18 -0
  3. package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
  4. package/dist/_api-client.d.ts +39 -0
  5. package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
  6. package/dist/_bundler.d.ts +43 -5
  7. package/dist/_cli-common.d.ts +67 -3
  8. package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
  9. package/dist/_deploy.d.ts +2 -5
  10. package/dist/_dev-restart.d.ts +6 -1
  11. package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
  12. package/dist/_dev-server.d.ts +30 -2
  13. package/dist/_e2e-test-utils.d.ts +80 -0
  14. package/dist/_fault-mode.d.ts +175 -0
  15. package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
  16. package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
  17. package/dist/_output.d.ts +9 -2
  18. package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
  19. package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
  20. package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
  21. package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
  22. package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
  23. package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
  24. package/dist/_utils.d.ts +18 -2
  25. package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
  26. package/dist/_vite-env.d.ts +30 -0
  27. package/dist/build-BO1Ni6oJ.mjs +62 -0
  28. package/dist/build.d.ts +19 -0
  29. package/dist/cli-workflow.d.ts +10 -0
  30. package/dist/cli.d.ts +1 -2
  31. package/dist/cli.mjs +406 -224
  32. package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
  33. package/dist/client-bundler.mjs +1 -1
  34. package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
  35. package/dist/delete.d.ts +2 -8
  36. package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
  37. package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
  38. package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
  39. package/dist/eject.d.ts +30 -3
  40. package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
  41. package/dist/init.d.ts +11 -0
  42. package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
  43. package/dist/project-config.mjs +1 -1
  44. package/dist/scaffold/CLAUDE.md +498 -91
  45. package/dist/scaffold/package.json +11 -8
  46. package/dist/scaffold/pnpm-workspace.yaml +8 -0
  47. package/dist/scaffold/server.mjs +53 -57
  48. package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
  49. package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
  50. package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
  51. package/dist/templates/code-interpreter/agent.ts +0 -2
  52. package/dist/templates/dispatch-center/agent.test.ts +17 -19
  53. package/dist/templates/dispatch-center/agent.ts +2 -29
  54. package/dist/templates/dispatch-center/client.tsx +33 -16
  55. package/dist/templates/dispatch-center/shared.ts +53 -19
  56. package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
  57. package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
  58. package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
  59. package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
  60. package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
  61. package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
  62. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
  63. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
  66. package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
  67. package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
  68. package/dist/templates/embedded-assets/agent.test.ts +83 -0
  69. package/dist/templates/embedded-assets/agent.ts +1 -48
  70. package/dist/templates/embedded-assets/shared.ts +17 -0
  71. package/dist/templates/embedded-assets/system-prompt.md +13 -0
  72. package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
  73. package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
  74. package/dist/templates/health-assistant/agent.test.ts +169 -0
  75. package/dist/templates/health-assistant/agent.ts +1 -166
  76. package/dist/templates/health-assistant/fda.ts +100 -0
  77. package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
  78. package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
  79. package/dist/templates/infocom-adventure/agent.test.ts +205 -0
  80. package/dist/templates/infocom-adventure/agent.ts +3 -106
  81. package/dist/templates/infocom-adventure/client.tsx +28 -12
  82. package/dist/templates/infocom-adventure/shared.ts +28 -11
  83. package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
  84. package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
  85. package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
  86. package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
  87. package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
  88. package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
  89. package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
  90. package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
  91. package/dist/templates/link-digest/agent.test.ts +223 -0
  92. package/dist/templates/link-digest/agent.ts +79 -0
  93. package/dist/templates/link-digest/client.tsx +146 -0
  94. package/dist/templates/link-digest/workflows/digest.ts +248 -0
  95. package/dist/templates/math-buddy/agent.ts +0 -2
  96. package/dist/templates/night-owl/agent.test.ts +80 -0
  97. package/dist/templates/night-owl/agent.ts +1 -68
  98. package/dist/templates/night-owl/system-prompt.md +10 -0
  99. package/dist/templates/night-owl/tools/recommend.ts +64 -0
  100. package/dist/templates/personal-finance/agent.ts +0 -2
  101. package/dist/templates/pizza-ordering/agent.test.ts +52 -47
  102. package/dist/templates/pizza-ordering/agent.ts +9 -158
  103. package/dist/templates/pizza-ordering/shared.ts +35 -12
  104. package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
  105. package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
  106. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
  107. package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
  108. package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
  109. package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
  110. package/dist/templates/plan-and-execute/agent.test.ts +411 -0
  111. package/dist/templates/plan-and-execute/agent.ts +23 -0
  112. package/dist/templates/plan-and-execute/client.tsx +115 -0
  113. package/dist/templates/plan-and-execute/graph.ts +178 -0
  114. package/dist/templates/plan-and-execute/prompts.ts +110 -0
  115. package/dist/templates/plan-and-execute/shared.ts +161 -0
  116. package/dist/templates/plan-and-execute/system-prompt.md +18 -0
  117. package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
  118. package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
  119. package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
  120. package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
  121. package/dist/templates/recap-workflow/agent.test.ts +762 -0
  122. package/dist/templates/recap-workflow/agent.ts +95 -0
  123. package/dist/templates/recap-workflow/shared.ts +43 -0
  124. package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
  125. package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
  126. package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
  127. package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
  128. package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
  129. package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
  130. package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
  131. package/dist/templates/redline/agent.test.ts +222 -0
  132. package/dist/templates/redline/agent.ts +89 -0
  133. package/dist/templates/redline/client.tsx +184 -0
  134. package/dist/templates/redline/workflows/prompts.ts +68 -0
  135. package/dist/templates/redline/workflows/redline.ts +258 -0
  136. package/dist/templates/research-workflow/agent.test.ts +525 -0
  137. package/dist/templates/research-workflow/agent.ts +94 -0
  138. package/dist/templates/research-workflow/shared.ts +35 -0
  139. package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
  140. package/dist/templates/research-workflow/tools/request_research.ts +33 -0
  141. package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
  142. package/dist/templates/research-workflow/tools/research_status.ts +40 -0
  143. package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
  144. package/dist/templates/research-workflow/workflows/research.ts +558 -0
  145. package/dist/templates/retail/agent.test.ts +236 -172
  146. package/dist/templates/retail/agent.ts +5 -42
  147. package/dist/templates/retail/client.tsx +33 -11
  148. package/dist/templates/retail/registry.test.ts +31 -16
  149. package/dist/templates/retail/resolve.ts +28 -64
  150. package/dist/templates/retail/shared.test.ts +4 -3
  151. package/dist/templates/retail/shared.ts +24 -7
  152. package/dist/templates/retail/store.test.ts +12 -10
  153. package/dist/templates/retail/store.ts +69 -30
  154. package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
  155. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
  156. package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
  157. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
  158. package/dist/templates/retail/tools/get_item_details.ts +5 -7
  159. package/dist/templates/retail/tools/get_order_details.ts +5 -7
  160. package/dist/templates/retail/tools/get_product_details.ts +4 -7
  161. package/dist/templates/retail/tools/get_user_details.ts +4 -7
  162. package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
  163. package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
  164. package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
  165. package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
  166. package/dist/templates/retail/tools/modify_user_address.ts +4 -9
  167. package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
  168. package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
  169. package/dist/templates/solo-rpg/agent.test.ts +152 -29
  170. package/dist/templates/solo-rpg/agent.ts +3 -21
  171. package/dist/templates/solo-rpg/client.tsx +19 -6
  172. package/dist/templates/solo-rpg/shared.ts +19 -11
  173. package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
  174. package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
  175. package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
  176. package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
  177. package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
  178. package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
  179. package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
  180. package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
  181. package/dist/templates/support-line/agent.test.ts +343 -0
  182. package/dist/templates/support-line/agent.ts +27 -0
  183. package/dist/templates/support-line/client.tsx +122 -0
  184. package/dist/templates/support-line/graph.ts +224 -0
  185. package/dist/templates/support-line/knowledge.json +65 -0
  186. package/dist/templates/support-line/prompts.ts +95 -0
  187. package/dist/templates/support-line/shared.ts +240 -0
  188. package/dist/templates/support-line/system-prompt.md +17 -0
  189. package/dist/templates/support-line/tools/answer_question.ts +72 -0
  190. package/dist/templates/support-line/tools/list_topics.ts +22 -0
  191. package/dist/templates/support-line/tools/log_ticket.ts +30 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +563 -0
  193. package/dist/templates/transcription-workflow/agent.ts +92 -0
  194. package/dist/templates/transcription-workflow/client.tsx +234 -0
  195. package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
  196. package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
  197. package/dist/templates/travel-concierge/agent.test.ts +338 -0
  198. package/dist/templates/travel-concierge/agent.ts +29 -0
  199. package/dist/templates/travel-concierge/client.tsx +161 -0
  200. package/dist/templates/travel-concierge/routing.ts +138 -0
  201. package/dist/templates/travel-concierge/shared.ts +547 -0
  202. package/dist/templates/travel-concierge/system-prompt.md +24 -0
  203. package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
  204. package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
  205. package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
  206. package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
  207. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
  208. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
  209. package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
  210. package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
  211. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
  212. package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
  213. package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
  214. package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
  215. package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
  216. package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
  217. package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
  218. package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
  219. package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
  220. package/dist/templates/web-researcher/agent.ts +0 -2
  221. package/dist/templates/web-researcher/system-prompt.md +10 -0
  222. package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
  223. package/dist/typecheck.mjs +17 -15
  224. package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
  225. package/dist/worker-bundler.d.ts +14 -0
  226. package/dist/worker-bundler.mjs +1 -108
  227. package/dist/workflow-B-O97AqI.mjs +125 -0
  228. package/dist/workflow-bundler.d.ts +133 -0
  229. package/dist/workflow.d.ts +67 -0
  230. package/package.json +13 -9
  231. 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 });