@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.
- package/CHANGELOG.md +242 -13
- package/README.md +223 -151
- package/package.json +15 -5
- package/src/animations.ts +0 -1
- package/src/components/AliaAskProvider.tsx +127 -0
- package/src/components/AliaChatContent.tsx +604 -392
- package/src/components/AliaChatHeader.tsx +14 -38
- package/src/components/AliaChatScreen.tsx +56 -31
- package/src/components/AliaChatSheet.tsx +110 -36
- package/src/components/AliaComposer.tsx +1 -1
- package/src/components/AliaParticipants.tsx +202 -0
- package/src/components/AliaParticipantsHeader.tsx +60 -0
- package/src/components/AliaSharedConversation.tsx +86 -0
- package/src/components/AliaWelcomeMessage.tsx +18 -5
- package/src/components/Image.tsx +12 -0
- package/src/components/Markdown.tsx +498 -14
- package/src/components/PlanPreviewCard.tsx +7 -49
- package/src/components/ReplyMarkdown.tsx +359 -0
- package/src/components/RichBlocks.tsx +195 -0
- package/src/components/canvas/CanvasComponent.tsx +59 -0
- package/src/components/canvas/ChartRenderer.tsx +73 -0
- package/src/components/canvas/CodeRenderer.tsx +24 -0
- package/src/components/canvas/FormRenderer.tsx +101 -0
- package/src/components/canvas/MarkdownRenderer.tsx +14 -0
- package/src/components/canvas/TableRenderer.tsx +47 -0
- package/src/components/thought/FilesAndSources.tsx +120 -0
- package/src/components/thought/ThoughtPanel.tsx +417 -0
- package/src/components/thought/ToolStep.tsx +141 -0
- package/src/components/thread/AgentResultCard.tsx +125 -0
- package/src/components/thread/AgentTaskCard.tsx +127 -0
- package/src/components/thread/AliaThread.tsx +401 -0
- package/src/components/thread/MessageBlockBoundary.tsx +78 -0
- package/src/components/thread/NewConversationOffer.tsx +54 -0
- package/src/components/thread/ThreadRow.tsx +458 -0
- package/src/components/thread/ThreadSearch.tsx +129 -0
- package/src/components/thread/ToolResultCard.tsx +254 -0
- package/src/components/thread/TurnMenu.tsx +54 -0
- package/src/components/thread/TurnStatusLine.tsx +34 -0
- package/src/components/voice/AliaVoiceCall.tsx +21 -0
- package/src/components/voice/VoiceControls.tsx +2 -28
- package/src/components/voice/VoiceSession.tsx +39 -11
- package/src/hooks/useAliaActivity.ts +2 -2
- package/src/hooks/useAtBottom.ts +51 -0
- package/src/hooks/useAttachMenu.ts +94 -0
- package/src/hooks/useChatComposer.ts +299 -0
- package/src/hooks/useChatVoice.ts +129 -0
- package/src/hooks/useDocumentPicker.ts +3 -29
- package/src/hooks/useFailedTurnToast.ts +43 -0
- package/src/hooks/useMessageVote.ts +50 -0
- package/src/hooks/useOpenThought.ts +79 -0
- package/src/hooks/usePendingQuestions.ts +2 -2
- package/src/hooks/useReadAloud.ts +92 -0
- package/src/hooks/useShareHandler.ts +24 -0
- package/src/hooks/useSoundEffects.ts +1 -1
- package/src/hooks/useThoughtScope.ts +59 -0
- package/src/hooks/useThreadRows.ts +145 -0
- package/src/hooks/useThreadView.ts +68 -0
- package/src/hooks/useTimelineWindow.ts +256 -0
- package/src/hooks/useVoiceLauncher.ts +180 -0
- package/src/hooks/useVoiceRoom.ts +0 -3
- package/src/index.ts +43 -29
- package/src/lib/activity-store.ts +1 -1
- package/src/lib/add-menu.ts +7 -0
- package/src/lib/call-thread.ts +33 -0
- package/src/lib/citations.ts +79 -2
- package/src/lib/clipboard.ts +23 -0
- package/src/lib/content-utils.ts +10 -0
- package/src/lib/copy-en.ts +284 -0
- package/src/lib/copy.ts +32 -0
- package/src/lib/document-picker.ts +31 -0
- package/src/lib/flights.ts +22 -13
- package/src/lib/focus-return.ts +66 -0
- package/src/lib/keyboard.native.tsx +19 -0
- package/src/lib/keyboard.tsx +9 -0
- package/src/lib/labels.tsx +69 -17
- package/src/lib/participants.ts +86 -0
- package/src/lib/power-level-options.ts +66 -0
- package/src/lib/questions.ts +2 -2
- package/src/lib/task-labels.ts +4 -4
- package/src/lib/tool-call-status.ts +1 -1
- package/src/lib/tool-icons.ts +36 -0
- package/src/lib/tool-renderers.tsx +48 -0
- package/src/lib/turn-state.ts +2 -2
- package/src/lib/voice-call-request.ts +49 -0
- package/src/lib/voice-call.ts +76 -34
- package/src/lib/voice-controls-labels.ts +31 -0
- package/src/lib/voice-protocol.ts +32 -14
- package/src/lib/work-log.ts +6 -6
- package/src/runtime/agents.ts +151 -0
- package/src/runtime/api.ts +123 -0
- package/src/runtime/attachment-utils.ts +96 -0
- package/src/runtime/composer-draft-store.ts +270 -0
- package/src/runtime/config.tsx +186 -0
- package/src/runtime/conversation-store.ts +49 -0
- package/src/runtime/conversations.ts +557 -0
- package/src/runtime/error-utils.ts +104 -0
- package/src/runtime/host-tools.ts +140 -0
- package/src/runtime/index.ts +113 -0
- package/src/runtime/message-history.ts +124 -0
- package/src/runtime/model.ts +39 -0
- package/src/runtime/oxy-files.ts +37 -0
- package/src/runtime/power-levels.ts +66 -0
- package/src/runtime/query-keys.ts +34 -0
- package/src/runtime/random-uuid.ts +25 -0
- package/src/runtime/routes.ts +51 -0
- package/src/runtime/sharing.ts +165 -0
- package/src/runtime/sse-frame-reader.ts +88 -0
- package/src/runtime/stream-events.ts +146 -0
- package/src/runtime/stream-outcome.ts +21 -0
- package/src/runtime/thought-store.ts +103 -0
- package/src/runtime/thread/agent-activity.ts +261 -0
- package/src/runtime/thread/automation-labels.ts +79 -0
- package/src/runtime/thread/conversation-export.ts +135 -0
- package/src/runtime/thread/message-days.ts +120 -0
- package/src/runtime/thread/message-processor.ts +93 -0
- package/src/runtime/thread/task-utils.ts +11 -0
- package/src/runtime/thread/thought-utils.ts +752 -0
- package/src/runtime/thread/thread-history.ts +139 -0
- package/src/runtime/thread/timeline.ts +202 -0
- package/src/runtime/thread/title-tags.ts +8 -0
- package/src/runtime/thread/tool-cards.ts +383 -0
- package/src/runtime/turn-failure.ts +108 -0
- package/src/runtime/types.ts +148 -0
- package/src/runtime/usage-limit-error.ts +44 -0
- package/src/runtime/use-agent-activity.ts +80 -0
- package/src/runtime/use-conversation-stream.ts +1427 -0
- package/src/runtime/use-conversation-thread.ts +556 -0
- package/src/runtime/use-start-conversation.ts +107 -0
- package/src/runtime/use-thread-history.ts +64 -0
- package/src/runtime/use-thread-search.ts +95 -0
- package/src/runtime/use-turn-edit.ts +99 -0
- package/src/tools.ts +2 -2
- package/src/types.ts +18 -47
- package/src/components/AliaChatMessageList.tsx +0 -532
- package/src/components/ThinkingBlock.tsx +0 -43
- package/src/hooks/useAliaChat.ts +0 -573
- package/src/lib/catalogue.ts +0 -236
- package/src/lib/chat-stream.ts +0 -655
- package/src/lib/chat-transport.ts +0 -98
- package/src/lib/delegation.ts +0 -34
- 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 {
|
|
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({
|
|
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 =
|
|
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={
|
|
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
|
+
});
|