@alia.onl/sdk 15.8.0 → 19.0.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 (141) hide show
  1. package/CHANGELOG.md +242 -13
  2. package/README.md +223 -151
  3. package/package.json +15 -5
  4. package/src/animations.ts +0 -1
  5. package/src/components/AliaAskProvider.tsx +127 -0
  6. package/src/components/AliaChatContent.tsx +604 -392
  7. package/src/components/AliaChatHeader.tsx +14 -38
  8. package/src/components/AliaChatScreen.tsx +56 -31
  9. package/src/components/AliaChatSheet.tsx +110 -36
  10. package/src/components/AliaComposer.tsx +1 -1
  11. package/src/components/AliaParticipants.tsx +202 -0
  12. package/src/components/AliaParticipantsHeader.tsx +60 -0
  13. package/src/components/AliaSharedConversation.tsx +86 -0
  14. package/src/components/AliaWelcomeMessage.tsx +18 -5
  15. package/src/components/Image.tsx +12 -0
  16. package/src/components/Markdown.tsx +498 -14
  17. package/src/components/PlanPreviewCard.tsx +7 -49
  18. package/src/components/ReplyMarkdown.tsx +359 -0
  19. package/src/components/RichBlocks.tsx +195 -0
  20. package/src/components/canvas/CanvasComponent.tsx +59 -0
  21. package/src/components/canvas/ChartRenderer.tsx +73 -0
  22. package/src/components/canvas/CodeRenderer.tsx +24 -0
  23. package/src/components/canvas/FormRenderer.tsx +101 -0
  24. package/src/components/canvas/MarkdownRenderer.tsx +14 -0
  25. package/src/components/canvas/TableRenderer.tsx +47 -0
  26. package/src/components/thought/FilesAndSources.tsx +120 -0
  27. package/src/components/thought/ThoughtPanel.tsx +417 -0
  28. package/src/components/thought/ToolStep.tsx +141 -0
  29. package/src/components/thread/AgentResultCard.tsx +125 -0
  30. package/src/components/thread/AgentTaskCard.tsx +127 -0
  31. package/src/components/thread/AliaThread.tsx +401 -0
  32. package/src/components/thread/MessageBlockBoundary.tsx +78 -0
  33. package/src/components/thread/NewConversationOffer.tsx +54 -0
  34. package/src/components/thread/ThreadRow.tsx +458 -0
  35. package/src/components/thread/ThreadSearch.tsx +129 -0
  36. package/src/components/thread/ToolResultCard.tsx +254 -0
  37. package/src/components/thread/TurnMenu.tsx +54 -0
  38. package/src/components/thread/TurnStatusLine.tsx +34 -0
  39. package/src/components/voice/AliaVoiceCall.tsx +21 -0
  40. package/src/components/voice/VoiceControls.tsx +2 -28
  41. package/src/components/voice/VoiceSession.tsx +39 -11
  42. package/src/hooks/useAliaActivity.ts +2 -2
  43. package/src/hooks/useAtBottom.ts +51 -0
  44. package/src/hooks/useAttachMenu.ts +94 -0
  45. package/src/hooks/useChatComposer.ts +299 -0
  46. package/src/hooks/useChatVoice.ts +129 -0
  47. package/src/hooks/useDocumentPicker.ts +3 -29
  48. package/src/hooks/useFailedTurnToast.ts +43 -0
  49. package/src/hooks/useMessageVote.ts +50 -0
  50. package/src/hooks/useOpenThought.ts +79 -0
  51. package/src/hooks/usePendingQuestions.ts +2 -2
  52. package/src/hooks/useReadAloud.ts +92 -0
  53. package/src/hooks/useShareHandler.ts +24 -0
  54. package/src/hooks/useSoundEffects.ts +1 -1
  55. package/src/hooks/useThoughtScope.ts +59 -0
  56. package/src/hooks/useThreadRows.ts +145 -0
  57. package/src/hooks/useThreadView.ts +68 -0
  58. package/src/hooks/useTimelineWindow.ts +256 -0
  59. package/src/hooks/useVoiceLauncher.ts +180 -0
  60. package/src/hooks/useVoiceRoom.ts +0 -3
  61. package/src/index.ts +43 -29
  62. package/src/lib/activity-store.ts +1 -1
  63. package/src/lib/add-menu.ts +7 -0
  64. package/src/lib/call-thread.ts +33 -0
  65. package/src/lib/citations.ts +79 -2
  66. package/src/lib/clipboard.ts +23 -0
  67. package/src/lib/content-utils.ts +10 -0
  68. package/src/lib/copy-en.ts +284 -0
  69. package/src/lib/copy.ts +32 -0
  70. package/src/lib/document-picker.ts +31 -0
  71. package/src/lib/flights.ts +22 -13
  72. package/src/lib/focus-return.ts +66 -0
  73. package/src/lib/keyboard.native.tsx +19 -0
  74. package/src/lib/keyboard.tsx +9 -0
  75. package/src/lib/labels.tsx +69 -17
  76. package/src/lib/participants.ts +86 -0
  77. package/src/lib/power-level-options.ts +66 -0
  78. package/src/lib/questions.ts +2 -2
  79. package/src/lib/task-labels.ts +4 -4
  80. package/src/lib/tool-call-status.ts +1 -1
  81. package/src/lib/tool-icons.ts +36 -0
  82. package/src/lib/tool-renderers.tsx +48 -0
  83. package/src/lib/turn-state.ts +2 -2
  84. package/src/lib/voice-call-request.ts +49 -0
  85. package/src/lib/voice-call.ts +76 -34
  86. package/src/lib/voice-controls-labels.ts +31 -0
  87. package/src/lib/voice-protocol.ts +32 -14
  88. package/src/lib/work-log.ts +6 -6
  89. package/src/runtime/agents.ts +151 -0
  90. package/src/runtime/api.ts +123 -0
  91. package/src/runtime/attachment-utils.ts +96 -0
  92. package/src/runtime/composer-draft-store.ts +270 -0
  93. package/src/runtime/config.tsx +186 -0
  94. package/src/runtime/conversation-store.ts +49 -0
  95. package/src/runtime/conversations.ts +557 -0
  96. package/src/runtime/error-utils.ts +104 -0
  97. package/src/runtime/host-tools.ts +140 -0
  98. package/src/runtime/index.ts +113 -0
  99. package/src/runtime/message-history.ts +124 -0
  100. package/src/runtime/model.ts +39 -0
  101. package/src/runtime/oxy-files.ts +37 -0
  102. package/src/runtime/power-levels.ts +66 -0
  103. package/src/runtime/query-keys.ts +34 -0
  104. package/src/runtime/random-uuid.ts +25 -0
  105. package/src/runtime/routes.ts +51 -0
  106. package/src/runtime/sharing.ts +165 -0
  107. package/src/runtime/sse-frame-reader.ts +88 -0
  108. package/src/runtime/stream-events.ts +146 -0
  109. package/src/runtime/stream-outcome.ts +21 -0
  110. package/src/runtime/thought-store.ts +103 -0
  111. package/src/runtime/thread/agent-activity.ts +261 -0
  112. package/src/runtime/thread/automation-labels.ts +79 -0
  113. package/src/runtime/thread/conversation-export.ts +135 -0
  114. package/src/runtime/thread/message-days.ts +120 -0
  115. package/src/runtime/thread/message-processor.ts +93 -0
  116. package/src/runtime/thread/task-utils.ts +11 -0
  117. package/src/runtime/thread/thought-utils.ts +752 -0
  118. package/src/runtime/thread/thread-history.ts +139 -0
  119. package/src/runtime/thread/timeline.ts +202 -0
  120. package/src/runtime/thread/title-tags.ts +8 -0
  121. package/src/runtime/thread/tool-cards.ts +383 -0
  122. package/src/runtime/turn-failure.ts +108 -0
  123. package/src/runtime/types.ts +148 -0
  124. package/src/runtime/usage-limit-error.ts +44 -0
  125. package/src/runtime/use-agent-activity.ts +80 -0
  126. package/src/runtime/use-conversation-stream.ts +1427 -0
  127. package/src/runtime/use-conversation-thread.ts +556 -0
  128. package/src/runtime/use-start-conversation.ts +107 -0
  129. package/src/runtime/use-thread-history.ts +64 -0
  130. package/src/runtime/use-thread-search.ts +95 -0
  131. package/src/runtime/use-turn-edit.ts +99 -0
  132. package/src/tools.ts +2 -2
  133. package/src/types.ts +18 -47
  134. package/src/components/AliaChatMessageList.tsx +0 -532
  135. package/src/components/ThinkingBlock.tsx +0 -43
  136. package/src/hooks/useAliaChat.ts +0 -573
  137. package/src/lib/catalogue.ts +0 -236
  138. package/src/lib/chat-stream.ts +0 -655
  139. package/src/lib/chat-transport.ts +0 -98
  140. package/src/lib/delegation.ts +0 -34
  141. package/src/lib/work-log-copy.ts +0 -61
