@wix/himalaya-cli 0.1317.0 → 0.1319.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/sdk-types/src/capabilities.generated.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/theme/src/index.d.ts +21 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/webview-page/src/capabilities.generated.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/brand-color-math.d.ts +98 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +9 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core/brand-color-math.mjs +183 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/theme.mjs +189 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +8 -0
- package/dist/cli.mjs +475 -372
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/app-config.md +1 -1
- package/guides/importable-sdks.md +1 -0
- package/guides/sdk-reference.json +39 -2
- package/guides/web-page-sdk-reference.md +1 -0
- package/package.json +2 -2
- package/templates/beauty-salon-owner/config.ts +1 -1
- package/templates/beauty-salon-owner/dev/index.ts +125 -24
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/community-club-owner/config.ts +1 -1
- package/templates/community-club-owner/dev/index.ts +125 -24
- package/templates/community-club-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/events-meetups-owner/config.ts +1 -1
- package/templates/events-meetups-owner/dev/index.ts +125 -24
- package/templates/events-meetups-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/gym-fitness-owner/config.ts +1 -1
- package/templates/gym-fitness-owner/dev/index.ts +125 -24
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/nonprofit-cause-owner/config.ts +1 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +125 -24
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/owner-lite/config.ts +1 -1
- package/templates/owner-lite/dev/index.ts +125 -24
- package/templates/owner-lite/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/podcast-creator-owner/config.ts +1 -1
- package/templates/podcast-creator-owner/dev/index.ts +125 -24
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/portfolio-creative-owner/config.ts +1 -1
- package/templates/portfolio-creative-owner/dev/index.ts +125 -24
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/restaurant-owner/config.ts +1 -1
- package/templates/restaurant-owner/dev/index.ts +125 -24
- package/templates/restaurant-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/retail-store-owner/config.ts +1 -1
- package/templates/retail-store-owner/dev/index.ts +125 -24
- package/templates/retail-store-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
- package/templates/yoga-studio-owner/config.ts +1 -1
- package/templates/yoga-studio-owner/dev/index.ts +125 -24
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-attachments.ts +41 -2
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +59 -31
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +154 -13
|
Binary file
|
package/guides/app-config.md
CHANGED
|
@@ -115,7 +115,7 @@ with a native footprint only arrives with a new binary. `himi docs capabilities`
|
|
|
115
115
|
loop — discover, declare, validate, ship — and `himi capabilities <id>` shows the import and which
|
|
116
116
|
shells' binaries serve it.
|
|
117
117
|
|
|
118
|
-
App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `health`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`, `base44`, `animation`.
|
|
118
|
+
App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `theme`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `health`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`, `base44`, `animation`.
|
|
119
119
|
|
|
120
120
|
## What `himi validate` checks (7 passes)
|
|
121
121
|
|
|
@@ -56,6 +56,7 @@ package in one greppable file) or `GET /v1/sdk/<name>` over HTTP; in the CLI,
|
|
|
56
56
|
- `@himalaya/speech` — Tier-5 typed binding to the platform's speech capability.
|
|
57
57
|
- `@himalaya/system-intents` — Tier-5 typed binding to the platform's system-intents capability.
|
|
58
58
|
- `@himalaya/tasks` — Tier-5 typed binding for reminders / to-dos.
|
|
59
|
+
- `@himalaya/theme` — Tier-5 typed binding for the runtime brand overlay.
|
|
59
60
|
- `@himalaya/time` — Tier-5 temporal helpers.
|
|
60
61
|
- `@himalaya/ui` — Tier-5 typed binding to the platform's imperative popup capability.
|
|
61
62
|
- `@himalaya/webview` — Tier-5 typed binding for the page ↔ worker message channel of a worker-backed webview screen (Part 2 / RFC 0025 composition).
|
|
@@ -986,7 +986,7 @@
|
|
|
986
986
|
"name": "NavTabItem",
|
|
987
987
|
"kind": "interface",
|
|
988
988
|
"jsdoc": "/** A single bottom-nav tab. */",
|
|
989
|
-
"decl": "export interface NavTabItem {\n id: string;\n screenId?: string;\n label?: string;\n icon?: IconRef; // design-system icon name\n selectedIcon?: IconRef; // optional filled/active variant\n imageUrl?: string; // optional custom asset (overrides icon)\n imageAsset?: string;\n badge?: NavBadge;\n /** Live bindings resolved\n * against `NavigationConfig.navLogicBundle`. Each is a `$service:` sentinel,\n * the SAME representation `NavBadge.valuePath` and `hiddenPath`\n * already use — not a new binding vehicle. Unset / unresolvable / no nav\n * worker ⇒ the sibling literal wins, so chrome degrades to static config\n * rather than to blank.\n *\n * `hiddenPath` FILTERS the tab out (zero width, not dimmed) while KEEPING its\n * router stack, so unhiding restores it intact. Truthiness follows the RFC\n * 0024 coercers exactly, so a value hides identically on every surface. */\n hiddenPath?: string;\n labelPath?: string;\n iconPath?: string;\n}"
|
|
989
|
+
"decl": "export interface NavTabItem {\n id: string;\n screenId?: string;\n label?: string;\n icon?: IconRef; // design-system icon name\n selectedIcon?: IconRef; // optional filled/active variant\n imageUrl?: string; // optional custom asset (overrides icon)\n imageAsset?: string;\n badge?: NavBadge;\n /** Live bindings resolved\n * against `NavigationConfig.navLogicBundle`. Each is a `$service:` sentinel,\n * the SAME representation `NavBadge.valuePath` and `hiddenPath`\n * already use — not a new binding vehicle. Unset / unresolvable / no nav\n * worker ⇒ the sibling literal wins, so chrome degrades to static config\n * rather than to blank.\n *\n * `hiddenPath` FILTERS the tab out (zero width, not dimmed) while KEEPING its\n * router stack, so unhiding restores it intact. Truthiness follows the RFC\n * 0024 coercers exactly, so a value hides identically on every surface. */\n hiddenPath?: string;\n labelPath?: string;\n iconPath?: string;\n /** With `hiddenPath`, hidden until the path resolves (fail-closed). */\n hiddenUntilResolved?: boolean;\n}"
|
|
990
990
|
},
|
|
991
991
|
{
|
|
992
992
|
"name": "DrawerConfig",
|
|
@@ -998,7 +998,7 @@
|
|
|
998
998
|
"name": "DrawerItem",
|
|
999
999
|
"kind": "interface",
|
|
1000
1000
|
"jsdoc": "/** A single drawer destination (mirrors NavTabItem). */",
|
|
1001
|
-
"decl": "export interface DrawerItem {\n id: string;\n screenId?: string;\n label?: string;\n icon?: IconRef; // design-system icon name\n badge?: NavBadge;\n /** Non-navigable decorative rows: \"section\" → a muted section header (e.g.\n * \"Workspaces\"); \"note\" → a muted body line (e.g. \"No workspaces yet\"). */\n role?: \"section\" | \"note\";\n /** Only meaningful on a `role:\"section\"` header. When set, that header becomes a\n * COLLAPSIBLE FOLDER: the nav rows following it (until the next section header)\n * hide/show when the header is tapped, and `collapsed` is the initial state\n * (`true` → folded away by default). Absent → the header is a static,\n * non-tappable label (back-compat: existing section headers are unaffected). */\n collapsed?: boolean;\n /** Live bindings against `NavigationConfig.navLogicBundle` — see `NavTabItem`.\n * A hidden row is filtered out, taking its section-folder membership with it. */\n hiddenPath?: string;\n labelPath?: string;\n iconPath?: string;\n}"
|
|
1001
|
+
"decl": "export interface DrawerItem {\n id: string;\n screenId?: string;\n label?: string;\n icon?: IconRef; // design-system icon name\n badge?: NavBadge;\n /** Non-navigable decorative rows: \"section\" → a muted section header (e.g.\n * \"Workspaces\"); \"note\" → a muted body line (e.g. \"No workspaces yet\"). */\n role?: \"section\" | \"note\";\n /** Only meaningful on a `role:\"section\"` header. When set, that header becomes a\n * COLLAPSIBLE FOLDER: the nav rows following it (until the next section header)\n * hide/show when the header is tapped, and `collapsed` is the initial state\n * (`true` → folded away by default). Absent → the header is a static,\n * non-tappable label (back-compat: existing section headers are unaffected). */\n collapsed?: boolean;\n /** Live bindings against `NavigationConfig.navLogicBundle` — see `NavTabItem`.\n * A hidden row is filtered out, taking its section-folder membership with it. */\n hiddenPath?: string;\n labelPath?: string;\n iconPath?: string;\n /** With `hiddenPath`, hidden until the path resolves (fail-closed). */\n hiddenUntilResolved?: boolean;\n}"
|
|
1002
1002
|
},
|
|
1003
1003
|
{
|
|
1004
1004
|
"name": "SidebarConfig",
|
|
@@ -3886,6 +3886,43 @@
|
|
|
3886
3886
|
}
|
|
3887
3887
|
]
|
|
3888
3888
|
},
|
|
3889
|
+
{
|
|
3890
|
+
"name": "@himalaya/theme",
|
|
3891
|
+
"source": "core/sdk/theme/src/index.ts",
|
|
3892
|
+
"description": "Tier-5 typed binding for the runtime brand overlay.",
|
|
3893
|
+
"api": [
|
|
3894
|
+
{
|
|
3895
|
+
"name": "RuntimeBrand",
|
|
3896
|
+
"kind": "interface",
|
|
3897
|
+
"jsdoc": "",
|
|
3898
|
+
"decl": "export interface RuntimeBrand {\n /** #RRGGBB */\n primaryColor: string;\n /** #RRGGBB; absent ⇒ derived, as at build time. */\n secondaryColor?: string;\n}"
|
|
3899
|
+
},
|
|
3900
|
+
{
|
|
3901
|
+
"name": "ApplyBrandResult",
|
|
3902
|
+
"kind": "interface",
|
|
3903
|
+
"jsdoc": "",
|
|
3904
|
+
"decl": "export interface ApplyBrandResult {\n applied: boolean;\n /** Why it was not applied: `invalid_color`, `stale_scope`, a native rejection reason. */\n reason?: string;\n}"
|
|
3905
|
+
},
|
|
3906
|
+
{
|
|
3907
|
+
"name": "applyBrand",
|
|
3908
|
+
"kind": "function",
|
|
3909
|
+
"jsdoc": "/**\n * Brand the shell with [brand]: the five accent tokens on every scheme, and the accent glow when\n * the app's tokens have one. Idempotent; the last call wins. Rejects nothing by throwing: a bad\n * colour or a refused write answers `{ applied: false, reason }`.\n */",
|
|
3910
|
+
"decl": "export async function applyBrand(brand: RuntimeBrand): Promise<ApplyBrandResult>;"
|
|
3911
|
+
},
|
|
3912
|
+
{
|
|
3913
|
+
"name": "clearBrand",
|
|
3914
|
+
"kind": "function",
|
|
3915
|
+
"jsdoc": "/** Drop the runtime brand: the app's registered tokens paint again. */",
|
|
3916
|
+
"decl": "export async function clearBrand(): Promise<void>;"
|
|
3917
|
+
},
|
|
3918
|
+
{
|
|
3919
|
+
"name": "version",
|
|
3920
|
+
"kind": "const",
|
|
3921
|
+
"jsdoc": "/** Capability version, for bundles that gate on min binary version. */",
|
|
3922
|
+
"decl": "export const version = 1;"
|
|
3923
|
+
}
|
|
3924
|
+
]
|
|
3925
|
+
},
|
|
3889
3926
|
{
|
|
3890
3927
|
"name": "@himalaya/time",
|
|
3891
3928
|
"source": "core/sdk/time/src/index.ts",
|
|
@@ -50,6 +50,7 @@ the SDK sources, so it matches the shipped SDKs.
|
|
|
50
50
|
- [`speech`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fspeech?format=md) — Speech capability. 8 methods.
|
|
51
51
|
- [`system_intents`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fsystem_intents?format=md) — System-intents capability. 2 methods · not in Brandy.
|
|
52
52
|
- [`tasks`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Ftasks?format=md) — Reminders / to-dos. 7 methods.
|
|
53
|
+
- [`theme`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Ftheme?format=md) — Runtime brand overlay. 2 methods.
|
|
53
54
|
- [`ui`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fui?format=md) — Imperative popup capability. 2 methods · not in Brandy.
|
|
54
55
|
- [`wix_cashier`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fwix_cashier?format=md) — Native cashier. 2 methods.
|
|
55
56
|
- [`wix_platform`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fwix_platform?format=md) — Wix Platform SDK, worker-facing bindings. 30 methods · not in Brandy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1319.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "60f160d9f74860a80bb7b1bedb582949eda4352aaf8909967561bac5"
|
|
75
75
|
}
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","presigned_upload","app_info","biometrics"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","presigned_upload","app_info","biometrics","wix_platform"],
|
|
41
41
|
designTokensPath: "./tokens.json",
|
|
42
42
|
navigation: {
|
|
43
43
|
"navKind": "responsive",
|
|
@@ -61983,6 +61983,65 @@ const SCREENS = runtimeSetting({
|
|
|
61983
61983
|
},
|
|
61984
61984
|
{
|
|
61985
61985
|
"id": "",
|
|
61986
|
+
"body": {
|
|
61987
|
+
"package": "himalaya",
|
|
61988
|
+
"type": "VStack",
|
|
61989
|
+
"props": {
|
|
61990
|
+
"spacing": 4,
|
|
61991
|
+
"children": [
|
|
61992
|
+
{
|
|
61993
|
+
"id": "",
|
|
61994
|
+
"body": {
|
|
61995
|
+
"package": "himalaya",
|
|
61996
|
+
"type": "Icon",
|
|
61997
|
+
"props": {
|
|
61998
|
+
"name": "doc",
|
|
61999
|
+
"size": 24,
|
|
62000
|
+
"tint": "textSecondary"
|
|
62001
|
+
}
|
|
62002
|
+
},
|
|
62003
|
+
"modifiers": {
|
|
62004
|
+
"accessibilityRole": "decorative"
|
|
62005
|
+
}
|
|
62006
|
+
},
|
|
62007
|
+
{
|
|
62008
|
+
"id": "",
|
|
62009
|
+
"body": {
|
|
62010
|
+
"package": "himalaya",
|
|
62011
|
+
"type": "Text",
|
|
62012
|
+
"props": {
|
|
62013
|
+
"text": "$item.name",
|
|
62014
|
+
"style": "caption",
|
|
62015
|
+
"color": "textPrimary",
|
|
62016
|
+
"maxLines": 2
|
|
62017
|
+
}
|
|
62018
|
+
},
|
|
62019
|
+
"modifiers": {
|
|
62020
|
+
"height": 44
|
|
62021
|
+
}
|
|
62022
|
+
},
|
|
62023
|
+
{
|
|
62024
|
+
"id": "",
|
|
62025
|
+
"body": {
|
|
62026
|
+
"package": "himalaya",
|
|
62027
|
+
"type": "Text",
|
|
62028
|
+
"props": {
|
|
62029
|
+
"text": "$item.typeLabel",
|
|
62030
|
+
"style": "caption",
|
|
62031
|
+
"color": "textSecondary",
|
|
62032
|
+
"maxLines": 1
|
|
62033
|
+
}
|
|
62034
|
+
}
|
|
62035
|
+
}
|
|
62036
|
+
]
|
|
62037
|
+
}
|
|
62038
|
+
},
|
|
62039
|
+
"modifiers": {
|
|
62040
|
+
"hiddenPath": "$item.documentHidden"
|
|
62041
|
+
}
|
|
62042
|
+
},
|
|
62043
|
+
{
|
|
62044
|
+
"id": "himi_owner_lite_aria.attachment_status_$item.id",
|
|
61986
62045
|
"body": {
|
|
61987
62046
|
"package": "himalaya",
|
|
61988
62047
|
"type": "Text",
|
|
@@ -61994,6 +62053,7 @@ const SCREENS = runtimeSetting({
|
|
|
61994
62053
|
}
|
|
61995
62054
|
},
|
|
61996
62055
|
"modifiers": {
|
|
62056
|
+
"height": 44,
|
|
61997
62057
|
"hiddenPath": "$item.statusHidden"
|
|
61998
62058
|
}
|
|
61999
62059
|
},
|
|
@@ -62009,21 +62069,22 @@ const SCREENS = runtimeSetting({
|
|
|
62009
62069
|
"id": "himi_owner_lite_aria.attachment_retry_$item.id",
|
|
62010
62070
|
"body": {
|
|
62011
62071
|
"package": "himalaya",
|
|
62012
|
-
"type": "
|
|
62072
|
+
"type": "Button",
|
|
62013
62073
|
"props": {
|
|
62014
|
-
"
|
|
62015
|
-
"
|
|
62016
|
-
|
|
62017
|
-
|
|
62074
|
+
"title": "Retry",
|
|
62075
|
+
"action": {
|
|
62076
|
+
"actionId": "tier5.himiOwnerLiteAria.retryAttachment",
|
|
62077
|
+
"params": {
|
|
62078
|
+
"id": "$item.id"
|
|
62079
|
+
}
|
|
62080
|
+
},
|
|
62081
|
+
"style": "tertiary",
|
|
62082
|
+
"size": "sm"
|
|
62018
62083
|
}
|
|
62019
62084
|
},
|
|
62020
62085
|
"modifiers": {
|
|
62021
|
-
"
|
|
62022
|
-
|
|
62023
|
-
"params": {
|
|
62024
|
-
"id": "$item.id"
|
|
62025
|
-
}
|
|
62026
|
-
},
|
|
62086
|
+
"width": 92,
|
|
62087
|
+
"height": 48,
|
|
62027
62088
|
"hiddenPath": "$item.retryHidden"
|
|
62028
62089
|
}
|
|
62029
62090
|
},
|
|
@@ -62031,32 +62092,51 @@ const SCREENS = runtimeSetting({
|
|
|
62031
62092
|
"id": "himi_owner_lite_aria.attachment_remove_$item.id",
|
|
62032
62093
|
"body": {
|
|
62033
62094
|
"package": "himalaya",
|
|
62034
|
-
"type": "
|
|
62095
|
+
"type": "Button",
|
|
62035
62096
|
"props": {
|
|
62036
|
-
"
|
|
62037
|
-
"
|
|
62038
|
-
|
|
62097
|
+
"title": "Remove attachment",
|
|
62098
|
+
"action": {
|
|
62099
|
+
"actionId": "tier5.himiOwnerLiteAria.removeAttachment",
|
|
62100
|
+
"params": {
|
|
62101
|
+
"id": "$item.id"
|
|
62102
|
+
}
|
|
62103
|
+
},
|
|
62104
|
+
"style": "tertiary",
|
|
62105
|
+
"iconOnly": true,
|
|
62106
|
+
"leadingIcon": "close",
|
|
62107
|
+
"cornerRadius": 24,
|
|
62108
|
+
"minHeight": 48
|
|
62039
62109
|
}
|
|
62040
62110
|
},
|
|
62041
62111
|
"modifiers": {
|
|
62042
|
-
"
|
|
62043
|
-
|
|
62044
|
-
|
|
62045
|
-
|
|
62046
|
-
}
|
|
62047
|
-
},
|
|
62048
|
-
"accessibilityLabel": "Remove photo"
|
|
62112
|
+
"width": 48,
|
|
62113
|
+
"height": 48,
|
|
62114
|
+
"accessibilityLabel": "Remove attachment",
|
|
62115
|
+
"accessibilityRole": "button"
|
|
62049
62116
|
}
|
|
62050
62117
|
}
|
|
62051
62118
|
]
|
|
62052
62119
|
}
|
|
62120
|
+
},
|
|
62121
|
+
"modifiers": {
|
|
62122
|
+
"height": 48
|
|
62053
62123
|
}
|
|
62054
62124
|
}
|
|
62055
62125
|
]
|
|
62056
62126
|
}
|
|
62057
62127
|
},
|
|
62058
62128
|
"modifiers": {
|
|
62059
|
-
"width":
|
|
62129
|
+
"width": 160,
|
|
62130
|
+
"padding": {
|
|
62131
|
+
"top": 8,
|
|
62132
|
+
"bottom": 8,
|
|
62133
|
+
"leading": 8,
|
|
62134
|
+
"trailing": 8
|
|
62135
|
+
},
|
|
62136
|
+
"background": {
|
|
62137
|
+
"token": "surfaceSecondary"
|
|
62138
|
+
},
|
|
62139
|
+
"cornerRadius": 12
|
|
62060
62140
|
}
|
|
62061
62141
|
}
|
|
62062
62142
|
]
|
|
@@ -62068,7 +62148,7 @@ const SCREENS = runtimeSetting({
|
|
|
62068
62148
|
"leading": 0,
|
|
62069
62149
|
"trailing": 0,
|
|
62070
62150
|
"top": 8,
|
|
62071
|
-
"bottom":
|
|
62151
|
+
"bottom": 8
|
|
62072
62152
|
}
|
|
62073
62153
|
}
|
|
62074
62154
|
},
|
|
@@ -62121,6 +62201,27 @@ const SCREENS = runtimeSetting({
|
|
|
62121
62201
|
},
|
|
62122
62202
|
"accessibilityLabel": "Take a photo"
|
|
62123
62203
|
}
|
|
62204
|
+
},
|
|
62205
|
+
{
|
|
62206
|
+
"id": "himi_owner_lite_aria.attach.document",
|
|
62207
|
+
"body": {
|
|
62208
|
+
"package": "himalaya",
|
|
62209
|
+
"type": "Icon",
|
|
62210
|
+
"props": {
|
|
62211
|
+
"name": "document",
|
|
62212
|
+
"size": 22,
|
|
62213
|
+
"tint": "accentPrimary"
|
|
62214
|
+
}
|
|
62215
|
+
},
|
|
62216
|
+
"modifiers": {
|
|
62217
|
+
"onTap": {
|
|
62218
|
+
"actionId": "tier5.himiOwnerLiteAria.attach",
|
|
62219
|
+
"params": {
|
|
62220
|
+
"source": "document"
|
|
62221
|
+
}
|
|
62222
|
+
},
|
|
62223
|
+
"accessibilityLabel": "Add files"
|
|
62224
|
+
}
|
|
62124
62225
|
}
|
|
62125
62226
|
]
|
|
62126
62227
|
}
|
|
@@ -27,6 +27,14 @@ export const ARIA_IMAGE_TYPES: readonly string[] = [
|
|
|
27
27
|
"image/heic",
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
/** Document MIME types supported by the native Aria reference. */
|
|
31
|
+
export const ARIA_DOCUMENT_TYPES: readonly string[] = [
|
|
32
|
+
"text/csv", "text/plain", "application/pdf", "application/msword",
|
|
33
|
+
"application/vnd.ms-excel", "application/vnd.ms-powerpoint",
|
|
34
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
35
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
36
|
+
];
|
|
37
|
+
|
|
30
38
|
/** At most this many attachments ride one message. The native Aria app caps at
|
|
31
39
|
* 10; nothing on the wire says otherwise. */
|
|
32
40
|
export const ARIA_MAX_ATTACHMENTS = 10;
|
|
@@ -40,6 +48,9 @@ export const ARIA_MAX_ATTACHMENTS = 10;
|
|
|
40
48
|
* Checking it HERE is what turns that refusal into a sentence the owner can act
|
|
41
49
|
* on, instead of a chip that fails for no stated reason. */
|
|
42
50
|
export const ARIA_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
51
|
+
// Inline documents include base64 and JSON overhead under the 4 MiB frame bridge.
|
|
52
|
+
// Larger documents use the file-backed attachment follow-up.
|
|
53
|
+
export const ARIA_MAX_DOCUMENT_BYTES = 2 * 1024 * 1024;
|
|
43
54
|
|
|
44
55
|
/** A chip in the composer. Every field is always present — a `$item.` path that
|
|
45
56
|
* is missing on some rows does not substitute — and each `…Hidden` flag
|
|
@@ -58,6 +69,9 @@ export interface AriaAttachmentChip {
|
|
|
58
69
|
* runtime's frame limit. */
|
|
59
70
|
thumbUrl: string;
|
|
60
71
|
thumbHidden: boolean;
|
|
72
|
+
/** Filename/type card for documents (images keep the 72pt preview). */
|
|
73
|
+
documentHidden: boolean;
|
|
74
|
+
typeLabel: string;
|
|
61
75
|
/** "Uploading…" / "Couldn't upload" / "" — collapses when empty. */
|
|
62
76
|
statusText: string;
|
|
63
77
|
statusHidden: boolean;
|
|
@@ -113,6 +127,21 @@ export function normalizeMime(mime: string): string {
|
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
|
|
130
|
+
/** Native providers sometimes report a generic MIME; match the reference's
|
|
131
|
+
* extension fallback only for those values, not a conflicting explicit type. */
|
|
132
|
+
export function attachmentMime(mime: string, name = ""): string {
|
|
133
|
+
const normalized = normalizeMime(mime);
|
|
134
|
+
if (normalized === "text/comma-separated-values") return "text/csv";
|
|
135
|
+
if (normalized && normalized !== "application/octet-stream") return normalized;
|
|
136
|
+
const extension = name.split(".").pop()?.toLowerCase() ?? "";
|
|
137
|
+
const types: Record<string, string> = {
|
|
138
|
+
png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", webp: "image/webp", gif: "image/gif", heic: "image/heic", heif: "image/heif",
|
|
139
|
+
csv: "text/csv", txt: "text/plain", pdf: "application/pdf", doc: "application/msword", xls: "application/vnd.ms-excel", ppt: "application/vnd.ms-powerpoint",
|
|
140
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
141
|
+
};
|
|
142
|
+
return types[extension] ?? normalized;
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
/** A file name for an image the picker did not name, by type. The service
|
|
117
146
|
* requires one (1–255 characters), and it is what the transcript shows. */
|
|
118
147
|
export function imageFileName(mimeType: string, n: number, given?: string): string {
|
|
@@ -131,8 +160,8 @@ export type AttachRefusal = "" | "type" | "size" | "count";
|
|
|
131
160
|
|
|
132
161
|
export function refusalFor(parsed: ParsedDataUri | null, mimeType: string, attached: number): AttachRefusal {
|
|
133
162
|
if (attached >= ARIA_MAX_ATTACHMENTS) return "count";
|
|
134
|
-
if (!parsed || !ARIA_IMAGE_TYPES.includes(mimeType)) return "type";
|
|
135
|
-
if (parsed.bytes > ARIA_MAX_IMAGE_BYTES) return "size";
|
|
163
|
+
if (!parsed || ![...ARIA_IMAGE_TYPES, ...ARIA_DOCUMENT_TYPES].includes(mimeType)) return "type";
|
|
164
|
+
if (parsed.bytes > (mimeType.startsWith("image/") ? ARIA_MAX_IMAGE_BYTES : ARIA_MAX_DOCUMENT_BYTES)) return "size";
|
|
136
165
|
return "";
|
|
137
166
|
}
|
|
138
167
|
|
|
@@ -146,10 +175,20 @@ export function chipIn(
|
|
|
146
175
|
ref = "",
|
|
147
176
|
): AriaAttachmentChip {
|
|
148
177
|
const statusText = state === "uploading" ? labels.uploading : state === "failed" ? labels.failed : "";
|
|
178
|
+
const dot = base.name.lastIndexOf(".");
|
|
179
|
+
const extension = dot > 0 && dot < base.name.length - 1 ? base.name.slice(dot + 1).toUpperCase() : "";
|
|
180
|
+
const mimeLabels: Record<string, string> = {
|
|
181
|
+
"application/pdf": "PDF", "text/csv": "CSV", "text/plain": "TXT", "application/msword": "DOC",
|
|
182
|
+
"application/vnd.ms-excel": "XLS", "application/vnd.ms-powerpoint": "PPT",
|
|
183
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
|
|
184
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
|
|
185
|
+
};
|
|
149
186
|
return {
|
|
150
187
|
...base,
|
|
151
188
|
state,
|
|
152
189
|
thumbHidden: !base.thumbUrl,
|
|
190
|
+
documentHidden: base.mimeType.startsWith("image/"),
|
|
191
|
+
typeLabel: extension || mimeLabels[base.mimeType] || base.mimeType,
|
|
153
192
|
statusText,
|
|
154
193
|
statusHidden: !statusText,
|
|
155
194
|
retryHidden: state !== "failed",
|
|
@@ -61,10 +61,11 @@ import {
|
|
|
61
61
|
import {
|
|
62
62
|
ARIA_MAX_ATTACHMENTS,
|
|
63
63
|
ARIA_MAX_IMAGE_BYTES,
|
|
64
|
+
ARIA_MAX_DOCUMENT_BYTES,
|
|
64
65
|
attachmentLine,
|
|
65
66
|
chipIn,
|
|
66
67
|
imageFileName,
|
|
67
|
-
|
|
68
|
+
attachmentMime,
|
|
68
69
|
parseDataUri,
|
|
69
70
|
refusalFor,
|
|
70
71
|
sendBlocker,
|
|
@@ -480,6 +481,9 @@ export interface AriaChatLabels {
|
|
|
480
481
|
/** Send refused while a photo is still uploading, or failed to. */
|
|
481
482
|
attachWaitUploading?: Resolvable<string>;
|
|
482
483
|
attachWaitFailed?: Resolvable<string>;
|
|
484
|
+
/** Send refused by a failed file that cannot be retried (a cold-start chip
|
|
485
|
+
* whose source did not survive): Remove is the only control it offers. */
|
|
486
|
+
attachWaitRemove?: Resolvable<string>;
|
|
483
487
|
/** Send refused with photos and no words. The service requires message text
|
|
484
488
|
* on every turn, so a photo alone is not a sendable message. */
|
|
485
489
|
attachNeedsText?: Resolvable<string>;
|
|
@@ -535,7 +539,7 @@ export interface AriaDictationOptions {
|
|
|
535
539
|
export interface AriaAttachmentPorts {
|
|
536
540
|
/** Let the owner pick (or take) one photo. Resolves the image as a `data:`
|
|
537
541
|
* URI, or null when they cancelled. */
|
|
538
|
-
pick(source: "library" | "camera"): Promise<{ dataUri: string; name?: string; mimeType?: string } | null>;
|
|
542
|
+
pick(source: "library" | "camera" | "document"): Promise<{ dataUri: string; name?: string; mimeType?: string } | null>;
|
|
539
543
|
/** PUT `base64` to a pre-signed url with `contentType`, carrying NO user
|
|
540
544
|
* credentials — the url authorizes itself. */
|
|
541
545
|
put(url: string, base64: string, contentType: string): Promise<{ ok: boolean; status: number }>;
|
|
@@ -1652,23 +1656,28 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1652
1656
|
const attachUploadingText = () => res(o.labels?.attachUploading ?? "Uploading…");
|
|
1653
1657
|
const attachFailedText = () => res(o.labels?.attachFailed ?? "Couldn't upload");
|
|
1654
1658
|
const chipLabels = () => ({ uploading: attachUploadingText(), failed: attachFailedText() });
|
|
1655
|
-
const refusalText = (why: "type" | "size" | "count"): string =>
|
|
1659
|
+
const refusalText = (why: "type" | "size" | "count", maxBytes = ARIA_MAX_IMAGE_BYTES): string =>
|
|
1656
1660
|
why === "count"
|
|
1657
|
-
? o.labels?.attachTooMany?.(ARIA_MAX_ATTACHMENTS) ?? `You can attach up to ${ARIA_MAX_ATTACHMENTS}
|
|
1661
|
+
? o.labels?.attachTooMany?.(ARIA_MAX_ATTACHMENTS) ?? `You can attach up to ${ARIA_MAX_ATTACHMENTS} files.`
|
|
1658
1662
|
: why === "size"
|
|
1659
|
-
? o.labels?.attachTooLarge?.(
|
|
1660
|
-
?? `That
|
|
1663
|
+
? o.labels?.attachTooLarge?.(maxBytes / (1024 * 1024))
|
|
1664
|
+
?? `That file is too large to attach (up to ${maxBytes / (1024 * 1024)} MB).`
|
|
1661
1665
|
: res(o.labels?.attachUnsupported ?? "That file type can't be attached.");
|
|
1662
|
-
const waitUploadingText = () => res(o.labels?.attachWaitUploading ?? "Wait for your
|
|
1663
|
-
const waitFailedText = () => res(o.labels?.attachWaitFailed ?? "Retry or remove the
|
|
1664
|
-
const
|
|
1666
|
+
const waitUploadingText = () => res(o.labels?.attachWaitUploading ?? "Wait for your file to finish uploading.");
|
|
1667
|
+
const waitFailedText = () => res(o.labels?.attachWaitFailed ?? "Retry or remove the file that didn't upload.");
|
|
1668
|
+
const waitRemoveText = () => res(o.labels?.attachWaitRemove ?? "Remove the file that didn't upload, then add it again.");
|
|
1669
|
+
/** The failed-file notice must name only a control the chips offer: "Retry"
|
|
1670
|
+
* while any failed chip can still retry, otherwise "Remove". */
|
|
1671
|
+
const failedNotice = (): string =>
|
|
1672
|
+
chips.some((c) => c.state === "failed" && !c.retryHidden) ? waitFailedText() : waitRemoveText();
|
|
1673
|
+
const needsTextText = () => res(o.labels?.attachNeedsText ?? "Add a message to send with your file.");
|
|
1665
1674
|
/** Clear the status line if what it says is an attachment notice, and only
|
|
1666
1675
|
* then. Every notice is advice about the composer's photos, so once the owner
|
|
1667
1676
|
* acts on them it is stale — but the same line also carries "Aria is
|
|
1668
1677
|
* typing…", which a photo edit must never erase. */
|
|
1669
1678
|
const withoutNotice = <T extends { statusText: string }>(s: T): T => {
|
|
1670
|
-
const notices = [waitUploadingText(), waitFailedText(), needsTextText(),
|
|
1671
|
-
refusalText("type"), refusalText("size"), refusalText("count")];
|
|
1679
|
+
const notices = [waitUploadingText(), waitFailedText(), waitRemoveText(), needsTextText(),
|
|
1680
|
+
refusalText("type"), refusalText("size"), refusalText("size", ARIA_MAX_DOCUMENT_BYTES), refusalText("count")];
|
|
1672
1681
|
return s.statusText && notices.includes(s.statusText) ? { ...s, statusText: "" } : s;
|
|
1673
1682
|
};
|
|
1674
1683
|
const toolFailedText = () => res(o.labels?.toolFailed ?? `${assistant()} couldn't finish that. Try again.`);
|
|
@@ -2023,16 +2032,20 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2023
2032
|
chipsSeeded = true;
|
|
2024
2033
|
const persisted = Array.isArray(s.attachments) ? s.attachments : [];
|
|
2025
2034
|
// An upload does not survive the process that ran it, so a chip persisted
|
|
2026
|
-
// mid-upload would say "Uploading…" forever.
|
|
2027
|
-
//
|
|
2035
|
+
// mid-upload would say "Uploading…" forever. Restore it as failed; only a
|
|
2036
|
+
// chip with retained inline bytes can offer Retry until the file-backed follow-up.
|
|
2028
2037
|
chips = persisted
|
|
2029
2038
|
.filter((c) => c && typeof c.id === "string")
|
|
2030
|
-
.map((c) =>
|
|
2039
|
+
.map((c) => {
|
|
2040
|
+
const chip = chipIn(c, c.state === "uploading" ? "failed" : c.state, chipLabels(), c.ref);
|
|
2041
|
+
// Inline bytes were worker-owned. A cold-start document or large image
|
|
2042
|
+
// has no source left: keep Remove available, never offer a dead Retry.
|
|
2043
|
+
return chip.state === "failed" && !chip.thumbUrl ? { ...chip, retryHidden: true } : chip;
|
|
2044
|
+
});
|
|
2031
2045
|
for (const c of chips) {
|
|
2032
2046
|
const n = Number(/^att-(\d+)$/.exec(c.id)?.[1] ?? 0);
|
|
2033
2047
|
if (n > chipSeq) chipSeq = n;
|
|
2034
|
-
// A rehydrated chip
|
|
2035
|
-
// none, so its retry fails honestly rather than sending nothing.
|
|
2048
|
+
// A rehydrated chip can retry only the source it kept inline.
|
|
2036
2049
|
if (c.thumbUrl && !chipBytes.has(c.id)) chipBytes.set(c.id, c.thumbUrl);
|
|
2037
2050
|
}
|
|
2038
2051
|
};
|
|
@@ -2044,21 +2057,21 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2044
2057
|
if (!chips.length) return null;
|
|
2045
2058
|
const blocker = sendBlocker(chips);
|
|
2046
2059
|
if (blocker === "uploading") return { ...s, statusText: waitUploadingText(), error: "" };
|
|
2047
|
-
if (blocker === "failed") return { ...s, statusText:
|
|
2060
|
+
if (blocker === "failed") return { ...s, statusText: failedNotice(), error: "" };
|
|
2048
2061
|
if (!content) return { ...s, statusText: needsTextText(), error: "" };
|
|
2049
2062
|
return null;
|
|
2050
2063
|
};
|
|
2051
2064
|
|
|
2052
2065
|
/** Keep a "wait for your photo" notice true to the chips as they are NOW. An
|
|
2053
|
-
* upload that FAILED while Send was held on "Wait for your
|
|
2066
|
+
* upload that FAILED while Send was held on "Wait for your file to finish
|
|
2054
2067
|
* uploading" must say to retry or remove it instead, and one that landed,
|
|
2055
2068
|
* with nothing else pending, clears it. Any other status line (the typing
|
|
2056
2069
|
* line above all) is left alone. */
|
|
2057
2070
|
const refreshWaitNotice = (s: AriaChatState): AriaChatState => {
|
|
2058
|
-
const waiting = [waitUploadingText(), waitFailedText()];
|
|
2071
|
+
const waiting = [waitUploadingText(), waitFailedText(), waitRemoveText()];
|
|
2059
2072
|
if (!s.statusText || !waiting.includes(s.statusText)) return s;
|
|
2060
2073
|
const blocker = sendBlocker(chips);
|
|
2061
|
-
const next = blocker === "uploading" ? waitUploadingText() : blocker === "failed" ?
|
|
2074
|
+
const next = blocker === "uploading" ? waitUploadingText() : blocker === "failed" ? failedNotice() : "";
|
|
2062
2075
|
return next === s.statusText ? s : { ...s, statusText: next };
|
|
2063
2076
|
};
|
|
2064
2077
|
|
|
@@ -3794,8 +3807,8 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
3794
3807
|
const atCategories = { ...m, open: true, provider: null, query: "", items: [], itemsLoading: false, itemsFailed: "" };
|
|
3795
3808
|
// Listed once per business. A failed list is asked again: the failure
|
|
3796
3809
|
// may have been the network, and the next open is the retry.
|
|
3797
|
-
if (m.providers !== null) { setMention(atCategories); return s; }
|
|
3798
|
-
if (m.providersLoading) { setMention(atCategories); return s; }
|
|
3810
|
+
if (m.providers !== null) { setMention(atCategories); return committed ?? s; }
|
|
3811
|
+
if (m.providersLoading) { setMention(atCategories); return committed ?? s; }
|
|
3799
3812
|
const site = m.site;
|
|
3800
3813
|
setMention({ ...atCategories, providersLoading: true, providersFailed: "" });
|
|
3801
3814
|
let providers: MentionProvider[] | null = null;
|
|
@@ -4112,7 +4125,8 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4112
4125
|
// picker whose result will be thrown away wastes the owner's time.
|
|
4113
4126
|
if (chips.length >= ARIA_MAX_ATTACHMENTS) return { ...s, statusText: refusalText("count"), error: "" };
|
|
4114
4127
|
const raw = typeof arg === "string" ? arg : firstString(arg?.source, arg?.value);
|
|
4115
|
-
const source = raw.trim().toLowerCase() === "
|
|
4128
|
+
const source = raw.trim().toLowerCase() === "document" ? "document"
|
|
4129
|
+
: raw.trim().toLowerCase() === "camera" ? "camera" : "library";
|
|
4116
4130
|
// The picker is open for as long as the owner takes, and a poll, a reply or
|
|
4117
4131
|
// another action can commit a NEWER state meanwhile. Returning `{ ...s }`
|
|
4118
4132
|
// afterwards would put this pre-picker snapshot back over it: an older
|
|
@@ -4124,17 +4138,18 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4124
4138
|
let picked: Awaited<ReturnType<AriaAttachmentPorts["pick"]>>;
|
|
4125
4139
|
try {
|
|
4126
4140
|
picked = await ports.pick(source);
|
|
4127
|
-
} catch {
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
if (/too large|size limit/i.test(String(error))) return { ...latest(), statusText: refusalText("size", source === "document" ? ARIA_MAX_DOCUMENT_BYTES : ARIA_MAX_IMAGE_BYTES), error: "" };
|
|
4128
4143
|
// A picker that could not open is not the owner's fault and there is no
|
|
4129
4144
|
// file to talk about; say nothing rather than something misleading.
|
|
4130
|
-
return latest();
|
|
4145
|
+
return withoutNotice(latest());
|
|
4131
4146
|
}
|
|
4132
4147
|
if (!picked || !picked.dataUri) return latest(); // cancelled
|
|
4133
4148
|
const parsed = parseDataUri(picked.dataUri);
|
|
4134
|
-
const mimeType =
|
|
4149
|
+
const mimeType = attachmentMime(picked.mimeType || parsed?.mimeType || "", picked.name);
|
|
4135
4150
|
// Re-checked after the await: other picks may have landed meanwhile.
|
|
4136
4151
|
const refused = refusalFor(parsed, mimeType, chips.length);
|
|
4137
|
-
if (refused) return { ...latest(), statusText: refusalText(refused), error: "" };
|
|
4152
|
+
if (refused) return { ...latest(), statusText: refusalText(refused, mimeType.startsWith("image/") ? ARIA_MAX_IMAGE_BYTES : ARIA_MAX_DOCUMENT_BYTES), error: "" };
|
|
4138
4153
|
chipSeq += 1;
|
|
4139
4154
|
const id = `att-${chipSeq}`;
|
|
4140
4155
|
chipBytes.set(id, picked.dataUri);
|
|
@@ -4143,7 +4158,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4143
4158
|
name: imageFileName(mimeType, chipSeq, picked.name),
|
|
4144
4159
|
mimeType,
|
|
4145
4160
|
// Only a small image rides the state as its own thumbnail; see `chipBytes`.
|
|
4146
|
-
thumbUrl: picked.dataUri.length <= THUMB_INLINE_MAX ? picked.dataUri : "",
|
|
4161
|
+
thumbUrl: mimeType.startsWith("image/") && picked.dataUri.length <= THUMB_INLINE_MAX ? picked.dataUri : "",
|
|
4147
4162
|
}, "uploading", chipLabels());
|
|
4148
4163
|
chips = [...chips, chip];
|
|
4149
4164
|
startUpload(chip);
|
|
@@ -4202,7 +4217,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4202
4217
|
retryAttachment: (s, arg) => {
|
|
4203
4218
|
const id = chipIdOf(arg);
|
|
4204
4219
|
const current = chips.find((c) => c.id === id);
|
|
4205
|
-
if (!current || current.state !== "failed") return s;
|
|
4220
|
+
if (!current || current.state !== "failed" || (!chipBytes.get(id) && !current.thumbUrl)) return s;
|
|
4206
4221
|
// A NEW id for the new attempt, in the same place in the strip. Keeping the
|
|
4207
4222
|
// old one would let the first attempt's outcome, if it is still in the air,
|
|
4208
4223
|
// settle this one.
|
|
@@ -4421,8 +4436,21 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4421
4436
|
await probeDictation();
|
|
4422
4437
|
return gated(r.listening && !dictationLive ? { ...r, listening: false } : r);
|
|
4423
4438
|
},
|
|
4424
|
-
submit: (s, arg) =>
|
|
4425
|
-
|
|
4439
|
+
submit: async (s, arg) => {
|
|
4440
|
+
const revision = draftSeq;
|
|
4441
|
+
const next = await impl.submit(s, arg);
|
|
4442
|
+
// Refusals preserve a draft; an edit while the async reducer resolved
|
|
4443
|
+
// belongs to the owner, including consuming a selected mention trigger.
|
|
4444
|
+
// Preserve the send result and its pending turn/poll; only rebase the draft.
|
|
4445
|
+
return gated(revision !== draftSeq && next.input === s.input && committed
|
|
4446
|
+
? { ...next, input: committed.input } : next);
|
|
4447
|
+
},
|
|
4448
|
+
submitPrompt: async (s, arg) => {
|
|
4449
|
+
const revision = draftSeq;
|
|
4450
|
+
const next = await impl.submitPrompt(s, arg);
|
|
4451
|
+
return gated(revision !== draftSeq && next.input === s.input && committed
|
|
4452
|
+
? { ...next, input: committed.input } : next);
|
|
4453
|
+
},
|
|
4426
4454
|
newChat: (s) => impl.newChat(s).then(gated),
|
|
4427
4455
|
typing: (s, arg) => gated(impl.typing(s, arg)),
|
|
4428
4456
|
tick: (s) => impl.tick(s).then((r) => (r ? gated(r) : r)),
|