@alia.onl/sdk 15.7.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 (150) hide show
  1. package/CHANGELOG.md +255 -12
  2. package/README.md +241 -155
  3. package/package.json +17 -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/csp.ts +50 -0
  43. package/src/hooks/useAliaActivity.ts +3 -3
  44. package/src/hooks/useAtBottom.ts +51 -0
  45. package/src/hooks/useAttachMenu.ts +94 -0
  46. package/src/hooks/useChatComposer.ts +299 -0
  47. package/src/hooks/useChatVoice.ts +129 -0
  48. package/src/hooks/useDictation.ts +2 -3
  49. package/src/hooks/useDocumentPicker.ts +3 -29
  50. package/src/hooks/useFailedTurnToast.ts +43 -0
  51. package/src/hooks/useMessageVote.ts +50 -0
  52. package/src/hooks/useOpenThought.ts +79 -0
  53. package/src/hooks/usePendingQuestions.ts +2 -2
  54. package/src/hooks/useReadAloud.ts +92 -0
  55. package/src/hooks/useShareHandler.ts +24 -0
  56. package/src/hooks/useSoundEffects.ts +1 -1
  57. package/src/hooks/useTTS.ts +2 -3
  58. package/src/hooks/useThoughtScope.ts +59 -0
  59. package/src/hooks/useThreadRows.ts +145 -0
  60. package/src/hooks/useThreadView.ts +68 -0
  61. package/src/hooks/useTimelineWindow.ts +256 -0
  62. package/src/hooks/useVoiceLauncher.ts +180 -0
  63. package/src/hooks/useVoiceRoom.ts +2 -6
  64. package/src/index.ts +43 -29
  65. package/src/lib/activity-store.ts +1 -1
  66. package/src/lib/add-menu.ts +7 -0
  67. package/src/lib/call-thread.ts +33 -0
  68. package/src/lib/character-runtime.ts +6 -4
  69. package/src/lib/citations.ts +79 -2
  70. package/src/lib/clipboard.ts +23 -0
  71. package/src/lib/content-utils.ts +10 -0
  72. package/src/lib/copy-en.ts +284 -0
  73. package/src/lib/copy.ts +32 -0
  74. package/src/lib/document-picker.ts +31 -0
  75. package/src/lib/flights.ts +22 -13
  76. package/src/lib/focus-return.ts +66 -0
  77. package/src/lib/keyboard.native.tsx +19 -0
  78. package/src/lib/keyboard.tsx +9 -0
  79. package/src/lib/labels.tsx +69 -17
  80. package/src/lib/origins.ts +15 -0
  81. package/src/lib/participants.ts +86 -0
  82. package/src/lib/power-level-options.ts +66 -0
  83. package/src/lib/questions.ts +2 -2
  84. package/src/lib/task-labels.ts +4 -4
  85. package/src/lib/tool-call-status.ts +1 -1
  86. package/src/lib/tool-icons.ts +36 -0
  87. package/src/lib/tool-renderers.tsx +48 -0
  88. package/src/lib/turn-state.ts +2 -2
  89. package/src/lib/voice-audio-engine.ts +14 -158
  90. package/src/lib/voice-call-request.ts +49 -0
  91. package/src/lib/voice-call.ts +76 -34
  92. package/src/lib/voice-controls-labels.ts +31 -0
  93. package/src/lib/voice-protocol.ts +32 -14
  94. package/src/lib/voice-worklets.ts +19 -0
  95. package/src/lib/work-log.ts +6 -6
  96. package/src/runtime/agents.ts +151 -0
  97. package/src/runtime/api.ts +123 -0
  98. package/src/runtime/attachment-utils.ts +96 -0
  99. package/src/runtime/composer-draft-store.ts +270 -0
  100. package/src/runtime/config.tsx +186 -0
  101. package/src/runtime/conversation-store.ts +49 -0
  102. package/src/runtime/conversations.ts +557 -0
  103. package/src/runtime/error-utils.ts +104 -0
  104. package/src/runtime/host-tools.ts +140 -0
  105. package/src/runtime/index.ts +113 -0
  106. package/src/runtime/message-history.ts +124 -0
  107. package/src/runtime/model.ts +39 -0
  108. package/src/runtime/oxy-files.ts +37 -0
  109. package/src/runtime/power-levels.ts +66 -0
  110. package/src/runtime/query-keys.ts +34 -0
  111. package/src/runtime/random-uuid.ts +25 -0
  112. package/src/runtime/routes.ts +51 -0
  113. package/src/runtime/sharing.ts +165 -0
  114. package/src/runtime/sse-frame-reader.ts +88 -0
  115. package/src/runtime/stream-events.ts +146 -0
  116. package/src/runtime/stream-outcome.ts +21 -0
  117. package/src/runtime/thought-store.ts +103 -0
  118. package/src/runtime/thread/agent-activity.ts +261 -0
  119. package/src/runtime/thread/automation-labels.ts +79 -0
  120. package/src/runtime/thread/conversation-export.ts +135 -0
  121. package/src/runtime/thread/message-days.ts +120 -0
  122. package/src/runtime/thread/message-processor.ts +93 -0
  123. package/src/runtime/thread/task-utils.ts +11 -0
  124. package/src/runtime/thread/thought-utils.ts +752 -0
  125. package/src/runtime/thread/thread-history.ts +139 -0
  126. package/src/runtime/thread/timeline.ts +202 -0
  127. package/src/runtime/thread/title-tags.ts +8 -0
  128. package/src/runtime/thread/tool-cards.ts +383 -0
  129. package/src/runtime/turn-failure.ts +108 -0
  130. package/src/runtime/types.ts +148 -0
  131. package/src/runtime/usage-limit-error.ts +44 -0
  132. package/src/runtime/use-agent-activity.ts +80 -0
  133. package/src/runtime/use-conversation-stream.ts +1427 -0
  134. package/src/runtime/use-conversation-thread.ts +556 -0
  135. package/src/runtime/use-start-conversation.ts +107 -0
  136. package/src/runtime/use-thread-history.ts +64 -0
  137. package/src/runtime/use-thread-search.ts +95 -0
  138. package/src/runtime/use-turn-edit.ts +99 -0
  139. package/src/tools.ts +2 -2
  140. package/src/types.ts +18 -47
  141. package/worklets/voice-processors.js +152 -0
  142. package/src/components/AliaChatMessageList.tsx +0 -532
  143. package/src/components/ThinkingBlock.tsx +0 -43
  144. package/src/hooks/useAliaChat.ts +0 -573
  145. package/src/lib/api-url.ts +0 -2
  146. package/src/lib/catalogue.ts +0 -236
  147. package/src/lib/chat-stream.ts +0 -655
  148. package/src/lib/chat-transport.ts +0 -98
  149. package/src/lib/delegation.ts +0 -34
  150. package/src/lib/work-log-copy.ts +0 -61