@@ -0,0 +1,284 @@
1
+ /**
2
+ * The chat's own English, keyed the way the Alia app's catalogue keys it, so the app hands its
3
+ * translator in (`labels.t`) and a host without one reads English. Generated from the app's
4
+ * `locales/en.json` and kept by hand from here: a key a component asks for belongs here.
5
+ */
6
+ export type CopyEntry = string | { zero?: string; one?: string; other: string };
7
+
8
+ export const COPY_EN: Readonly<Record<string, CopyEntry>> = {
9
+ 'automations.cron.dailyAt': 'Daily at {{time}}',
10
+ 'automations.cron.dayPlural.0': 'Sundays',
11
+ 'automations.cron.dayPlural.1': 'Mondays',
12
+ 'automations.cron.dayPlural.2': 'Tuesdays',
13
+ 'automations.cron.dayPlural.3': 'Wednesdays',
14
+ 'automations.cron.dayPlural.4': 'Thursdays',
15
+ 'automations.cron.dayPlural.5': 'Fridays',
16
+ 'automations.cron.dayPlural.6': 'Saturdays',
17
+ 'automations.cron.daysAt': '{{days}} at {{time}}',
18
+ 'automations.cron.everyHour': 'Every hour',
19
+ 'automations.cron.everyHours': 'Every {{count}} hours',
20
+ 'automations.cron.everyMinutes': { one: 'Every minute', other: 'Every {{count}} minutes' },
21
+ 'automations.cron.weekdaysAt': 'Weekdays at {{time}}',
22
+ 'automations.cron.weekendsAt': 'Weekends at {{time}}',
23
+ 'automations.trigger.anyApp': 'Any app',
24
+ 'automations.trigger.anyEvent': 'Any event',
25
+ 'automations.trigger.manual': 'Manual request',
26
+ 'automations.trigger.unscheduled': 'Unscheduled',
27
+ 'chat.agentRun.by': 'by %{name}',
28
+ 'chat.agentRun.complete': 'Task complete',
29
+ 'chat.agentRun.completed': 'Completed',
30
+ 'chat.agentRun.credits': '%{count} credits',
31
+ 'chat.agentRun.error': 'Error',
32
+ 'chat.agentRun.failed': 'Failed',
33
+ 'chat.agentRun.planItems': '%{done}/%{total} plan items',
34
+ 'chat.agentRun.planSummary': 'Plan summary',
35
+ 'chat.agentRun.steps': '%{count} steps',
36
+ 'chat.agentRun.taskComplete': 'Task complete',
37
+ 'chat.agentRun.taskFailed': 'Task failed',
38
+ 'chat.agentRun.unknown': 'Unknown',
39
+ 'chat.agentRun.working': 'Agent working',
40
+ 'chat.backToLatest': 'Back to the latest',
41
+ 'chat.blockFailed': "This part of the answer couldn't be shown.",
42
+ 'chat.bloom.context.freeSpace': 'Free space',
43
+ 'chat.bloom.context.mcp': 'MCP tools',
44
+ 'chat.bloom.context.memory': 'Memory',
45
+ 'chat.bloom.context.messages': 'Messages',
46
+ 'chat.bloom.context.skills': 'Skills',
47
+ 'chat.bloom.context.system': 'System prompt',
48
+ 'chat.bloom.context.title': 'Context window',
49
+ 'chat.bloom.context.tools': 'Tools',
50
+ 'chat.bloom.createdFile': 'Created',
51
+ 'chat.bloom.limits.close': 'Close credits panel',
52
+ 'chat.bloom.limits.dailyFree': 'Daily free credits',
53
+ 'chat.bloom.limits.planUsage': 'Plan usage limits',
54
+ 'chat.bloom.limits.resetsDaily': 'Resets every 24 hours',
55
+ 'chat.bloom.limits.resetsInHours': 'Resets in {{hours}} hr {{minutes}} min',
56
+ 'chat.bloom.limits.resetsInMinutes': 'Resets in {{minutes}} min',
57
+ 'chat.bloom.limits.resetsSoon': 'Resets soon',
58
+ 'chat.bloom.limits.window': '{{hours}}-hour limit',
59
+ 'chat.bloom.limits.windowFresh': 'Resets {{hours}} hours after your next message',
60
+ 'chat.bloom.noResults': 'No results',
61
+ 'chat.bloom.researched': 'Researched',
62
+ 'chat.bloom.researching': 'Researching',
63
+ 'chat.bloom.resultCount': { one: '1 result', other: '{{count}} results' },
64
+ 'chat.bloom.scrollToLatest': 'Scroll to latest messages',
65
+ 'chat.bloom.searchCount': { one: '1 search', other: '{{count}} searches' },
66
+ 'chat.bloom.searchFailed': 'Search failed',
67
+ 'chat.bloom.searchedWeb': 'Searched the web for',
68
+ 'chat.bloom.sourceCount': { one: '1 source', other: '{{count}} sources' },
69
+ 'chat.bloom.taskFailed': 'Returned an error',
70
+ 'chat.bloom.taskInput': 'With',
71
+ 'chat.bloom.taskStopped': "Didn't finish",
72
+ 'chat.bloom.visited': 'Visited',
73
+ 'chat.bloom.writingFile': 'Writing',
74
+ 'chat.copied': 'Copied!',
75
+ 'chat.copiedToClipboard': 'Copied to clipboard',
76
+ 'chat.copy': 'Copy',
77
+ 'chat.copyFailed': "Couldn't copy to the clipboard",
78
+ 'chat.dislike': 'Dislike',
79
+ 'chat.edit': 'Edit',
80
+ 'chat.feedbackFailed': "Your feedback couldn't be saved.",
81
+ 'chat.like': 'Like',
82
+ 'chat.loadingHistory': 'Loading earlier messages…',
83
+ 'chat.newConversationAccept': 'Start a new one',
84
+ 'chat.newConversationDismiss': 'Not now',
85
+ 'chat.newConversationOffer': 'Start a new conversation?',
86
+ 'chat.newStretch': 'New conversation',
87
+ 'chat.readAloud': 'Read Aloud',
88
+ 'chat.readAloudFailed': "Couldn't read this aloud",
89
+ 'chat.regenerate': 'Regenerate',
90
+ 'chat.retry': 'Retry',
91
+ 'chat.richBlocks.source': 'Source: {{source}}',
92
+ 'chat.searchThreadAgent': 'Agent',
93
+ 'chat.searchThreadEmpty': 'Nothing matched.',
94
+ 'chat.searchThreadFailed': 'The search could not be run.',
95
+ 'chat.searchThreadHint':
96
+ 'Search everything you have said to this agent, across every conversation.',
97
+ 'chat.searchThreadPlaceholder': 'Search this thread',
98
+ 'chat.searchThreadSearching': 'Searching…',
99
+ 'chat.searchThreadYou': 'You',
100
+ 'chat.sources': 'Sources',
101
+ 'chat.stopReading': 'Stop reading',
102
+ 'chat.thanksFeedback': 'Thanks for your feedback!',
103
+ 'chat.thinking': 'Thinking',
104
+ 'chat.today': 'Today',
105
+ 'chat.toolCard.openTask': 'Open scheduled task %{objective}',
106
+ 'chat.toolCard.paused': 'Paused',
107
+ 'chat.toolCard.scheduled': 'Scheduled',
108
+ 'chat.turnActions': 'Message actions',
109
+ 'chat.consultFailed': "%{name} couldn't answer.",
110
+ 'chat.shareFailed': "Couldn't create the share link.",
111
+ 'chat.sharedFailed': "Couldn't load this conversation.",
112
+ 'chat.sharedGone': 'This shared conversation is no longer available.',
113
+ 'chat.turnFailed': "Alia couldn't answer right now.",
114
+ 'chat.turnFailedRetryHint': 'Try again.',
115
+ 'chat.turnInterrupted': 'Alia was interrupted before finishing.',
116
+ 'chat.working': 'Working',
117
+ 'chat.yesterday': 'Yesterday',
118
+ 'common.close': 'Close',
119
+ 'composer.cancelEdit': 'Cancel',
120
+ 'composer.editPlaceholder': 'Edit your message',
121
+ 'composer.editingMessage': 'Editing message',
122
+ 'faircoin.indexed':
123
+ 'Indexed price from the WFAIR/USDC pool on Base — FairCoin has no exchange listing',
124
+ 'faircoin.liquidity': 'Liquidity',
125
+ 'faircoin.marketCap': 'Market cap',
126
+ 'faircoin.name': 'FairCoin',
127
+ 'faircoin.noChart': 'Only one sample so far — not enough to draw a line.',
128
+ 'faircoin.noPrice': 'No price',
129
+ 'faircoin.noPriceHint': 'The WFAIR/USDC pool had no indexed price when this was asked.',
130
+ 'faircoin.noSamples': 'No samples for this period yet.',
131
+ 'faircoin.range.1y': '1Y',
132
+ 'faircoin.range.24h': '24h',
133
+ 'faircoin.range.30d': '30D',
134
+ 'faircoin.range.7d': '7D',
135
+ 'faircoin.range.all': 'All',
136
+ 'faircoin.scale.billion': 'B',
137
+ 'faircoin.scale.million': 'M',
138
+ 'faircoin.scale.thousand': 'K',
139
+ 'faircoin.scale.trillion': 'T',
140
+ 'faircoin.source': 'From %{source}',
141
+ 'faircoin.symbol': 'FAIR',
142
+ 'faircoin.unavailable': 'No history for %{ranges}',
143
+ 'faircoin.updated': 'Updated %{time}',
144
+ 'faircoin.volume': '24h volume',
145
+ 'market.dayHigh': 'Day high',
146
+ 'market.dayLow': 'Day low',
147
+ 'market.marketCap': 'Market cap',
148
+ 'market.noChart': 'Not enough history to chart this range',
149
+ 'market.noPrice': 'No quote available',
150
+ 'market.open': 'Open',
151
+ 'market.range.1D': '1D',
152
+ 'market.range.1M': '1M',
153
+ 'market.range.1Y': '1Y',
154
+ 'market.range.5D': '5D',
155
+ 'market.range.5Y': '5Y',
156
+ 'market.range.6M': '6M',
157
+ 'market.range.MAX': 'MAX',
158
+ 'market.range.YTD': 'YTD',
159
+ 'market.volume': 'Volume (24h)',
160
+ 'panels.canvas.image': 'Image',
161
+ 'panels.canvas.unsupported': 'Unsupported component type: {{type}}',
162
+ 'panels.chart.share': 'Share',
163
+ 'panels.chart.value': 'Value',
164
+ 'panels.code.copied': 'Code copied',
165
+ 'panels.code.copy': 'Copy code',
166
+ 'panels.form.enter': 'Enter {{field}}',
167
+ 'panels.form.select': 'Select…',
168
+ 'panels.form.submit': 'Submit',
169
+ 'powerLevels.auto.description': 'Picks the right level for each message',
170
+ 'powerLevels.auto.label': 'Auto',
171
+ 'powerLevels.high.description': 'Thinks harder on tougher questions',
172
+ 'powerLevels.high.label': 'High',
173
+ 'powerLevels.instant.description': 'Fastest and cheapest, for quick answers',
174
+ 'powerLevels.instant.label': 'Instant',
175
+ 'powerLevels.medium.description': 'Quick and capable, for everyday tasks',
176
+ 'powerLevels.medium.label': 'Medium',
177
+ 'powerLevels.onDevice': 'On %{device}',
178
+ 'powerLevels.pro.description': 'Frontier models; slower and costs more',
179
+ 'powerLevels.pro.label': 'Pro',
180
+ 'powerLevels.ultra.description': 'Most capable; slowest and costs the most',
181
+ 'powerLevels.ultra.label': 'Ultra',
182
+ 'powerLevels.xhigh.description': 'Thinks even harder; a little slower',
183
+ 'powerLevels.xhigh.label': 'XHigh',
184
+ 'tasks.tool.done.agentSearch': 'Searched agents',
185
+ 'tasks.tool.done.browse': 'Browsed',
186
+ 'tasks.tool.done.codeInterpreter': 'Ran code',
187
+ 'tasks.tool.done.consultAgent': 'Consulted an agent',
188
+ 'tasks.tool.done.fileEdit': 'Edited file',
189
+ 'tasks.tool.done.generateFile': 'Generated file',
190
+ 'tasks.tool.done.shellExec': 'Ran command',
191
+ 'tasks.tool.done.loadTools': 'Loaded tools',
192
+ 'tasks.tool.done.userMemory': 'Remembered',
193
+ 'tasks.tool.done.webScraper': 'Read page',
194
+ 'tasks.tool.pill.agentSearch': 'Searching agents',
195
+ 'tasks.tool.pill.browse': 'Browsing',
196
+ 'tasks.tool.pill.codeInterpreter': 'Running code',
197
+ 'tasks.tool.pill.consultAgent': 'Consulting an agent',
198
+ 'tasks.tool.pill.fileEdit': 'Editing file',
199
+ 'tasks.tool.pill.generateFile': 'Generating file',
200
+ 'tasks.tool.pill.sendDiscord': 'Discord',
201
+ 'tasks.tool.pill.sendEmail': 'Email',
202
+ 'tasks.tool.pill.sendSignal': 'Signal',
203
+ 'tasks.tool.pill.sendSlack': 'Slack',
204
+ 'tasks.tool.pill.sendTelegramMessage': 'Telegram',
205
+ 'tasks.tool.pill.shellExec': 'Running command',
206
+ 'tasks.tool.pill.loadTools': 'Loading tools',
207
+ 'tasks.tool.pill.userMemory': 'Remembering',
208
+ 'tasks.tool.pill.webScraper': 'Reading page',
209
+ 'tasks.tool.pill.webSearch': 'Web search',
210
+ 'thought.activity': 'Activity',
211
+ 'thought.audit.agent': 'Agent: {{name}}',
212
+ 'thought.audit.fileGenerated': 'File generated',
213
+ 'thought.audit.plan': 'Plan',
214
+ 'thought.audit.planSteps': { one: '1 step', other: '{{count}} steps' },
215
+ 'thought.audit.researchComplete': 'Research complete',
216
+ 'thought.audit.researchIncomplete': 'Research incomplete',
217
+ 'thought.audit.researchPhase.decomposing': 'Research: breaking down the question',
218
+ 'thought.audit.researchPhase.finalizing': 'Research: finishing up',
219
+ 'thought.audit.researchPhase.follow_up': 'Research: following up',
220
+ 'thought.audit.researchPhase.reading': 'Research: reading articles',
221
+ 'thought.audit.researchPhase.searching': 'Research: searching sources',
222
+ 'thought.audit.researchPhase.synthesizing': 'Research: synthesizing findings',
223
+ 'thought.audit.researching': 'Researching',
224
+ 'thought.cancelled': 'Stopped',
225
+ 'thought.done': 'Done',
226
+ 'thought.failed': 'Failed',
227
+ 'thought.failedAfter': 'Failed after {{elapsed}}',
228
+ 'thought.filesAndSources': 'Files and sources',
229
+ 'thought.input': 'Input',
230
+ 'thought.loadFailed': "This conversation couldn't be loaded",
231
+ 'thought.loading': 'Loading this conversation…',
232
+ 'thought.messageGone': 'This message is no longer in the conversation',
233
+ 'thought.noActivity': 'No actions recorded yet',
234
+ 'thought.noOutputs': 'No files generated',
235
+ 'thought.noSources': 'No sources used',
236
+ 'thought.noSteps': 'No steps to display',
237
+ 'thought.openOutput': 'Open {{name}} in the canvas',
238
+ 'thought.output': 'Output',
239
+ 'thought.outputs': 'Outputs',
240
+ 'thought.reasoning': 'Reasoning',
241
+ 'thought.references': 'References',
242
+ 'thought.sourceLabel': 'Source {{n}}: {{title}}',
243
+ 'thought.sources': 'Sources',
244
+ 'thought.steps': 'Steps',
245
+ 'thought.stoppedAfter': 'Stopped after {{elapsed}}',
246
+ 'thought.thinking': 'Thinking',
247
+ 'thought.thinkingFor': 'Thinking for {{seconds}}s...',
248
+ 'thought.thoughtFor': 'Thought for {{seconds}} seconds',
249
+ 'thought.title': 'Thought',
250
+ 'thought.tool.browse': 'Browsing the web',
251
+ 'thought.tool.generateFile': 'Generating file',
252
+ 'thought.tool.getCurrentDate': 'Getting current date',
253
+ 'thought.tool.getTimeline': 'Getting timeline',
254
+ 'thought.tool.getWhatsAppChats': 'Loading WhatsApp chats',
255
+ 'thought.tool.getWhatsAppMessages': 'Reading WhatsApp messages',
256
+ 'thought.tool.saveUserMemory': 'Saving to memory',
257
+ 'thought.tool.scrapeURL': 'Reading URL',
258
+ 'thought.tool.searchKnowledgeBase': 'Searching knowledge base',
259
+ 'thought.tool.sendTelegramMessage': 'Sending Telegram message',
260
+ 'thought.tool.sendWhatsAppMessage': 'Sending WhatsApp message',
261
+ 'thought.tool.updateUserContext': 'Updating context',
262
+ 'thought.tool.updateUserPreferences': 'Updating preferences',
263
+ 'thought.tool.webScraper': 'Reading web page',
264
+ 'thought.tool.webSearch': 'Searching the web',
265
+ 'thought.viewDetails': 'View details',
266
+ 'thought.websites': 'Websites',
267
+ 'thought.worked': 'Worked',
268
+ 'thought.workedFor': 'Worked for {{elapsed}}',
269
+ 'thought.working': 'Working…',
270
+ 'thought.workingFor': 'Working for {{elapsed}}…',
271
+ 'thought.writing': 'Writing',
272
+ 'usageLimit.inputDisabledPlaceholder': 'Usage limit reached',
273
+ 'weather.condition.clear': 'Clear',
274
+ 'weather.condition.cloudy': 'Cloudy',
275
+ 'weather.condition.drizzle': 'Drizzle',
276
+ 'weather.condition.fog': 'Fog',
277
+ 'weather.condition.partly-cloudy': 'Partly cloudy',
278
+ 'weather.condition.rain': 'Rain',
279
+ 'weather.condition.showers': 'Showers',
280
+ 'weather.condition.snow': 'Snow',
281
+ 'weather.condition.snow-showers': 'Snow showers',
282
+ 'weather.condition.thunderstorm': 'Thunderstorm',
283
+ 'weather.toggleUnit': 'Switch temperature unit',
284
+ };
@@ -0,0 +1,32 @@
1
+ import { COPY_EN, type CopyEntry } from './copy-en';
2
+
3
+ /**
4
+ * A translator keyed the way the Alia app's own catalogue keys its strings, so the app hands its
5
+ * `t` straight in (`labels.t`). The words the chat draws that are not one fixed `labels` field —
6
+ * the thread, its cards, the thought panel, the work log — are asked for by key.
7
+ */
8
+ export type Translate = (key: string, params?: Record<string, unknown>) => string;
9
+
10
+ function entryFor(entry: CopyEntry, count: number | undefined): string {
11
+ if (typeof entry === 'string') return entry;
12
+ if (count === 0 && entry.zero !== undefined) return entry.zero;
13
+ if (count === 1 && entry.one !== undefined) return entry.one;
14
+ return entry.other;
15
+ }
16
+
17
+ /** `{{name}}` and `%{name}` both name a parameter, as in the app's catalogue. */
18
+ const PLACEHOLDER = /\{\{\s*(\w+)\s*\}\}|%\{(\w+)\}/g;
19
+
20
+ /**
21
+ * The default translator: the English in `copy-en.ts`, with `{{count}}` plurals and parameters.
22
+ * A key it does not know answers itself, so a missing string is visible rather than blank.
23
+ */
24
+ export const englishCopy: Translate = (key, params) => {
25
+ const entry = COPY_EN[key];
26
+ if (entry === undefined) return key;
27
+ const count = typeof params?.count === 'number' ? params.count : undefined;
28
+ return entryFor(entry, count).replace(PLACEHOLDER, (placeholder, a?: string, b?: string) => {
29
+ const value = params?.[(a ?? b) as string];
30
+ return value === undefined ? placeholder : String(value);
31
+ });
32
+ };
@@ -0,0 +1,31 @@
1
+ import * as DocumentPicker from 'expo-document-picker';
2
+
3
+ export type DocumentPickerResult = {
4
+ uri: string;
5
+ name: string;
6
+ size: number;
7
+ mimeType: string;
8
+ };
9
+
10
+ /** The platform's document picker, several at once; nothing when the person cancels or it fails. */
11
+ export async function pickDocuments(): Promise<DocumentPickerResult[] | undefined> {
12
+ try {
13
+ const result = await DocumentPicker.getDocumentAsync({
14
+ type: '*/*',
15
+ multiple: true,
16
+ copyToCacheDirectory: true,
17
+ });
18
+
19
+ if (result.canceled === false && result.assets && result.assets.length > 0) {
20
+ return result.assets.map((asset) => ({
21
+ uri: asset.uri,
22
+ name: asset.name,
23
+ size: asset.size || 0,
24
+ mimeType: asset.mimeType || 'application/octet-stream',
25
+ }));
26
+ }
27
+ } catch (error) {
28
+ console.warn('Failed to pick document:', error);
29
+ }
30
+ return undefined;
31
+ }
@@ -1,24 +1,33 @@
1
1
  import { createContext, useContext } from 'react';
