@happyvertical/smrt-svelte 0.51.38 → 0.52.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/AGENTS.md +3 -0
- package/README.md +167 -1
- package/agents/workspace.md +24 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +2 -1
- package/dist/components/forms/Form.svelte +118 -8
- package/dist/components/forms/Form.svelte.d.ts +18 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormScope.svelte +202 -0
- package/dist/components/forms/FormScope.svelte.d.ts +22 -0
- package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
- package/dist/components/forms/__tests__/FormScope.test.js +39 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/workspace/README.md +18 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +586 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +85 -0
- package/dist/components/workspace/__tests__/TenantNav.test.js +49 -0
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +998 -40
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +42 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +46 -1
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +84 -2
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +55 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +149 -6
- package/dist/components/workspace/admin-shell/types.d.ts +97 -0
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +11 -4
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +12 -3
- package/dist/components/workspace/tools-dock/ToolsDock.svelte +2 -0
- package/dist/i18n/strings.workspace.d.ts +10 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
- package/dist/web/__tests__/webmcp-ui.test.js +18 -0
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +4 -1
- package/dist/web/link-surface.svelte.d.ts +14 -0
- package/dist/web/link-surface.svelte.d.ts.map +1 -0
- package/dist/web/link-surface.svelte.js +49 -0
- package/dist/web/list-surface.svelte.d.ts +80 -0
- package/dist/web/list-surface.svelte.d.ts.map +1 -0
- package/dist/web/list-surface.svelte.js +309 -0
- package/dist/web/step-surface.svelte.d.ts +17 -0
- package/dist/web/step-surface.svelte.d.ts.map +1 -0
- package/dist/web/step-surface.svelte.js +56 -0
- package/dist/web/webmcp-ui-context.d.ts +7 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -1
- package/dist/web/webmcp-ui-context.js +10 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +16 -3
- package/package.json +16 -16
package/AGENTS.md
CHANGED
|
@@ -20,6 +20,9 @@ subpath you are editing. This file keeps what holds in every module.
|
|
|
20
20
|
| `src/web/webmcp-provider.ts` | Provider config for generated data/model WebMCP tools: definitions, effect policy, namespace, budget, legacy/canonical filters, and fetcher seams (#2520) | — |
|
|
21
21
|
| `src/web/webmcp-ui.ts` | Fixed, low-cardinality WebMCP adapter over the Provider's mounted form-control and data-surface registries (#2521) | — |
|
|
22
22
|
| `src/web/webmcp.svelte.ts` (`useWebMcpTool`) | Component-owned bespoke WebMCP tool. Routes through `@happyvertical/smrt-web`'s `registerWebMcpBespokeTool`, so it shares the fail-closed effect classification and `effects` exposure policy of generated tools — undeclared `annotations` classify destructive and are excluded by default. `namespace` and `maxTools` never apply to a bespoke tool (#2586) | — |
|
|
23
|
+
| `src/web/{list,link,step}-surface.svelte.ts` (`useListSurface`, `useLinkSurface`, `useStepSurface`) | Lifecycle hooks mounting a rendered list (projected `state.rows` + a `find` text lookup), a menu/tab row (`open` → `navigate`, hrefs never published), and a wizard (`next`/`back`/`go-to`; a writing step is revealed via `showNext`, never pressed) on the Provider's data-surface registry. The Svelte-free halves live in `@happyvertical/smrt-ui/data` | — |
|
|
24
|
+
| `src/web/webmcp-ui-context.ts` (`useWebMcpUi`, `tryUseWebMcpUi`) | Provider mounted-UI registries; `tryUseWebMcpUi` returns `null` instead of throwing when there is no Provider or its WebMCP UI is off (#2915) | — |
|
|
25
|
+
| `src/components/forms/Form.svelte` + `FormScope.svelte` | Rich Form spreads native attributes/attachments, takes `enhance`, and its `*_stage_changes` tool covers smrt-ui controls via `controlProposalProperties`/`stageControlProposals` (smrt-ui); `FormScope` is the same without a `<form>` element | — |
|
|
23
26
|
|
|
24
27
|
The composed WebMCP fixture in
|
|
25
28
|
`src/web/__tests__/webmcp-composed.integration.svelte.test.ts` mounts a real
|
package/README.md
CHANGED
|
@@ -182,6 +182,25 @@ default) — except a canonical table-control id (`set-filters`, `reset`,
|
|
|
182
182
|
reaches `onControl`, even under a custom label. See
|
|
183
183
|
`docs/data-surface-conformance.md` for the full contract.
|
|
184
184
|
|
|
185
|
+
Three lifecycle hooks cover the common page shapes without any registration
|
|
186
|
+
code; each resolves the nearest Provider's registry (or an explicit one),
|
|
187
|
+
re-mounts on an identity change, and unmounts with the component:
|
|
188
|
+
|
|
189
|
+
- `useListSurface(() => ({ surfaceId, label, description, columns, rows }))`
|
|
190
|
+
publishes the page's rendered rows, projected to the declared columns and
|
|
191
|
+
capped (`maxRows`, default 50), as `state.rows`. Columns marked
|
|
192
|
+
`searchable` enable a `find` control: `{ text }` publishes the rows whose
|
|
193
|
+
searchable columns contain the text (any case, accents ignored) across all
|
|
194
|
+
rows, with `state.find = { text, matches }` — so an agent can look an
|
|
195
|
+
article up by title on the page. It narrows only what the agent reads;
|
|
196
|
+
table commands stay refused and the page's own controls stay in charge.
|
|
197
|
+
- `useLinkSurface(() => ({ surfaceId, label, description, links, navigate }))`
|
|
198
|
+
exposes a menu or tab row as an `open` control (see `registerLinkSurface`
|
|
199
|
+
in smrt-ui).
|
|
200
|
+
- `useStepSurface(() => ({ surfaceId, …, steps, current, next, back,
|
|
201
|
+
nextWrites, showNext }))` exposes a wizard's `next`/`back`/`go-to`; a step
|
|
202
|
+
whose forward button writes is revealed for the person, never pressed.
|
|
203
|
+
|
|
185
204
|
The default prefix is `smrt_ui_`. Configure `ui.prefix` when multiple Providers
|
|
186
205
|
must coexist in one document; the same prefix cannot be registered twice. The
|
|
187
206
|
six derived names are reserved through the document-global tool-name lock, so
|
|
@@ -201,6 +220,47 @@ hidden data-surface columns are not serialized. Read responses are marked as
|
|
|
201
220
|
untrusted content. Bespoke `useWebMcpTool` and `<Form webmcp>` tools retain their
|
|
202
221
|
existing lifecycle and submit behavior.
|
|
203
222
|
|
|
223
|
+
#### `<Form webmcp>` with SvelteKit `enhance` and smrt-ui fields
|
|
224
|
+
|
|
225
|
+
The rich `Form` renders its own `<form>`, so it forwards every native form
|
|
226
|
+
attribute (`method`, `action`, `enctype`, `novalidate`, `autocomplete`,
|
|
227
|
+
`aria-*`, `data-*`, …) and attachments (`{@attach …}`) to that element. A
|
|
228
|
+
component cannot take `use:`, so pass SvelteKit's `enhance` as a prop — it is
|
|
229
|
+
applied to the rendered `<form>` and torn down with it. Without `onsubmit`
|
|
230
|
+
the Form never prevents submission, so `enhance` (or a native POST) runs as
|
|
231
|
+
usual and an error summary above the fields keeps working:
|
|
232
|
+
|
|
233
|
+
```svelte
|
|
234
|
+
<script lang="ts">
|
|
235
|
+
import { enhance } from '$app/forms';
|
|
236
|
+
import { Form } from '@happyvertical/smrt-svelte/forms';
|
|
237
|
+
import { ErrorSummary, FormGroup, Input } from '@happyvertical/smrt-ui/forms';
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
<Form formId="setup-network" webmcp method="POST" action="?/create" novalidate
|
|
241
|
+
enhance={(form) => enhance(form, submit)}>
|
|
242
|
+
<ErrorSummary errors={errors} />
|
|
243
|
+
<FormGroup label="Network name" id="name"><Input name="name" bind:value={name} /></FormGroup>
|
|
244
|
+
</Form>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Its `<formId>_stage_changes` tool describes rich fields (`TextInput`,
|
|
248
|
+
`MoneyInput`, …) and every other control the registry holds for the form:
|
|
249
|
+
smrt-ui primitives (`FormGroup` + `Input`/`Select`/`Textarea`/`Combobox`/…)
|
|
250
|
+
and composites registered with `useControlRegistration`. Proposals for either
|
|
251
|
+
kind are staged for the person to review and apply; nothing is written or
|
|
252
|
+
submitted by the tool.
|
|
253
|
+
|
|
254
|
+
`FormScope` (`@happyvertical/smrt-svelte/forms`) is the same tool and review
|
|
255
|
+
surface without a `<form>` element — for a fetch-driven wizard, an editor
|
|
256
|
+
saved in parts, or a page that must keep its own `<form>` markup. It joins
|
|
257
|
+
the nearest Provider's control registry (or a local one) and registers no
|
|
258
|
+
tool until at least one proposable control is mounted.
|
|
259
|
+
|
|
260
|
+
`tryUseWebMcpUi()` returns the Provider's mounted-UI registries, or `null`
|
|
261
|
+
when there is no Provider or its WebMCP UI is off — use it to fall back to a
|
|
262
|
+
local registry instead of catching `useWebMcpUi()`'s throw (#2915).
|
|
263
|
+
|
|
204
264
|
Custom rich fields may continue to call `registerField(field)` and later
|
|
205
265
|
`unregisterField(name)`. New code should retain and invoke the disposer returned
|
|
206
266
|
by `registerField`: it is bound to that exact registration, so cleanup cannot
|
|
@@ -319,6 +379,112 @@ fetch) and mount `AdminShell` in `+layout.svelte`. The `template-sveltekit`
|
|
|
319
379
|
scaffold adopts AdminShell as its default chrome exactly this way; copy its
|
|
320
380
|
`src/routes/+layout.server.ts` / `+layout.svelte` / `settings/+page.svelte`.
|
|
321
381
|
|
|
382
|
+
#### Responsive chrome and resizable edges
|
|
383
|
+
|
|
384
|
+
AdminShell carries the phone/tablet/desktop chrome itself, so hosts do not
|
|
385
|
+
wrap it in a frame. Everything below is opt-in; without these props and config
|
|
386
|
+
keys the shell renders exactly as before.
|
|
387
|
+
|
|
388
|
+
```svelte
|
|
389
|
+
<script lang="ts">
|
|
390
|
+
import {
|
|
391
|
+
AdminShell,
|
|
392
|
+
createShellState,
|
|
393
|
+
PhoneBottomBar,
|
|
394
|
+
PhoneTopBar,
|
|
395
|
+
ShellNavToggle,
|
|
396
|
+
ShellTitle,
|
|
397
|
+
phoneTopBarFor,
|
|
398
|
+
} from '@happyvertical/smrt-svelte/workspace';
|
|
399
|
+
|
|
400
|
+
const shell = createShellState({
|
|
401
|
+
config: {
|
|
402
|
+
top: false,
|
|
403
|
+
bottom: false,
|
|
404
|
+
left: {
|
|
405
|
+
label: 'Navigation',
|
|
406
|
+
// Drawer on phones, icons on tablets, open on desktop.
|
|
407
|
+
viewportDefaults: { phone: 'collapsed', tablet: 'collapsed', desktop: 'expanded' },
|
|
408
|
+
},
|
|
409
|
+
right: {
|
|
410
|
+
label: 'Assistant',
|
|
411
|
+
expandedSize: '28rem',
|
|
412
|
+
resizable: { min: 320, max: 720 }, // drag, arrows, Home/End, double-click resets
|
|
413
|
+
phone: 'sheet', // or 'hidden' when the host shows it elsewhere on phones
|
|
414
|
+
keepMounted: true, // closing the sheet keeps the chat (and its draft) alive
|
|
415
|
+
overlayMedia: '(max-width: 99.9375rem)', // slide over the page below 1600px
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
storageKey: 'my-app-shell',
|
|
419
|
+
});
|
|
420
|
+
</script>
|
|
421
|
+
|
|
422
|
+
<AdminShell state={shell} path={page.url.pathname} phone={{ scrim: true, swipeToClose: true }}>
|
|
423
|
+
{#snippet header()}
|
|
424
|
+
<ShellNavToggle expanded={shell.panels.left === 'expanded'} onclick={() => shell.togglePanel('left')} />
|
|
425
|
+
<ShellTitle title="Acme" href="/" />
|
|
426
|
+
{/snippet}
|
|
427
|
+
{#snippet phoneTopBar()}
|
|
428
|
+
<PhoneTopBar model={phoneTopBarFor({ path, homeHref: '/', homeTitle: 'Acme', navItems })} homeHref="/" />
|
|
429
|
+
{/snippet}
|
|
430
|
+
{#snippet phoneBottomBar()}
|
|
431
|
+
<PhoneBottomBar {items} />
|
|
432
|
+
{/snippet}
|
|
433
|
+
{#snippet overlays({ viewport })}<!-- PhoneSheet, WorkingStrip, … -->{/snippet}
|
|
434
|
+
…
|
|
435
|
+
</AdminShell>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
- **`header`** is a full-width row above every edge (`#smrt-admin-shell-header`,
|
|
439
|
+
`--smrt-admin-shell-header-size`, default 3.5rem). On phones it is replaced
|
|
440
|
+
by `phoneTopBar` when one is given.
|
|
441
|
+
- **Viewport classes**: `shell.viewport` is `phone` (≤ 48rem), `tablet`
|
|
442
|
+
(≤ 64rem) or `desktop`; `ADMIN_SHELL_PHONE_QUERY` / `ADMIN_SHELL_DESKTOP_QUERY`.
|
|
443
|
+
`viewportDefaults` apply on first render and whenever the class changes; such
|
|
444
|
+
an edge never persists its open/closed state.
|
|
445
|
+
- **Phone chrome**: `phoneTopBar` overlays the top of the main region and hides
|
|
446
|
+
on scroll (`phone.hideOnScroll`, pinned by `pinChrome` or an open drawer);
|
|
447
|
+
`phoneBottomBar` is a bottom row that a form's `[data-form-action-bar]` (see
|
|
448
|
+
smrt-ui `FormActionBar`) replaces and the on-screen keyboard hides
|
|
449
|
+
(`:root[data-keyboard-open]`). `phone.scrim` dims the page behind a drawer or
|
|
450
|
+
sheet, `phone.swipeToClose` closes drawers by swiping. A `path` change on a
|
|
451
|
+
phone closes open drawers. `overlays` is a layer above the edges (and above
|
|
452
|
+
the bottom bar's row) for `PhoneSheet`s and status strips.
|
|
453
|
+
- **Page contracts** on phones: `data-shell-breadcrumbs` is hidden,
|
|
454
|
+
`data-shell-page-title` is visually hidden while the phone top bar shows
|
|
455
|
+
(the bar carries the title), `data-shell-tabs` sticks under the top bar and
|
|
456
|
+
slides away with it. smrt-ui's `PageHeader` renders the first two.
|
|
457
|
+
- **Page trail**: `shellPageTrailFor({ path, homeHref, homeTitle, navItems,
|
|
458
|
+
parents })` is the one source for a page's ancestors: section homes (home
|
|
459
|
+
and top-level nav pages) have none; other pages get the home, the nav items
|
|
460
|
+
above them, then page-given `parents`, never the page itself. Pass the
|
|
461
|
+
`crumbs` to `PageHeader`; `phoneTopBarFor` (same input plus `pageTitle`)
|
|
462
|
+
sends the phone back arrow to the last crumb.
|
|
463
|
+
- **Resizable edges**: `resizable` on a `push` side edge adds a
|
|
464
|
+
`role="separator"` (with `aria-valuenow/min/max` in px) on its inner border.
|
|
465
|
+
Sizes are stored as `ShellSettingsDelta.sizes` through the settings adapter;
|
|
466
|
+
`persist: false` / `{ state?: false, size?: false }` opts an edge out of
|
|
467
|
+
storage.
|
|
468
|
+
- **Overlay edges**: `overlayMedia` on a side edge is a media query under
|
|
469
|
+
which the expanded edge slides over the page (above a scrim, content keeps
|
|
470
|
+
its width) instead of pushing it, on tablet and desktop; above it the edge
|
|
471
|
+
docks. `shell.presentationFor(edge)` reports the live presentation. While
|
|
472
|
+
an overlay is open the page and the other edge are `inert`, the panel takes
|
|
473
|
+
focus (unless something inside already has it) and returns it on close;
|
|
474
|
+
Escape or a scrim click closes it, sliding it back out as the scrim fades
|
|
475
|
+
(no motion under `prefers-reduced-motion`). Resizing applies only while docked.
|
|
476
|
+
- **Kept panels**: `keepMounted: true` keeps a collapsed edge's panel content
|
|
477
|
+
(`appPanel`, `tenantPanel`, the focus panel, `systemPanel`) mounted with the
|
|
478
|
+
`hidden` attribute instead of unmounting it, so component state (a chat
|
|
479
|
+
draft, scroll position) survives closing. The rail renders as usual; the
|
|
480
|
+
left edge keeps only a `tenantPanel`.
|
|
481
|
+
- **Nav attention**: a `ShellNavItem` with `attention: true` (or a string
|
|
482
|
+
label) shows a dot in `TenantNav`, over the icon when collapsed, and
|
|
483
|
+
announces the label ("Needs attention" by default) inside the link.
|
|
484
|
+
- **Public region ids** (`ADMIN_SHELL_REGION_IDS`): `smrt-admin-shell-header`,
|
|
485
|
+
`smrt-admin-shell-{top,left,right,bottom}-panel`, and
|
|
486
|
+
`smrt-admin-shell-main`, which is the page scroller.
|
|
487
|
+
|
|
322
488
|
- **Migration guide** (first-generation `WorkspaceShell`/`RoleShell` →
|
|
323
489
|
`AdminShell`; adoption is additive and non-breaking):
|
|
324
490
|
[`src/components/workspace/MIGRATION.md`](./src/components/workspace/MIGRATION.md)
|
|
@@ -357,7 +523,7 @@ resolve against `smrt-ui`:
|
|
|
357
523
|
| `@happyvertical/smrt-ui` | `PermissionCheck`, `permission` / `hasPermission` / `hasAnyPermission` / `hasAllPermissions` |
|
|
358
524
|
| `@happyvertical/smrt-ui/ui` | UI primitives (Button, Card, Badge, Pagination) |
|
|
359
525
|
| `@happyvertical/smrt-ui/layout` | Layout (Container, Grid, Header, Footer, Masthead, etc.) |
|
|
360
|
-
| `@happyvertical/smrt-ui/calendar` | Calendar
|
|
526
|
+
| `@happyvertical/smrt-ui/calendar` | CalendarView (deprecated: Calendar, DayView) |
|
|
361
527
|
| `@happyvertical/smrt-ui/data` | DataTable, CollectionList/ContentList, CollectionToolbar |
|
|
362
528
|
| `@happyvertical/smrt-ui/feedback` | Modal, ConfirmDialog, LoadingOverlay, ProgressBar |
|
|
363
529
|
| `@happyvertical/smrt-ui/chat` | Message bubble, reaction picker, typing indicator |
|
package/agents/workspace.md
CHANGED
|
@@ -45,6 +45,30 @@ App settings, tenant names and environment badges wrap within narrow drawers.
|
|
|
45
45
|
system bars never clip wrapped chips. Custom bars must allow this flex child
|
|
46
46
|
to shrink; use safe alignment if aligning its contents to the end.
|
|
47
47
|
|
|
48
|
+
### Responsive chrome, viewport defaults, resizable edges
|
|
49
|
+
|
|
50
|
+
The root grid has five rows: `header` · top edge · body · bottom edge · phone
|
|
51
|
+
bottom bar. The header and phone-bar tracks are `0rem` unless their snippets
|
|
52
|
+
render, so hosts that pass none of the new props get the original layout; keep
|
|
53
|
+
that invariant (`AdminShell.responsive.test.ts` "backward compatibility").
|
|
54
|
+
Phone drawers and the bottom drawer offset themselves by the header and phone
|
|
55
|
+
bar tracks. `shell.viewport` comes from `ADMIN_SHELL_PHONE_QUERY` (48rem, the
|
|
56
|
+
same boundary as the CSS media query — never make one configurable without the
|
|
57
|
+
other) and `ADMIN_SHELL_DESKTOP_QUERY` (64rem); `installShellViewport` runs on
|
|
58
|
+
mount and the `ShellState` constructor detects the initial class so the first
|
|
59
|
+
paint is right.
|
|
60
|
+
|
|
61
|
+
Edge config: `viewportDefaults` (applied per class change; such an edge never
|
|
62
|
+
stores its state), `phone: 'drawer' | 'sheet' | 'hidden'` (`sheet` is right
|
|
63
|
+
edge only), `resizable` (side edges, `push` only, not on phones) and `persist`.
|
|
64
|
+
`stripUnpersistedSettings` runs on both `hydrate()` and adapter writes;
|
|
65
|
+
runtime `applySettings` is never filtered. Resized widths live in
|
|
66
|
+
`ShellSettingsDelta.sizes` (px; `null` = configured size, pruned on write).
|
|
67
|
+
Drags update with `persist: false` and persist once on pointer up.
|
|
68
|
+
|
|
69
|
+
The resize separator is a focusable `role="separator"`: its svelte-ignore of
|
|
70
|
+
the non-interactive a11y warnings is deliberate (WAI-ARIA window splitter).
|
|
71
|
+
|
|
48
72
|
Run `pnpm --filter @happyvertical/smrt-svelte test:e2e` after building the
|
|
49
73
|
package dependencies. The package-local fixture exercises real Chromium bounds
|
|
50
74
|
and touch input across mobile widths and desktop panel-state combinations.
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { InitState, OnProgress } from '../../core/types.js';
|
|
2
2
|
import type { BrowserSpeechSTTOptions, STTAdapter, STTCapabilities, STTOptions, STTResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* An error from the browser's speech recognition. `speechError` is the Web
|
|
5
|
+
* Speech error code (`network`, `not-allowed`, `service-not-allowed`,
|
|
6
|
+
* `no-speech`, `audio-capture`, `aborted`, `language-not-supported`, …).
|
|
7
|
+
*/
|
|
8
|
+
export type BrowserSpeechError = Error & {
|
|
9
|
+
speechError?: string;
|
|
10
|
+
speechMessage?: string;
|
|
11
|
+
};
|
|
3
12
|
/**
|
|
4
13
|
* Browser Speech Recognition adapter
|
|
5
14
|
*/
|
|
@@ -9,6 +18,8 @@ export declare class BrowserSpeechSTTAdapter implements STTAdapter {
|
|
|
9
18
|
private recognition;
|
|
10
19
|
private options;
|
|
11
20
|
private _isListening;
|
|
21
|
+
/** Set while our own `abort()` runs, so its `aborted` error is not reported. */
|
|
22
|
+
private aborting;
|
|
12
23
|
private resultListeners;
|
|
13
24
|
private errorListeners;
|
|
14
25
|
private startListeners;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-speech.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/browser-speech.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EACV,uBAAuB,EACvB,UAAU,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACV,MAAM,YAAY,CAAC;AAWpB;;GAEG;AACH,qBAAa,uBAAwB,YAAW,UAAU;IACxD,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAE1C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,YAAY,CAAS;
|
|
1
|
+
{"version":3,"file":"browser-speech.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/browser-speech.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EACV,uBAAuB,EACvB,UAAU,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACV,MAAM,YAAY,CAAC;AAWpB;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,qBAAa,uBAAwB,YAAW,UAAU;IACxD,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAE1C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,YAAY,CAAS;IAC7B,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAS;IAGzB,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,YAAY,CAAyB;gBAEjC,OAAO,GAAE,uBAA4B;IAQjD,IAAI,SAAS,IAAI,SAAS,CAEzB;IAEK,iBAAiB,CAAC,WAAW,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChE,OAAO,CAAC,wBAAwB;IAqFhC,eAAe,IAAI,eAAe;IA2B5B,KAAK,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,KAAK,IAAI,IAAI;IAOb,WAAW,IAAI,OAAO;IAItB,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAK3D,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI;IAKrD,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKzC,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAS/B"}
|
|
@@ -23,6 +23,8 @@ export class BrowserSpeechSTTAdapter {
|
|
|
23
23
|
recognition = null;
|
|
24
24
|
options;
|
|
25
25
|
_isListening = false;
|
|
26
|
+
/** Set while our own `abort()` runs, so its `aborted` error is not reported. */
|
|
27
|
+
aborting = false;
|
|
26
28
|
// Event listeners
|
|
27
29
|
resultListeners = new Set();
|
|
28
30
|
errorListeners = new Set();
|
|
@@ -94,8 +96,13 @@ export class BrowserSpeechSTTAdapter {
|
|
|
94
96
|
}
|
|
95
97
|
};
|
|
96
98
|
this.recognition.onerror = (event) => {
|
|
99
|
+
const code = event.error;
|
|
100
|
+
if (code === 'aborted' && this.aborting) {
|
|
101
|
+
// We called abort() ourselves: not an error.
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
97
104
|
let error;
|
|
98
|
-
switch (
|
|
105
|
+
switch (code) {
|
|
99
106
|
case 'not-allowed':
|
|
100
107
|
case 'service-not-allowed':
|
|
101
108
|
error = new PermissionDeniedError('microphone', 'browser-speech');
|
|
@@ -110,11 +117,18 @@ export class BrowserSpeechSTTAdapter {
|
|
|
110
117
|
error = new Error('Network error during speech recognition');
|
|
111
118
|
break;
|
|
112
119
|
case 'aborted':
|
|
113
|
-
//
|
|
114
|
-
|
|
120
|
+
// Not our abort(): the browser (or another tab) cut it off.
|
|
121
|
+
error = new Error('Speech recognition was aborted');
|
|
122
|
+
break;
|
|
115
123
|
default:
|
|
116
|
-
error = new Error(`Speech recognition error: ${
|
|
124
|
+
error = new Error(`Speech recognition error: ${code}`);
|
|
117
125
|
}
|
|
126
|
+
// The Web Speech error code, so callers can tell `service-not-allowed`
|
|
127
|
+
// (the browser has no speech service, e.g. Brave) from `not-allowed`
|
|
128
|
+
// (the microphone is blocked), and `network` from everything else.
|
|
129
|
+
error.speechError = code;
|
|
130
|
+
if (event.message)
|
|
131
|
+
error.speechMessage = event.message;
|
|
118
132
|
for (const cb of this.errorListeners) {
|
|
119
133
|
cb(error);
|
|
120
134
|
}
|
|
@@ -127,6 +141,7 @@ export class BrowserSpeechSTTAdapter {
|
|
|
127
141
|
};
|
|
128
142
|
this.recognition.onend = () => {
|
|
129
143
|
this._isListening = false;
|
|
144
|
+
this.aborting = false;
|
|
130
145
|
for (const cb of this.endListeners) {
|
|
131
146
|
cb();
|
|
132
147
|
}
|
|
@@ -181,6 +196,7 @@ export class BrowserSpeechSTTAdapter {
|
|
|
181
196
|
}
|
|
182
197
|
abort() {
|
|
183
198
|
if (this.recognition && this._isListening) {
|
|
199
|
+
this.aborting = true;
|
|
184
200
|
this.recognition.abort();
|
|
185
201
|
}
|
|
186
202
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserSpeechSTTAdapter errors: every Web Speech error reaches `onError`
|
|
3
|
+
* with its code (`speechError`), including an `aborted` the adapter did not
|
|
4
|
+
* cause, and through smrt-ui's `Dictation` Brave's "starts, then `network`"
|
|
5
|
+
* becomes a plain error instead of a silent stop.
|
|
6
|
+
*/
|
|
7
|
+
import { Dictation } from '@happyvertical/smrt-ui/forms';
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { BrowserSpeechSTTAdapter, } from './browser-speech.js';
|
|
10
|
+
import { createSttDictationSource } from './dictation-source.js';
|
|
11
|
+
class FakeRecognition {
|
|
12
|
+
static last = null;
|
|
13
|
+
/** What the browser does after start(): Brave errors with `network`. */
|
|
14
|
+
static afterStart = null;
|
|
15
|
+
lang = '';
|
|
16
|
+
continuous = false;
|
|
17
|
+
interimResults = false;
|
|
18
|
+
maxAlternatives = 1;
|
|
19
|
+
onresult = null;
|
|
20
|
+
onerror = null;
|
|
21
|
+
onstart = null;
|
|
22
|
+
onend = null;
|
|
23
|
+
constructor() {
|
|
24
|
+
FakeRecognition.last = this;
|
|
25
|
+
}
|
|
26
|
+
start = vi.fn(() => {
|
|
27
|
+
this.onstart?.();
|
|
28
|
+
FakeRecognition.afterStart?.(this);
|
|
29
|
+
});
|
|
30
|
+
stop = vi.fn(() => this.onend?.());
|
|
31
|
+
abort = vi.fn(() => {
|
|
32
|
+
this.onerror?.({ error: 'aborted', message: '' });
|
|
33
|
+
this.onend?.();
|
|
34
|
+
});
|
|
35
|
+
fail(code) {
|
|
36
|
+
this.onerror?.({ error: code, message: '' });
|
|
37
|
+
this.onend?.();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function install() {
|
|
41
|
+
window.webkitSpeechRecognition =
|
|
42
|
+
FakeRecognition;
|
|
43
|
+
}
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
delete window
|
|
46
|
+
.webkitSpeechRecognition;
|
|
47
|
+
FakeRecognition.last = null;
|
|
48
|
+
FakeRecognition.afterStart = null;
|
|
49
|
+
});
|
|
50
|
+
describe('BrowserSpeechSTTAdapter errors', () => {
|
|
51
|
+
it.each([
|
|
52
|
+
'network',
|
|
53
|
+
'not-allowed',
|
|
54
|
+
'service-not-allowed',
|
|
55
|
+
'no-speech',
|
|
56
|
+
'audio-capture',
|
|
57
|
+
'aborted',
|
|
58
|
+
'language-not-supported',
|
|
59
|
+
])('reports %s with its code', async (code) => {
|
|
60
|
+
install();
|
|
61
|
+
const adapter = new BrowserSpeechSTTAdapter();
|
|
62
|
+
const errors = [];
|
|
63
|
+
adapter.onError((error) => errors.push(error));
|
|
64
|
+
await adapter.start({ continuous: true });
|
|
65
|
+
FakeRecognition.last?.fail(code);
|
|
66
|
+
expect(errors).toHaveLength(1);
|
|
67
|
+
expect(errors[0].speechError).toBe(code);
|
|
68
|
+
});
|
|
69
|
+
it('does not report the abort it caused itself', async () => {
|
|
70
|
+
install();
|
|
71
|
+
const adapter = new BrowserSpeechSTTAdapter();
|
|
72
|
+
const onError = vi.fn();
|
|
73
|
+
adapter.onError(onError);
|
|
74
|
+
await adapter.start();
|
|
75
|
+
adapter.abort();
|
|
76
|
+
expect(onError).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('Dictation over the browser adapter', () => {
|
|
80
|
+
it("Brave: it beeps, then 'network' — shown as unavailable, not a silent stop", async () => {
|
|
81
|
+
install();
|
|
82
|
+
FakeRecognition.afterStart = (r) => queueMicrotask(() => r.fail('network'));
|
|
83
|
+
const log = vi.fn();
|
|
84
|
+
const beep = vi.fn(async () => true);
|
|
85
|
+
const dictation = new Dictation({
|
|
86
|
+
source: createSttDictationSource(),
|
|
87
|
+
onText: () => { },
|
|
88
|
+
beep,
|
|
89
|
+
log,
|
|
90
|
+
requestMicrophone: false,
|
|
91
|
+
});
|
|
92
|
+
await dictation.start();
|
|
93
|
+
await vi.waitFor(() => expect(dictation.state).toBe('error'));
|
|
94
|
+
expect(beep).toHaveBeenCalled();
|
|
95
|
+
expect(dictation.errorKind).toBe('unsupported');
|
|
96
|
+
expect(dictation.errorCode).toBe('network');
|
|
97
|
+
expect(log).toHaveBeenCalledWith(expect.objectContaining({ kind: 'unsupported', code: 'network' }));
|
|
98
|
+
});
|
|
99
|
+
it('an end straight after starting with no error is a failure too', async () => {
|
|
100
|
+
install();
|
|
101
|
+
FakeRecognition.afterStart = (r) => queueMicrotask(() => r.onend?.());
|
|
102
|
+
const dictation = new Dictation({
|
|
103
|
+
source: createSttDictationSource(),
|
|
104
|
+
onText: () => { },
|
|
105
|
+
beep: false,
|
|
106
|
+
log: () => { },
|
|
107
|
+
requestMicrophone: false,
|
|
108
|
+
});
|
|
109
|
+
await dictation.start();
|
|
110
|
+
await vi.waitFor(() => expect(dictation.state).toBe('error'));
|
|
111
|
+
expect(dictation.errorKind).toBe('interrupted');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-source.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/dictation-source.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,aAA0C,GAClD,MAAM,OAAO,CAAC,UAAU,CAAC,CAc3B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A speech source for smrt-ui's `Dictation` (the microphone button and
|
|
3
|
+
* "press and hold a field to speak" in `@happyvertical/smrt-ui/forms`).
|
|
4
|
+
*
|
|
5
|
+
* `STTAdapter` already has the shape `Dictation` needs (`start`, `stop`,
|
|
6
|
+
* `onResult`, `onError`, `onStart`, `onEnd`), so this only creates the
|
|
7
|
+
* adapter lazily, once, the first time someone starts dictating: nothing
|
|
8
|
+
* loads and no microphone prompt appears until then. Defaults to the
|
|
9
|
+
* browser's own speech recognition (`browser-speech`), which needs no
|
|
10
|
+
* download; an unsupported browser rejects with
|
|
11
|
+
* `CapabilityNotAvailableError`, which `Dictation` reports in plain words.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <script>
|
|
16
|
+
* import { createSttDictationSource } from '@happyvertical/smrt-svelte/browser-ai';
|
|
17
|
+
* import { Dictation, DictationButton } from '@happyvertical/smrt-ui/forms';
|
|
18
|
+
* const dictation = new Dictation({ source: createSttDictationSource(), onText: (t) => … });
|
|
19
|
+
* </script>
|
|
20
|
+
* <DictationButton {dictation} />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { getSTT } from './factory.js';
|
|
24
|
+
export function createSttDictationSource(options = { type: 'browser-speech' }) {
|
|
25
|
+
let adapter = null;
|
|
26
|
+
return () => {
|
|
27
|
+
adapter ??= (async () => {
|
|
28
|
+
const created = await getSTT(options);
|
|
29
|
+
await created.ensureInitialized();
|
|
30
|
+
return created;
|
|
31
|
+
})().catch((error) => {
|
|
32
|
+
// Let a later tap try again (for example after a permission change).
|
|
33
|
+
adapter = null;
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
36
|
+
return adapter;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createSttDictationSource: the browser speech adapter, created once and
|
|
3
|
+
* lazily, and rejected plainly where the browser has no speech recognition.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { createSttDictationSource } from './dictation-source.js';
|
|
7
|
+
class FakeRecognition {
|
|
8
|
+
lang = '';
|
|
9
|
+
continuous = false;
|
|
10
|
+
interimResults = false;
|
|
11
|
+
maxAlternatives = 1;
|
|
12
|
+
onresult = null;
|
|
13
|
+
onerror = null;
|
|
14
|
+
onstart = null;
|
|
15
|
+
onend = null;
|
|
16
|
+
start = vi.fn(() => this.onstart?.());
|
|
17
|
+
stop = vi.fn(() => this.onend?.());
|
|
18
|
+
abort = vi.fn();
|
|
19
|
+
}
|
|
20
|
+
describe('createSttDictationSource', () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
delete window.SpeechRecognition;
|
|
23
|
+
delete window
|
|
24
|
+
.webkitSpeechRecognition;
|
|
25
|
+
});
|
|
26
|
+
it('creates one browser speech adapter, on first use', async () => {
|
|
27
|
+
window.webkitSpeechRecognition =
|
|
28
|
+
FakeRecognition;
|
|
29
|
+
const provide = createSttDictationSource();
|
|
30
|
+
const first = await provide();
|
|
31
|
+
const second = await provide();
|
|
32
|
+
expect(first).toBe(second);
|
|
33
|
+
expect(first.type).toBe('browser-speech');
|
|
34
|
+
const onStart = vi.fn();
|
|
35
|
+
first.onStart(onStart);
|
|
36
|
+
await first.start({
|
|
37
|
+
language: 'en-CA',
|
|
38
|
+
continuous: true,
|
|
39
|
+
interimResults: true,
|
|
40
|
+
});
|
|
41
|
+
expect(onStart).toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
it('rejects with CapabilityNotAvailableError without speech recognition, then can retry', async () => {
|
|
44
|
+
const provide = createSttDictationSource();
|
|
45
|
+
await expect(provide()).rejects.toMatchObject({
|
|
46
|
+
name: 'CapabilityNotAvailableError',
|
|
47
|
+
});
|
|
48
|
+
window.SpeechRecognition =
|
|
49
|
+
FakeRecognition;
|
|
50
|
+
await expect(provide()).resolves.toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Speech-to-Text adapters
|
|
3
3
|
*/
|
|
4
|
-
export { BrowserSpeechSTTAdapter } from './browser-speech.js';
|
|
4
|
+
export { type BrowserSpeechError, BrowserSpeechSTTAdapter, } from './browser-speech.js';
|
|
5
|
+
export { createSttDictationSource } from './dictation-source.js';
|
|
5
6
|
export * from './factory.js';
|
|
6
7
|
export * from './types.js';
|
|
7
8
|
export { WhisperCppSTTAdapter } from './whisper-cpp.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,kBAAkB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Speech-to-Text adapters
|
|
3
3
|
*/
|
|
4
|
-
export { BrowserSpeechSTTAdapter } from './browser-speech.js';
|
|
4
|
+
export { BrowserSpeechSTTAdapter, } from './browser-speech.js';
|
|
5
|
+
export { createSttDictationSource } from './dictation-source.js';
|
|
5
6
|
export * from './factory.js';
|
|
6
7
|
export * from './types.js';
|
|
7
8
|
export { WhisperCppSTTAdapter } from './whisper-cpp.js';
|