@@ -0,0 +1,202 @@
1
+ /**
2
+ * The conversation's participants: Bloom's `AgentProfile` with `[lead, ...joined]`, and the
3
+ * choreography by which a consulted agent joins it (ADR 0020 §7).
4
+ *
5
+ * When the lead consults another agent (`alia.agent_start`), the agent's reply appears in the
6
+ * thread under its own identity (anchor `reply:<agentId>`), and that avatar flies into the header
7
+ * — Bloom's multi-agent join: `centeredLanding`, merging into the profile, staggered when several
8
+ * join at once. The header gains the agent only when its flight lands; with reduced motion, or
9
+ * when there is nothing to measure, it gains it at once. An agent already in the conversation
10
+ * when the chat opens is simply there.
11
+ *
12
+ * This is the ONLY file that draws `AgentProfile` or builds a join flight
13
+ * (`one-thread.test.tsx` holds it), so the header of the sheet, the screen and the Alia app are
14
+ * one header.
15
+ */
16
+
17
+ import { AgentProfile } from '@oxy.so/bloom/agent-reply';
18
+ import { type AvatarFlights, centeredLanding } from '@oxy.so/bloom/avatar-flight';
19
+ import { memo, useEffect, useMemo, useRef, useState } from 'react';
20
+ import { ANCHORS, useAliaFlights } from '../lib/flights';
21
+ import {
22
+ type AliaChatParticipants,
23
+ type AliaLead,
24
+ type AliaParticipant,
25
+ consultedAgents,
26
+ } from '../lib/participants';
27
+ import type { Message } from '../runtime/types';
28
+
29
+ /** As Bloom's multi-agent chat flies a waiting agent into its profile. */
30
+ const JOIN_DURATION = 950;
31
+ const JOIN_STAGGER = 120;
32
+ const JOIN_LANDING_SIZE = 14;
33
+
34
+ /** The flights of agents joining the header: from each one's reply, merging into the profile. */
35
+ export function joinFlights(agents: readonly AliaParticipant[]) {
36
+ return agents.map((agent, index) => ({
37
+ key: `join:${agent.id}`,
38
+ character: agent.character,
39
+ from: ANCHORS.reply(agent.id),
40
+ to: ANCHORS.profile,
41
+ landing: centeredLanding(JOIN_LANDING_SIZE),
42
+ duration: JOIN_DURATION,
43
+ delay: index * JOIN_STAGGER,
44
+ merge: true,
45
+ }));
46
+ }
47
+
48
+ /** Two frames: the reply that carries the avatar mounts, then lays out, before it is measured. */
49
+ function afterLayout(run: () => void): () => void {
50
+ const raf =
51
+ typeof requestAnimationFrame === 'function'
52
+ ? requestAnimationFrame
53
+ : (callback: () => void) => setTimeout(callback, 16) as unknown as number;
54
+ const cancelRaf =
55
+ typeof cancelAnimationFrame === 'function'
56
+ ? cancelAnimationFrame
57
+ : (id: number) => clearTimeout(id as unknown as ReturnType<typeof setTimeout>);
58
+ let second: number | undefined;
59
+ const first = raf(() => {
60
+ second = raf(run);
61
+ });
62
+ return () => {
63
+ cancelRaf(first);
64
+ if (second !== undefined) cancelRaf(second);
65
+ };
66
+ }
67
+
68
+ export interface UseParticipantsOptions {
69
+ lead: AliaLead;
70
+ messages: readonly Message[];
71
+ /** The chat's flights; `null` draws everybody in place without flying. */
72
+ flights: AvatarFlights | null;
73
+ }
74
+
75
+ /**
76
+ * Who the header shows, and the joins under way.
77
+ *
78
+ * One batch flies at a time: agents that start answering while a batch (or the lead's own
79
+ * welcome flight) is in the air wait for it to land, then fly together, staggered.
80
+ */
81
+ export function useParticipants({
82
+ lead,
83
+ messages,
84
+ flights,
85
+ }: UseParticipantsOptions): AliaChatParticipants {
86
+ const consulted = useMemo(() => consultedAgents(messages, lead.id), [messages, lead.id]);
87
+ const [joined, setJoined] = useState<AliaParticipant[]>([]);
88
+ /** Agents waiting for a flight, and the batch in the air (`launched` once it took off). */
89
+ const queue = useRef<AliaParticipant[]>([]);
90
+ const [batch, setBatch] = useState<{ agents: AliaParticipant[]; launched: boolean } | null>(null);
91
+ const [queued, setQueued] = useState(0);
92
+ const flightsRef = useRef(flights);
93
+ flightsRef.current = flights;
94
+
95
+ // A new lead is a new conversation: nobody has joined it.
96
+ // biome-ignore lint/correctness/useExhaustiveDependencies: reset on the lead alone
97
+ useEffect(() => {
98
+ queue.current = [];
99
+ setJoined([]);
100
+ setBatch(null);
101
+ }, [lead.id]);
102
+
103
+ // Everybody who answered is in the header, or on the way to it.
104
+ useEffect(() => {
105
+ const known = new Set([
106
+ ...joined.map((agent) => agent.id),
107
+ ...queue.current.map((agent) => agent.id),
108
+ ...(batch?.agents.map((agent) => agent.id) ?? []),
109
+ ]);
110
+ const already: AliaParticipant[] = [];
111
+ let waiting = false;
112
+ for (const { participant, live } of consulted) {
113
+ if (known.has(participant.id)) continue;
114
+ if (live && flightsRef.current !== null) {
115
+ queue.current = [...queue.current, participant];
116
+ waiting = true;
117
+ } else {
118
+ already.push(participant);
119
+ }
120
+ }
121
+ if (already.length > 0) setJoined((current) => [...current, ...already]);
122
+ if (waiting) setQueued((count) => count + 1);
123
+ }, [consulted, joined, batch]);
124
+
125
+ // A batch takes off once nothing else is landing on the header.
126
+ const landing = flights?.isLanding(ANCHORS.profile) ?? false;
127
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `queued` signals the queue (a ref) grew
128
+ useEffect(() => {
129
+ if (batch !== null || landing || queue.current.length === 0) return;
130
+ const agents = queue.current;
131
+ queue.current = [];
132
+ setBatch({ agents, launched: false });
133
+ }, [batch, landing, queued]);
134
+
135
+ useEffect(() => {
136
+ if (batch === null || batch.launched) return;
137
+ let live = true;
138
+ const cancel = afterLayout(() => {
139
+ const current = flightsRef.current;
140
+ void (current ? current.launch(joinFlights(batch.agents)) : Promise.resolve(false)).then(
141
+ (flying) => {
142
+ if (!live) return;
143
+ if (flying) setBatch({ agents: batch.agents, launched: true });
144
+ else {
145
+ // Reduced motion, nothing measurable, or superseded: they join without flying.
146
+ setJoined((current) => [...current, ...batch.agents]);
147
+ setBatch(null);
148
+ }
149
+ },
150
+ );
151
+ });
152
+ return () => {
153
+ live = false;
154
+ cancel();
155
+ };
156
+ }, [batch]);
157
+
158
+ // The batch is in: the header gains its agents as their avatars land.
159
+ useEffect(() => {
160
+ if (batch === null || !batch.launched || landing) return;
161
+ setJoined((current) => [...current, ...batch.agents]);
162
+ setBatch(null);
163
+ }, [batch, landing]);
164
+
165
+ return useMemo(() => ({ lead, joined }), [lead, joined]);
166
+ }
167
+
168
+ export interface AliaParticipantsProps {
169
+ participants: AliaChatParticipants;
170
+ /** Pressing the profile. Omitted, every avatar in it waves. */
171
+ onPress?: () => void;
172
+ }
173
+
174
+ const noop = () => undefined;
175
+
176
+ /**
177
+ * The header's participants: the lead's avatar above its name, joined by the agents it consulted
178
+ * ("Alia + Sindi"). Place it in the chat's chrome; it is the conversation's title.
179
+ */
180
+ export const AliaParticipants = memo(function AliaParticipants({
181
+ participants,
182
+ onPress,
183
+ }: AliaParticipantsProps) {
184
+ const flights = useAliaFlights();
185
+ const agents = useMemo(
186
+ () =>
187
+ [participants.lead, ...participants.joined].map((agent) => ({
188
+ id: agent.id,
189
+ name: agent.name,
190
+ avatar: agent.character,
191
+ })),
192
+ [participants],
193
+ );
194
+ return (
195
+ <AgentProfile
196
+ agents={agents}
197
+ onPress={onPress ?? noop}
198
+ avatarRef={flights?.anchor(ANCHORS.profile)}
199
+ avatarHidden={flights?.isLanding(ANCHORS.profile)}
200
+ />
201
+ );
202
+ });
@@ -0,0 +1,60 @@
1
+ import { useAiChatShell } from '@oxy.so/bloom/ai-chat';
2
+ import { Button } from '@oxy.so/bloom/button';
3
+ import { RiMenuLine } from '@oxy.so/bloom/icons/RiMenuLine';
4
+ import { memo } from 'react';
5
+ import { View } from 'react-native';
6
+ import type { AliaChatParticipants } from '../lib/participants';
7
+ import { AliaParticipants } from './AliaParticipants';
8
+
9
+ export interface AliaParticipantsHeaderProps {
10
+ participants: AliaChatParticipants;
11
+ /** Pressing the participants. Omitted, every avatar in it waves. */
12
+ onPress?: () => void;
13
+ }
14
+
15
+ /**
16
+ * The conversation's header inside Bloom's AI chat shell: the participants as its title, between
17
+ * the shell's own buttons — the navigation drawer's on a narrow screen and the panel's below `xl`,
18
+ * as `AiChatMobileHeader` places them. That header's title is a string; this one is the
19
+ * conversation's participants, so a host puts this in `chrome.header` instead of drawing a title
20
+ * of its own. Outside a shell it is the participants alone.
21
+ */
22
+ export const AliaParticipantsHeader = memo(function AliaParticipantsHeader({
23
+ participants,
24
+ onPress,
25
+ }: AliaParticipantsHeaderProps) {
26
+ const shell = useAiChatShell();
27
+ const nav = shell?.compact === true && shell.navCollapsed && shell.hasNav;
28
+ const panel = shell?.compact === true && shell.hasPanel;
29
+ return (
30
+ <View className="min-h-12 shrink-0 flex-row items-center justify-between px-3 pt-3">
31
+ <View className="w-10">
32
+ {nav && shell ? (
33
+ <Button
34
+ size="md"
35
+ icon={RiMenuLine}
36
+ accessibilityLabel={shell.labels.openNavigation}
37
+ onPress={shell.openNav}
38
+ appearance="plain"
39
+ tone="neutral"
40
+ />
41
+ ) : null}
42
+ </View>
43
+ <View className="min-w-0 shrink items-center">
44
+ <AliaParticipants participants={participants} onPress={onPress} />
45
+ </View>
46
+ <View className="w-10 items-end">
47
+ {panel && shell ? (
48
+ <Button
49
+ size="md"
50
+ icon={shell.panelIcon}
51
+ accessibilityLabel={shell.labels.openPanel(shell.panelLabel)}
52
+ onPress={shell.openPanel}
53
+ appearance="plain"
54
+ tone="neutral"
55
+ />
56
+ ) : null}
57
+ </View>
58
+ </View>
59
+ );
60
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * A conversation somebody shared (`GET /v1/shared/:token`, `docs/agent-embedding.md` §6), read
3
+ * by anybody with the link, signed in or not. It is the SDK's own thread (`AliaThread`), read-only:
4
+ * words, pictures and who wrote them — the server never sends a tool's output, which can hold the
5
+ * person's own data — and nothing to vote on, play, edit or regenerate.
6
+ */
7
+
8
+ import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
9
+ import type { AiChatThreadHandle } from '@oxy.so/bloom/ai-chat';
10
+ import { Loading } from '@oxy.so/bloom/loading';
11
+ import { Text } from '@oxy.so/bloom/typography';
12
+ import { useMemo, useRef } from 'react';
13
+ import { View } from 'react-native';
14
+ import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
15
+ import { type AliaChatLabels, AliaChatLabelsProvider, useLabels } from '../lib/labels';
16
+ import { AliaRuntimeProvider } from '../runtime/config';
17
+ import {
18
+ SharedConversationGoneError,
19
+ sharedMessages,
20
+ useSharedConversation,
21
+ } from '../runtime/sharing';
22
+ import { AliaThread } from './thread/AliaThread';
23
+
24
+ export interface AliaSharedConversationProps {
25
+ /** The share link's token. */
26
+ token: string;
27
+ /** Alia's API origin; omitted, production. */
28
+ apiUrl?: string;
29
+ /** Overrides for the chat's own copy; omitted, English. */
30
+ labels?: Partial<AliaChatLabels>;
31
+ /** Where the 3D avatar's runtime is served from; omitted, Alia's shared copy. */
32
+ characterRuntimeUrl?: string;
33
+ }
34
+
35
+ function SharedThread({ token }: { token: string }) {
36
+ const { t } = useLabels();
37
+ const { data, error, isLoading } = useSharedConversation(token);
38
+ const threadRef = useRef<AiChatThreadHandle | null>(null);
39
+ const messages = useMemo(() => (data ? sharedMessages(data) : []), [data]);
40
+
41
+ if (isLoading) {
42
+ return (
43
+ <View className="flex-1 items-center justify-center">
44
+ <Loading />
45
+ </View>
46
+ );
47
+ }
48
+ if (error || !data) {
49
+ return (
50
+ <View className="flex-1 items-center justify-center px-6">
51
+ <Text className="text-center text-base text-muted-foreground">
52
+ {error instanceof SharedConversationGoneError
53
+ ? t('chat.sharedGone')
54
+ : t('chat.sharedFailed')}
55
+ </Text>
56
+ </View>
57
+ );
58
+ }
59
+ return (
60
+ <View className="flex-1">
61
+ {data.title ? (
62
+ <Text className="px-4 pt-4 text-lg font-semibold text-foreground" numberOfLines={2}>
63
+ {data.title}
64
+ </Text>
65
+ ) : null}
66
+ <AliaThread messages={messages} isLoading={false} threadRef={threadRef} readOnly />
67
+ </View>
68
+ );
69
+ }
70
+
71
+ export function AliaSharedConversation({
72
+ token,
73
+ apiUrl,
74
+ labels,
75
+ characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
76
+ }: AliaSharedConversationProps) {
77
+ return (
78
+ <AgentAvatarProvider runtimeUrl={characterRuntimeUrl}>
79
+ <AliaChatLabelsProvider labels={labels}>
80
+ <AliaRuntimeProvider apiUrl={apiUrl}>
81
+ <SharedThread token={token} />
82
+ </AliaRuntimeProvider>
83
+ </AliaChatLabelsProvider>
84
+ </AgentAvatarProvider>
85
+ );
86
+ }
@@ -1,8 +1,10 @@
1
+ import { AgentAvatar } from '@oxy.so/bloom/agent-avatar';
1
2
  import { AvatarAnchor } from '@oxy.so/bloom/avatar-flight';
2
3
  import { Text } from '@oxy.so/bloom/typography';
3
4
  import { View } from 'react-native';
4
- import { ALIA_ANCHORS, useAliaFlights } from '../lib/flights';
5
+ import { ANCHORS, useAliaFlights } from '../lib/flights';
5
6
  import { greetingLines } from '../lib/greeting';
7
+ import { ALIA_LEAD, type AliaLead } from '../lib/participants';
6
8
  import { AliaAvatar } from './AliaAvatar';
7
9
 
8
10
  export interface AliaWelcomeMessageProps {
@@ -10,24 +12,35 @@ export interface AliaWelcomeMessageProps {
10
12
  greeting: string;
11
13
  subtitle?: string;
12
14
  markSize?: number;
15
+ /** Who greets: the agent the chat was opened on. Default Alia. */
16
+ lead?: AliaLead;
13
17
  }
14
18
 
15
19
  /**
16
- * The empty chat's greeting: the avatar above it, both centred, the address on top ("Hi Nate,")
20
+ * The empty chat's greeting: the lead's avatar above it, both centred, the address on top ("Hi Nate,")
17
21
  * and the rest under it (`greetingLines`). The Alia app and the SDK chat
18
22
  * draw exactly this. The greeting has a max width (`max-w-xs`, 20rem) so it sets in two lines at
19
23
  * any width, as a headline under a mark rather than one long line across a wide column. The
20
24
  * suggestions are not part of it; they sit over the composer (`ComposerSuggestions`).
21
25
  */
22
- export function AliaWelcomeMessage({ greeting, subtitle, markSize = 88 }: AliaWelcomeMessageProps) {
26
+ export function AliaWelcomeMessage({
27
+ greeting,
28
+ subtitle,
29
+ markSize = 88,
30
+ lead = ALIA_LEAD,
31
+ }: AliaWelcomeMessageProps) {
23
32
  const flights = useAliaFlights();
24
- const avatar = <AliaAvatar size={markSize} interactive />;
33
+ const avatar = lead.isAlia ? (
34
+ <AliaAvatar size={markSize} interactive />
35
+ ) : (
36
+ <AgentAvatar character={lead.character} size={markSize} label={lead.name} interactive />
37
+ );
25
38
  return (
26
39
  <View className="items-center gap-2">
27
40
  <View className="items-center justify-center gap-4">
28
41
  {/* It flies up into the header's profile when the conversation begins. */}
29
42
  {flights ? (
30
- <AvatarAnchor flights={flights} anchor={ALIA_ANCHORS.welcome}>
43
+ <AvatarAnchor flights={flights} anchor={ANCHORS.welcome}>
31
44
  {avatar}
32
45
  </AvatarAnchor>
33
46
  ) : (
@@ -0,0 +1,12 @@
1
+ import { Image as ExpoImage, type ImageProps } from 'expo-image';
2
+ import type { ComponentType } from 'react';
3
+ import { styled } from 'react-native-css';
4
+
5
+ /**
6
+ * `expo-image` with `className`. On web the class reaches the DOM either way; on native
7
+ * react-native-css maps classes to styles only for react-native's own primitives, so anything
8
+ * else drops them silently unless it is wrapped once, here, at module scope.
9
+ */
10
+ export const Image: ComponentType<ImageProps & { className?: string }> = styled(ExpoImage, {
11
+ className: 'style',
12
+ });