2
2
  import type { AvatarFlights, AvatarFlightSpec } from '@oxy.so/bloom/avatar-flight';
3
3
 
4
- import { ALIA_AVATAR } from '../components/AliaAvatar';
4
+ import type { AliaParticipant } from './participants';
5
5
 
6
- /** Where Alia's avatar is in the chat: large in the greeting, then small in the header's profile. */
7
- export const ALIA_ANCHORS = { welcome: 'alia:welcome', profile: 'alia:profile' } as const;
6
+ /**
7
+ * Where the conversation's avatars are: the lead's, large in the greeting; everybody's, small in
8
+ * the header's participants; and a consulted agent's, beside the reply it is writing.
9
+ */
10
+ export const ANCHORS = {
11
+ welcome: 'lead:welcome',
12
+ profile: 'participants',
13
+ reply: (agentId: string) => `reply:${agentId}`,
14
+ } as const;
8
15
 
9
- /** The greeting's avatar flies up into the header's profile as the conversation begins. */
10
- export const ALIA_TO_PROFILE: AvatarFlightSpec = {
11
- key: 'alia',
12
- character: ALIA_AVATAR,
13
- from: ALIA_ANCHORS.welcome,
14
- to: ALIA_ANCHORS.profile,
15
- duration: 800,
16
- };
16
+ /** The greeting's avatar flies up into the header's participants as the conversation begins. */
17
+ export function leadToProfile(lead: AliaParticipant): AvatarFlightSpec {
18
+ return {
19
+ key: 'lead',
20
+ character: lead.character,
21
+ from: ANCHORS.welcome,
22
+ to: ANCHORS.profile,
23
+ duration: 800,
24
+ };
25
+ }
17
26
 
18
27
  const FlightsContext = createContext<AvatarFlights | null>(null);
19
28
 
20
- /** The chat's avatar flights, for the greeting and the header that share them. */
29
+ /** The chat's avatar flights, for the greeting, the thread and the header that share them. */
21
30
  export const AliaFlightsProvider = FlightsContext.Provider;
22
31
 
23
- /** `null` outside a chat: the greeting and the header then draw their avatars without flying. */
32
+ /** `null` outside a chat: the greeting, the replies and the header then draw their avatars without flying. */
24
33
  export const useAliaFlights = () => useContext(FlightsContext);
@@ -0,0 +1,66 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ /**
4
+ * Where keyboard focus goes back to when the execution panel closes.
5
+ *
6
+ * The panel is opened from a row in the conversation — a tool step, the
7
+ * sources row, the reasoning trigger — and on the web the desktop rail is not
8
+ * a dialog: nothing moves focus into it and nothing would move it back. So a
9
+ * screen-reader or keyboard user who opened it and pressed Escape was left
10
+ * with focus wherever the browser had put it, which after a click on a
11
+ * `Pressable` is `body` in Safari. The opener is remembered here at open time
12
+ * and focused again at close.
13
+ *
14
+ * One slot, module-level: there is one panel, and the store's own selection
15
+ * already says which message it is about. A DOM element in the store would
16
+ * also be a DOM element in a persisted store's state tree, which it must not
17
+ * be.
18
+ */
19
+ type Focusable = { focus: () => void };
20
+
21
+ let opener: Focusable | null = null;
22
+
23
+ function isFocusable(value: unknown): value is Focusable {
24
+ return (
25
+ typeof value === 'object' &&
26
+ value !== null &&
27
+ typeof (value as { focus?: unknown }).focus === 'function'
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Remember what opened the panel. A caller with a ref to its own control
33
+ * passes it; one without (a memoised row whose ref would be one more prop)
34
+ * lets this read the element that is focused right now, which on the web is
35
+ * the button that was just activated by keyboard, and the button or `body`
36
+ * after a pointer press depending on the browser. Nothing is remembered on
37
+ * native, where focus does not work this way.
38
+ */
39
+ export function rememberOpener(candidate?: unknown): void {
40
+ if (isFocusable(candidate)) {
41
+ opener = candidate;
42
+ return;
43
+ }
44
+ if (
45
+ Platform.OS === 'web' &&
46
+ typeof document !== 'undefined' &&
47
+ isFocusable(document.activeElement)
48
+ ) {
49
+ opener = document.activeElement;
50
+ return;
51
+ }
52
+ opener = null;
53
+ }
54
+
55
+ /** Focus the remembered opener, once, and forget it. */
56
+ export function restoreOpenerFocus(): void {
57
+ const target = opener;
58
+ opener = null;
59
+ if (target === null) return;
60
+ try {
61
+ target.focus();
62
+ } catch {
63
+ // An element that has since left the document cannot take focus; there
64
+ // is nowhere better to send it.
65
+ }
66
+ }
@@ -1,6 +1,25 @@
1
1
  // Native: re-export react-native-keyboard-controller components
2
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated';
3
+ import { useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller';
4
+
2
5
  export {
3
6
  KeyboardAwareScrollView,
4
7
  KeyboardAvoidingView,
5
8
  KeyboardProvider,
6
9
  } from 'react-native-keyboard-controller';
10
+
11
+ /**
12
+ * The room under a bottom-anchored control for the gesture bar, while the gesture bar is what lies
13
+ * under it.
14
+ *
15
+ * With the keyboard up, a keyboard-avoiding host already ends at the keyboard's top edge, and the
16
+ * keyboard covers the gesture bar. Keeping this room then counts the bar twice: the chat's composer
17
+ * floated a bar's height above the keyboard, with the transcript showing through the gap (Pixel 8a).
18
+ * It follows the keyboard's own animation, so the control settles onto the keyboard as it rises
19
+ * instead of jumping after.
20
+ */
21
+ export function KeyboardSafeAreaFloor({ inset }: { inset: number }) {
22
+ const { progress } = useReanimatedKeyboardAnimation();
23
+ const style = useAnimatedStyle(() => ({ height: inset * (1 - progress.value) }), [inset]);
24
+ return <Animated.View style={style} />;
25
+ }
@@ -5,6 +5,7 @@ import {
5
5
  type ScrollViewProps,
6
6
  KeyboardAvoidingView as RNKeyboardAvoidingView,
7
7
  type KeyboardAvoidingViewProps as RNKeyboardAvoidingViewProps,
8
+ View,
8
9
  } from 'react-native';
9
10
 
10
11
  // Accept native-only props so shared components don't cause TS errors
@@ -41,3 +42,11 @@ export { KeyboardAwareScrollView, KeyboardAvoidingView };
41
42
  export function KeyboardProvider({ children }: { children: React.ReactNode }) {
42
43
  return <>{children}</>;
43
44
  }
45
+
46
+ /**
47
+ * The room under a bottom-anchored control for the system's bottom bar. The web's keyboard resizes
48
+ * the page rather than covering it, so that room never has to give way to it (see the native file).
49
+ */
50
+ export function KeyboardSafeAreaFloor({ inset }: { inset: number }) {
51
+ return <View style={{ height: inset }} />;
52
+ }
@@ -1,6 +1,11 @@
1
1
  import { createContext, type ReactNode, useContext, useMemo } from 'react';
2
2
  import { VOICE_ERROR_MESSAGES, type VoiceErrorCode } from './speech-messages';
3
- import { englishWorkLog, type Translate } from './work-log-copy';
3
+ import { englishCopy, type Translate } from './copy';
4
+ import { VOICE_CONTROLS_LABELS, type VoiceControlsLabels } from './voice-controls-labels';
5
+ import type { VoiceCloseReason } from './voice-protocol';
6
+
7
+ /** Why a call could not be opened; the host's `canStartVoice` answers with one. */
8
+ export type VoiceBlockReason = 'sign-in' | 'plan-required' | 'credits-exhausted';
4
9
 
5
10
  /** Every string the chat UI draws itself. Bloom's own copy follows `BloomProvider locale`. */
6
11
  export interface AliaChatLabels {
@@ -11,11 +16,21 @@ export interface AliaChatLabels {
11
16
  more: string;
12
17
  chatActions: string;
13
18
  // Composer
14
- placeholder: string;
19
+ /** The empty composer's prompt, naming the conversation's lead ("Ask Sindi..."). */
20
+ placeholder: (leadName: string) => string;
15
21
  addMenuGroup: string;
16
22
  addPhotos: string;
23
+ /** The add menu's row for documents, where the lead takes them. */
24
+ addDocuments: string;
17
25
  startVoice: string;
18
- voiceFailed: string;
26
+ /** The call bar's words (status, mute, end). */
27
+ voiceControls: VoiceControlsLabels;
28
+ /** Why a call ended without failing; `null` for a hang-up (the person knows). */
29
+ voiceEnded: (reason: VoiceCloseReason) => string | null;
30
+ /** Said once, and shown in the bar, when the call is about to reach its time limit. */
31
+ voiceEndingSoon: string;
32
+ /** Why a call was not opened, told instead of a button that silently does nothing. */
33
+ voiceBlocked: (reason: VoiceBlockReason) => string;
19
34
  /** The field's accessible name. */
20
35
  composerMessage: string;
21
36
  /** The add button's accessible name. */
@@ -61,30 +76,61 @@ export interface AliaChatLabels {
61
76
  stopReading: string;
62
77
  // Plan
63
78
  planTitle: string;
64
- planApprove: string;
65
- planCancel: string;
66
- planApproved: string;
67
- planCancelled: string;
68
79
  // Reasoning
69
80
  reasoning: string;
70
81
  thoughtFor: (seconds: number) => string;
71
- // The turn's work log
72
- /** The words of the tool log, keyed as the Alia app keys them (see `work-log-copy.ts`). */
73
- workLog: Translate;
82
+ // The words that are not one fixed field
83
+ /** The words that are not one fixed field (the thread, its cards, the work log), keyed as the Alia app keys them (see `copy.ts`). */
84
+ t: Translate;
85
+ /** The language `t` speaks, as a BCP 47 tag: dates, days and prices in the thread are formatted in it. */
86
+ locale: string;
87
+ // Conversations (the runtime's own words: toasts and the errors it raises)
88
+ /** A conversation's title until it has one. */
89
+ newChat: string;
90
+ /** A turn could not be sent and the composer got its text back. */
91
+ sendFailed: string;
92
+ /** The session ended mid-request. */
93
+ signInRequired: string;
94
+ conversationSaveFailed: string;
95
+ conversationDeleteFailed: string;
96
+ conversationCreateFailed: string;
97
+ conversationClearFailed: string;
98
+ /** An attachment of a kind the chat API does not carry was left out of the turn. */
99
+ attachmentNotSent: (name: string) => string;
100
+ /** A photo whose bytes could not be read was left out of the turn. */
101
+ attachmentUnreadable: (name: string) => string;
74
102
  // Research
75
103
  }
76
104
 
105
+ const ENDED_ENGLISH: Partial<Record<VoiceCloseReason, string>> = {
106
+ 'call-moved-elsewhere': 'The call continues on your other device',
107
+ 'max-duration': 'The call reached its time limit',
108
+ 'idle-timeout': 'The call ended after two minutes of silence',
109
+ 'credits-exhausted': 'The call ended: you ran out of credits',
110
+ 'server-shutdown': 'The call ended while Alia was updating — start a new one',
111
+ };
112
+
113
+ const BLOCKED_ENGLISH: Record<VoiceBlockReason, string> = {
114
+ 'sign-in': 'Sign in to use voice',
115
+ 'plan-required': 'Voice mode needs a paid plan',
116
+ 'credits-exhausted': "You've run out of credits",
117
+ };
118
+
77
119
  export const DEFAULT_LABELS: AliaChatLabels = {
78
120
  clear: 'Clear',
79
121
  back: 'Back',
80
122
  share: 'Share',
81
123
  more: 'More',
82
124
  chatActions: 'Chat actions',
83
- placeholder: 'Ask Alia...',
125
+ placeholder: (leadName) => `Ask ${leadName}...`,
84
126
  addMenuGroup: 'Add',
85
127
  addPhotos: 'Photos',
128
+ addDocuments: 'Files',
86
129
  startVoice: 'Start voice',
87
- voiceFailed: "Couldn't start the voice call",
130
+ voiceControls: VOICE_CONTROLS_LABELS,
131
+ voiceEnded: (reason) => ENDED_ENGLISH[reason] ?? null,
132
+ voiceEndingSoon: 'One minute left in this call',
133
+ voiceBlocked: (reason) => BLOCKED_ENGLISH[reason],
88
134
  composerMessage: 'Message',
89
135
  composerAddMenu: 'Add to chat',
90
136
  composerModes: 'Power level',
@@ -114,13 +160,19 @@ export const DEFAULT_LABELS: AliaChatLabels = {
114
160
  readAloud: 'Read aloud',
115
161
  stopReading: 'Stop reading',
116
162
  planTitle: "Here's my plan",
117
- planApprove: 'Approve',
118
- planCancel: 'Cancel',
119
- planApproved: 'Plan approved',
120
- planCancelled: 'Plan cancelled',
121
163
  reasoning: 'Reasoning',
122
164
  thoughtFor: (seconds) => `Thought for ${seconds} seconds`,
123
- workLog: englishWorkLog,
165
+ t: englishCopy,
166
+ locale: 'en',
167
+ newChat: 'New chat',
168
+ sendFailed: "Your message couldn't be sent",
169
+ signInRequired: 'Sign in to keep chatting',
170
+ conversationSaveFailed: 'Failed to save conversation',
171
+ conversationDeleteFailed: 'Failed to delete conversation',
172
+ conversationCreateFailed: 'Failed to create conversation',
173
+ conversationClearFailed: "The conversation couldn't be cleared",
174
+ attachmentNotSent: (name) => `${name} wasn't sent — only photos can be sent`,
175
+ attachmentUnreadable: (name) => `${name} couldn't be read and wasn't sent`,
124
176
  };
125
177
 
126
178
  const LabelsContext = createContext<AliaChatLabels>(DEFAULT_LABELS);