@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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host side of a voice call, shared by every surface that offers one.
|
|
3
|
+
*
|
|
4
|
+
* It owns whether a call is open, the state it reports back (for the ambient
|
|
5
|
+
* wave, the sound effects and the transcript), the gate before one opens (plan,
|
|
6
|
+
* credits, sign-in: the host answers, the SDK tells the person), and the end of
|
|
7
|
+
* a call that is no longer the screen's — another screen on show, another
|
|
8
|
+
* conversation, another account. `sessionProps` is everything `VoiceSession`
|
|
9
|
+
* needs; the sheet, `AliaChatScreen` and the Alia app all spread it.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here imports the call or its audio engine.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
|
+
import { toast } from '@oxy.so/bloom/toast';
|
|
16
|
+
import { useLabels, type VoiceBlockReason } from '../lib/labels';
|
|
17
|
+
import { isRequestFor, useVoiceCallRequests } from '../lib/voice-call-request';
|
|
18
|
+
import type { VoiceNotice, VoiceSessionProps, VoiceSessionState } from '../types';
|
|
19
|
+
|
|
20
|
+
export interface UseVoiceLauncherOptions {
|
|
21
|
+
/** Alia API base URL. */
|
|
22
|
+
apiUrl?: string;
|
|
23
|
+
/** The conversation open on this screen, which is where the call lives. */
|
|
24
|
+
conversationId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Called after the gate, when no conversation is open: creates (and shows) the one the call
|
|
27
|
+
* will live in and returns its id, or `undefined` when it could not be made (the call then
|
|
28
|
+
* does not open). Every call has a conversation, so its transcript has a home. The call opens
|
|
29
|
+
* on the screen that shows that conversation: this one, or the one the host navigates to.
|
|
30
|
+
*/
|
|
31
|
+
resolveConversation?: () => Promise<string | undefined>;
|
|
32
|
+
/** Whose call it would be. A live call ends when the account, or the open conversation, changes. */
|
|
33
|
+
accountId?: string | null;
|
|
34
|
+
/** The agent the call talks to; omitted, Alia. */
|
|
35
|
+
agentId?: string;
|
|
36
|
+
/** Language spoken (BCP-47); omitted, the platform's. */
|
|
37
|
+
locale?: string;
|
|
38
|
+
/** Which of the product's two voices answers. */
|
|
39
|
+
voice?: 'male' | 'female';
|
|
40
|
+
/** Alia stored messages of the call in the conversation: refresh the thread. */
|
|
41
|
+
onMessagesSaved?: VoiceSessionProps['onMessagesSaved'];
|
|
42
|
+
/**
|
|
43
|
+
* The gate before a call opens: why it may not (no account, no plan, no credits), or `null`.
|
|
44
|
+
* The person is told (`labels.voiceBlocked`); `onVoiceBlocked` adds what only the host can do.
|
|
45
|
+
*/
|
|
46
|
+
canStartVoice?: () => VoiceBlockReason | null;
|
|
47
|
+
/** The call was not opened for `reason`: sign the person in, open the plans… */
|
|
48
|
+
onVoiceBlocked?: (reason: VoiceBlockReason) => void;
|
|
49
|
+
/** Where the call's notices go; omitted, Bloom's toast. */
|
|
50
|
+
onNotice?: (notice: VoiceNotice) => void;
|
|
51
|
+
/** A call is over, however it ended: refresh credits, the thread… */
|
|
52
|
+
onCallEnded?: () => void;
|
|
53
|
+
/** Whether the screen holding the call is the one on show. A live call ends when it is not. */
|
|
54
|
+
isFocused?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface VoiceLauncher {
|
|
58
|
+
/** A call is open (or being opened). */
|
|
59
|
+
isActive: boolean;
|
|
60
|
+
/** What the call reports back; `null` until it is under way. */
|
|
61
|
+
state: VoiceSessionState | null;
|
|
62
|
+
/** Open a call, after the gate. Does nothing while one is open or the screen is not on show. */
|
|
63
|
+
start: () => Promise<void>;
|
|
64
|
+
/** End the call. */
|
|
65
|
+
end: () => void;
|
|
66
|
+
/** Spread onto `VoiceSession` (or `AliaVoiceCall`). */
|
|
67
|
+
sessionProps: VoiceSessionProps;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Bloom's toast, by kind: the default home of a call's notices. */
|
|
71
|
+
export function toastNotice({ kind, text }: VoiceNotice): void {
|
|
72
|
+
if (kind === 'error') toast.error(text);
|
|
73
|
+
else if (kind === 'warning') toast.warning(text);
|
|
74
|
+
else toast.info(text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useVoiceLauncher(options: UseVoiceLauncherOptions = {}): VoiceLauncher {
|
|
78
|
+
const labels = useLabels();
|
|
79
|
+
const {
|
|
80
|
+
apiUrl,
|
|
81
|
+
conversationId,
|
|
82
|
+
resolveConversation,
|
|
83
|
+
accountId = null,
|
|
84
|
+
agentId,
|
|
85
|
+
locale,
|
|
86
|
+
voice,
|
|
87
|
+
onMessagesSaved,
|
|
88
|
+
canStartVoice,
|
|
89
|
+
onVoiceBlocked,
|
|
90
|
+
onNotice,
|
|
91
|
+
onCallEnded,
|
|
92
|
+
isFocused = true,
|
|
93
|
+
} = options;
|
|
94
|
+
const [isActive, setIsActive] = useState(false);
|
|
95
|
+
const [state, setState] = useState<VoiceSessionState | null>(null);
|
|
96
|
+
const latest = useRef({
|
|
97
|
+
canStartVoice,
|
|
98
|
+
onVoiceBlocked,
|
|
99
|
+
onNotice,
|
|
100
|
+
onCallEnded,
|
|
101
|
+
resolveConversation,
|
|
102
|
+
labels,
|
|
103
|
+
});
|
|
104
|
+
latest.current = {
|
|
105
|
+
canStartVoice,
|
|
106
|
+
onVoiceBlocked,
|
|
107
|
+
onNotice,
|
|
108
|
+
onCallEnded,
|
|
109
|
+
resolveConversation,
|
|
110
|
+
labels,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const notify = useCallback((notice: VoiceNotice) => {
|
|
114
|
+
(latest.current.onNotice ?? toastNotice)(notice);
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
const end = useCallback(() => {
|
|
118
|
+
setIsActive(false);
|
|
119
|
+
setState(null);
|
|
120
|
+
latest.current.onCallEnded?.();
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const start = useCallback(async () => {
|
|
124
|
+
if (isActive || !isFocused) return;
|
|
125
|
+
const reason = latest.current.canStartVoice?.() ?? null;
|
|
126
|
+
if (reason !== null) {
|
|
127
|
+
notify({
|
|
128
|
+
kind: reason === 'credits-exhausted' ? 'error' : 'info',
|
|
129
|
+
text: latest.current.labels.voiceBlocked(reason),
|
|
130
|
+
});
|
|
131
|
+
latest.current.onVoiceBlocked?.(reason);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// The gate first: a call that may not open must not leave an empty conversation behind.
|
|
135
|
+
const home = conversationId ?? (await latest.current.resolveConversation?.());
|
|
136
|
+
if (home === undefined) return;
|
|
137
|
+
// The call opens where its conversation is shown (see `useVoiceCallRequests`): here, at once,
|
|
138
|
+
// when it already is; wherever the host takes the new conversation, when it was just made.
|
|
139
|
+
useVoiceCallRequests.getState().ask(home, accountId);
|
|
140
|
+
}, [isActive, isFocused, conversationId, accountId, notify]);
|
|
141
|
+
|
|
142
|
+
const request = useVoiceCallRequests((store) => store.request);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (isActive || !isFocused || !isRequestFor(request, conversationId, accountId)) return;
|
|
145
|
+
useVoiceCallRequests.getState().clear();
|
|
146
|
+
setIsActive(true);
|
|
147
|
+
}, [request, isActive, isFocused, conversationId, accountId]);
|
|
148
|
+
|
|
149
|
+
// A call belongs to one account and one conversation, on the screen that shows it: it ends when
|
|
150
|
+
// the screen goes out of focus, the account changes, or the screen moves to another conversation.
|
|
151
|
+
const openedIn = useRef<{ conversationId: string | undefined; accountId: string | null }>(
|
|
152
|
+
undefined,
|
|
153
|
+
);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!isActive) {
|
|
156
|
+
openedIn.current = undefined;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
openedIn.current ??= { conversationId, accountId };
|
|
160
|
+
const call = openedIn.current;
|
|
161
|
+
if (!isFocused || call.accountId !== accountId || call.conversationId !== conversationId) end();
|
|
162
|
+
}, [isActive, isFocused, accountId, conversationId, end]);
|
|
163
|
+
|
|
164
|
+
const sessionProps = useMemo<VoiceSessionProps>(
|
|
165
|
+
() => ({
|
|
166
|
+
...(apiUrl === undefined ? {} : { apiUrl }),
|
|
167
|
+
...(conversationId === undefined ? {} : { conversationId }),
|
|
168
|
+
...(agentId === undefined ? {} : { agentId }),
|
|
169
|
+
...(locale === undefined ? {} : { locale }),
|
|
170
|
+
...(voice === undefined ? {} : { voice }),
|
|
171
|
+
...(onMessagesSaved === undefined ? {} : { onMessagesSaved }),
|
|
172
|
+
onNotice: notify,
|
|
173
|
+
onStateChange: setState,
|
|
174
|
+
onEnd: end,
|
|
175
|
+
}),
|
|
176
|
+
[apiUrl, conversationId, agentId, locale, voice, onMessagesSaved, notify, end],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return { isActive, state, start, end, sessionProps };
|
|
180
|
+
}
|
|
@@ -43,8 +43,6 @@ export interface UseVoiceRoomOptions {
|
|
|
43
43
|
lang?: string;
|
|
44
44
|
/** Alia stored messages of the call: refresh the thread. */
|
|
45
45
|
onMessagesSaved?: (conversationId: string, messageIds: readonly string[]) => void;
|
|
46
|
-
/** A tool filed an action for the person's OK; its card is in the conversation. */
|
|
47
|
-
onApprovalRequested?: (toolName: string) => void;
|
|
48
46
|
/** Test seams: the device's audio and the socket. */
|
|
49
47
|
createAudioEngine?: () => VoiceAudioEngine;
|
|
50
48
|
openSocket?: VoiceSocketFactory;
|
|
@@ -133,7 +131,6 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
133
131
|
},
|
|
134
132
|
onMessagesSaved: (conversationId, ids) =>
|
|
135
133
|
optionsRef.current.onMessagesSaved?.(conversationId, ids),
|
|
136
|
-
onApprovalRequested: (toolName) => optionsRef.current.onApprovalRequested?.(toolName),
|
|
137
134
|
});
|
|
138
135
|
callRef.current = call;
|
|
139
136
|
await call.start();
|
package/src/index.ts
CHANGED
|
@@ -3,17 +3,39 @@
|
|
|
3
3
|
// Reusable chat UI, voice, and streaming components for Oxy apps
|
|
4
4
|
|
|
5
5
|
// ── Main components ──
|
|
6
|
-
export
|
|
6
|
+
export { AliaChatContent } from './components/AliaChatContent';
|
|
7
|
+
export type {
|
|
8
|
+
AliaChatAgentRef,
|
|
9
|
+
AliaChatChrome,
|
|
10
|
+
AliaChatContentProps,
|
|
11
|
+
AliaChatState,
|
|
12
|
+
} from './components/AliaChatContent';
|
|
13
|
+
export { AliaParticipants, joinFlights, useParticipants } from './components/AliaParticipants';
|
|
14
|
+
export type { AliaParticipantsProps, UseParticipantsOptions } from './components/AliaParticipants';
|
|
15
|
+
export { AliaParticipantsHeader } from './components/AliaParticipantsHeader';
|
|
16
|
+
export type { AliaParticipantsHeaderProps } from './components/AliaParticipantsHeader';
|
|
17
|
+
export { ALIA_LEAD, leadOf } from './lib/participants';
|
|
18
|
+
export type { AliaChatParticipants, AliaLead, AliaParticipant } from './lib/participants';
|
|
19
|
+
export { ANCHORS, leadToProfile } from './lib/flights';
|
|
20
|
+
export { parseCatalogueTool, toolRendererKey } from './lib/tool-renderers';
|
|
21
|
+
export type { AliaToolRenderers, AliaToolResult } from './lib/tool-renderers';
|
|
22
|
+
export { AliaAskProvider, useAskAgent } from './components/AliaAskProvider';
|
|
23
|
+
export type { AliaAsk, AliaAskAgents, AliaAskProviderProps } from './components/AliaAskProvider';
|
|
24
|
+
export { AliaSharedConversation } from './components/AliaSharedConversation';
|
|
25
|
+
export type { AliaSharedConversationProps } from './components/AliaSharedConversation';
|
|
26
|
+
export type { AliaAddMenu } from './lib/add-menu';
|
|
27
|
+
export type { AliaVoiceOptions } from './hooks/useChatVoice';
|
|
7
28
|
export { AliaChatSheet } from './components/AliaChatSheet';
|
|
8
29
|
export type { AliaChatSheetProps, AliaChatSheetRef } from './components/AliaChatSheet';
|
|
9
|
-
export type { AliaTurn } from './
|
|
30
|
+
export type { AliaTurn } from './hooks/useChatComposer';
|
|
10
31
|
export { AliaChatScreen } from './components/AliaChatScreen';
|
|
11
32
|
export type { AliaChatScreenProps } from './components/AliaChatScreen';
|
|
12
33
|
|
|
13
34
|
// ── Chat UI components ──
|
|
14
35
|
export { ALIA_CHARACTER_RUNTIME_URL } from './lib/character-runtime';
|
|
15
36
|
export { DEFAULT_LABELS } from './lib/labels';
|
|
16
|
-
export
|
|
37
|
+
export { AliaChatLabelsProvider, useLabels } from './lib/labels';
|
|
38
|
+
export type { AliaChatLabels, VoiceBlockReason } from './lib/labels';
|
|
17
39
|
export { VoiceModeIcon } from './components/VoiceModeIcon';
|
|
18
40
|
export { AmbientField, PARALLAX_DURATION, PARALLAX_EASE } from './components/AmbientField';
|
|
19
41
|
export type { AmbientFieldProps } from './components/AmbientField';
|
|
@@ -27,6 +49,16 @@ export { IdentityMark } from './components/IdentityMark';
|
|
|
27
49
|
export type { IdentityMarkProps, IdentityMarkState } from './components/IdentityMark';
|
|
28
50
|
export { AliaMarkdown } from './components/Markdown';
|
|
29
51
|
export type { AliaMarkdownProps, RenderCodeBlock } from './components/Markdown';
|
|
52
|
+
export { ReplyMarkdown } from './components/ReplyMarkdown';
|
|
53
|
+
export { AliaThread } from './components/thread/AliaThread';
|
|
54
|
+
export type { AliaThreadProps, NewConversationOfferProps } from './components/thread/AliaThread';
|
|
55
|
+
export { ThoughtPanel } from './components/thought/ThoughtPanel';
|
|
56
|
+
export type { ThoughtPanelProps } from './components/thought/ThoughtPanel';
|
|
57
|
+
export type { OutputActions } from './components/thought/FilesAndSources';
|
|
58
|
+
export { CanvasComponent } from './components/canvas/CanvasComponent';
|
|
59
|
+
export { restoreOpenerFocus } from './lib/focus-return';
|
|
60
|
+
export { buildPowerLevelOptions, POWER_LEVEL_ICONS } from './lib/power-level-options';
|
|
61
|
+
export type { DeviceModel } from './lib/power-level-options';
|
|
30
62
|
export { AliaWelcomeMessage } from './components/AliaWelcomeMessage';
|
|
31
63
|
export { PlanPreviewCard } from './components/PlanPreviewCard';
|
|
32
64
|
export { AskedQuestions, QuestionCard } from './components/QuestionCard';
|
|
@@ -49,11 +81,8 @@ export {
|
|
|
49
81
|
} from './lib/questions';
|
|
50
82
|
export type { AskedOption, AskedQuestion, QuestionAnswer } from './lib/questions';
|
|
51
83
|
|
|
52
|
-
// ──
|
|
53
|
-
export
|
|
54
|
-
export type { UseAliaChatOptions, UseAliaChatReturn } from './hooks/useAliaChat';
|
|
55
|
-
export { AliaChatStreamError } from './lib/chat-stream';
|
|
56
|
-
export type { AliaChatFailure } from './lib/chat-stream';
|
|
84
|
+
// ── Conversation runtime: the one data layer under the sheet, the screen and the Alia app ──
|
|
85
|
+
export * from './runtime';
|
|
57
86
|
|
|
58
87
|
// ── Read aloud and dictation (no call, no audio engine) ──
|
|
59
88
|
export { useTTS } from './hooks/useTTS';
|
|
@@ -67,36 +96,21 @@ export type { VoiceErrorCode } from './lib/speech-messages';
|
|
|
67
96
|
export { SpeechClipError } from './lib/speech-synthesis';
|
|
68
97
|
export type { SpeechClipErrorCode } from './lib/speech-synthesis';
|
|
69
98
|
|
|
70
|
-
// ── Model catalogue (GET /catalogue) ──
|
|
71
|
-
// Exported so a consumer building its own picker reads the real models the
|
|
72
|
-
// server offers; the SDK itself ships no model identifier.
|
|
73
|
-
export {
|
|
74
|
-
fetchCatalogue,
|
|
75
|
-
parseCatalogue,
|
|
76
|
-
resolveSelection,
|
|
77
|
-
resolveModelId,
|
|
78
|
-
clearCatalogueCache,
|
|
79
|
-
} from './lib/catalogue';
|
|
80
|
-
export type { Catalogue, CatalogueEntry, ModelSelection, ReasoningEffort } from './lib/catalogue';
|
|
81
|
-
|
|
82
99
|
// ── Types ──
|
|
83
100
|
export type {
|
|
84
|
-
ChatMessage,
|
|
85
|
-
ToolInvocation,
|
|
86
101
|
AliaAttachment,
|
|
87
102
|
AliaSuggestion,
|
|
88
103
|
ResearchProgress,
|
|
89
104
|
ResearchSource,
|
|
90
|
-
|
|
105
|
+
MessagePlan,
|
|
91
106
|
PlanStep,
|
|
92
107
|
} from './types';
|
|
93
108
|
|
|
94
|
-
// ── Voice
|
|
95
|
-
export type {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} from './types';
|
|
109
|
+
// ── Voice: part of the conversation screen; the host brings callbacks (see `AliaVoiceOptions`) ──
|
|
110
|
+
export type { VoiceNotice, VoiceSessionProps, VoiceSessionState } from './types';
|
|
111
|
+
export { AliaVoiceCall } from './components/voice/AliaVoiceCall';
|
|
112
|
+
export { useVoiceLauncher, toastNotice } from './hooks/useVoiceLauncher';
|
|
113
|
+
export type { UseVoiceLauncherOptions, VoiceLauncher } from './hooks/useVoiceLauncher';
|
|
100
114
|
|
|
101
115
|
// ── UI Primitives (NativeWind) ──
|
|
102
116
|
export { cn, formatFileSize } from './lib/utils';
|
|
@@ -6,7 +6,7 @@ type Sources = Readonly<Record<string, AliaAvatarState>>;
|
|
|
6
6
|
/**
|
|
7
7
|
* What Alia is doing, from every place that can know. Two sources feed it and neither owns it:
|
|
8
8
|
*
|
|
9
|
-
* - `local`: the turns running in THIS app, one entry per `
|
|
9
|
+
* - `local`: the turns running in THIS app, one entry per `useConversationThread` instance;
|
|
10
10
|
* - `remote`: runs the server reports for the person, one entry per agent session, so a button
|
|
11
11
|
* reacts to work that is not in any open chat.
|
|
12
12
|
*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComposerPanelAddMenuGroup } from '@oxy.so/bloom/composer-panel';
|
|
2
|
+
|
|
3
|
+
/** The rows of the composer's add menu, and what pressing one does. */
|
|
4
|
+
export interface AliaAddMenu {
|
|
5
|
+
groups: readonly ComposerPanelAddMenuGroup[];
|
|
6
|
+
onSelect: (rowId: string) => void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the thread shows while a call is open: the text messages it held when
|
|
3
|
+
* the call started, then the call's messages.
|
|
4
|
+
*
|
|
5
|
+
* A call message the server has stored is shown AS STORED — the copy a reload
|
|
6
|
+
* or a resume shows, with its tools' arguments and results, which the call's
|
|
7
|
+
* socket never carries. Same id either way (the call names every message by the
|
|
8
|
+
* id it is stored under), so swapping one for the other never adds a message.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Message } from '../runtime/types';
|
|
12
|
+
import type { VoiceMessage } from '../types';
|
|
13
|
+
|
|
14
|
+
export function callThread(
|
|
15
|
+
before: readonly Message[],
|
|
16
|
+
call: readonly VoiceMessage[],
|
|
17
|
+
stored: readonly Message[] | undefined,
|
|
18
|
+
): Message[] {
|
|
19
|
+
const storedById = new Map((stored ?? []).map((message) => [message.id, message]));
|
|
20
|
+
return [...before, ...call.map((message) => storedById.get(message.id) ?? fromCall(message))];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fromCall(message: VoiceMessage): Message {
|
|
24
|
+
return {
|
|
25
|
+
id: message.id,
|
|
26
|
+
role: message.role,
|
|
27
|
+
content: message.content,
|
|
28
|
+
toolInvocations: message.toolInvocations,
|
|
29
|
+
createdAt: new Date(message.timestamp).toISOString(),
|
|
30
|
+
source: 'voice' as const,
|
|
31
|
+
speaker: message.speaker,
|
|
32
|
+
};
|
|
33
|
+
}
|
package/src/lib/citations.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ToolInvocation } from '../types';
|
|
1
|
+
import type { ToolInvocation } from '../runtime/types';
|
|
2
2
|
import { domainOf } from './domain';
|
|
3
3
|
|
|
4
4
|
export interface CitationSource {
|
|
@@ -87,7 +87,7 @@ export function normalizeCitationMarkers(text: string, knownIds?: Iterable<numbe
|
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* The accessible name of one citation: what a screen reader says for the
|
|
90
|
-
* link, and what `
|
|
90
|
+
* link, and what `ReplyMarkdown` puts in the link's title.
|
|
91
91
|
*/
|
|
92
92
|
export function citationLabel(source: CitationSource): string {
|
|
93
93
|
return `Source ${source.id}: ${source.title}`;
|
|
@@ -113,3 +113,80 @@ export function linkifyCitations(text: string, sources: CitationSource[]): strin
|
|
|
113
113
|
return `[${n}](${source.url} "${title}")`;
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/** One entry of a rendered references section. */
|
|
118
|
+
export interface ReferenceEntry {
|
|
119
|
+
id: number;
|
|
120
|
+
title: string;
|
|
121
|
+
url: string;
|
|
122
|
+
domain: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Where the references section starts: the API writes `## References` after a
|
|
127
|
+
* rule; the heading is matched loosely so an older answer, or one
|
|
128
|
+
* written in Spanish by the model itself, splits the same way.
|
|
129
|
+
*/
|
|
130
|
+
const REFERENCES_HEADING_RE =
|
|
131
|
+
/(?:^|\n)(?:---\s*\n+)?#{1,3}\s*(?:References|Referencias|Sources|Fuentes)\s*:?\s*(?:\n|$)/i;
|
|
132
|
+
|
|
133
|
+
/** `[n] [Title](url)` (new) or `[n] Title` + a URL line (legacy). */
|
|
134
|
+
const REFERENCE_LINE_RE = /^\s*\[(\d{1,3})\]\s*(.*)$/;
|
|
135
|
+
// The title may carry escaped brackets (`React \[Wikipedia\]`), which the
|
|
136
|
+
// API writes for a title that contains them.
|
|
137
|
+
const MARKDOWN_LINK_RE = /^\[((?:\\.|[^\]\\])*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)\s*$/;
|
|
138
|
+
const URL_RE = /https?:\/\/[^\s)]+/;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Split an answer into its body and the entries of its references section.
|
|
142
|
+
*
|
|
143
|
+
* `references` is `null` when the section is absent or none of its lines
|
|
144
|
+
* parse, in which case the caller renders the whole text as Markdown and
|
|
145
|
+
* nothing is lost. Known sources fill in a title or URL a legacy line lacks.
|
|
146
|
+
*/
|
|
147
|
+
export function splitReferences(
|
|
148
|
+
text: string,
|
|
149
|
+
sources: CitationSource[] = [],
|
|
150
|
+
): { body: string; references: ReferenceEntry[] | null } {
|
|
151
|
+
const match = REFERENCES_HEADING_RE.exec(text);
|
|
152
|
+
if (!match) return { body: text, references: null };
|
|
153
|
+
|
|
154
|
+
const body = text.slice(0, match.index).replace(/\s+$/, '');
|
|
155
|
+
const section = text.slice(match.index + match[0].length);
|
|
156
|
+
const byId = new Map(sources.map((s) => [s.id, s]));
|
|
157
|
+
|
|
158
|
+
const entries: ReferenceEntry[] = [];
|
|
159
|
+
let current: { id: number; rest: string } | null = null;
|
|
160
|
+
const flush = () => {
|
|
161
|
+
if (!current) return;
|
|
162
|
+
const known = byId.get(current.id);
|
|
163
|
+
const rest = current.rest.trim();
|
|
164
|
+
const link = MARKDOWN_LINK_RE.exec(rest);
|
|
165
|
+
let url = link ? link[2] : (URL_RE.exec(rest)?.[0] ?? known?.url ?? '');
|
|
166
|
+
let title = link ? link[1] : rest.replace(URL_RE, '').replace(/\s+/g, ' ').trim();
|
|
167
|
+
if (!url && known) url = known.url;
|
|
168
|
+
if (!title) title = known?.title ?? (url ? domainOf(url, { stripWww: true }) : '');
|
|
169
|
+
if (url)
|
|
170
|
+
entries.push({
|
|
171
|
+
id: current.id,
|
|
172
|
+
title: title.replace(/\\([[\]])/g, '$1'),
|
|
173
|
+
url,
|
|
174
|
+
domain: domainOf(url, { stripWww: true }),
|
|
175
|
+
});
|
|
176
|
+
current = null;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
for (const line of section.split('\n')) {
|
|
180
|
+
const m = REFERENCE_LINE_RE.exec(line);
|
|
181
|
+
if (m) {
|
|
182
|
+
flush();
|
|
183
|
+
current = { id: Number(m[1]), rest: m[2] };
|
|
184
|
+
} else if (current && line.trim().length > 0) {
|
|
185
|
+
// A legacy entry's URL sits on the indented line after the title.
|
|
186
|
+
current.rest += ` ${line.trim()}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
flush();
|
|
190
|
+
|
|
191
|
+
return entries.length > 0 ? { body, references: entries } : { body: text, references: null };
|
|
192
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { toast } from '@oxy.so/bloom/toast';
|
|
2
|
+
import * as Clipboard from 'expo-clipboard';
|
|
3
|
+
import type { Translate } from './copy';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Copy `text`, and say which happened.
|
|
7
|
+
*
|
|
8
|
+
* The result is the clipboard's own: `setStringAsync` resolves `false` on web when the browser
|
|
9
|
+
* refuses (no permission, no focus, an insecure origin) and rejects on native when the module is
|
|
10
|
+
* missing. Either way the answer is `false` and an error toast, and Bloom's copy button, which
|
|
11
|
+
* confirms only on `true`, stays a copy glyph instead of announcing "Copied!" over nothing.
|
|
12
|
+
*/
|
|
13
|
+
export async function copyText(text: string, t: Translate): Promise<boolean> {
|
|
14
|
+
let copied: boolean;
|
|
15
|
+
try {
|
|
16
|
+
copied = (await Clipboard.setStringAsync(text)) !== false;
|
|
17
|
+
} catch {
|
|
18
|
+
copied = false;
|
|
19
|
+
}
|
|
20
|
+
if (copied) toast.success(t('chat.copiedToClipboard'));
|
|
21
|
+
else toast.error(t('chat.copyFailed'));
|
|
22
|
+
return copied;
|
|
23
|
+
}
|
package/src/lib/content-utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { processMessage } from '../runtime/thread/message-processor';
|
|
2
|
+
|
|
1
3
|
/** A single part of a multi-part chat message content array. */
|
|
2
4
|
export interface ContentPart {
|
|
3
5
|
type: string;
|
|
@@ -22,6 +24,14 @@ export function getTextFromContent(content: MessageContent | null | undefined):
|
|
|
22
24
|
return String(content || '');
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* A message's words as the thread draws them: its text parts, without the tags other platforms
|
|
29
|
+
* use (Telegram's), which a reply written for one can still carry.
|
|
30
|
+
*/
|
|
31
|
+
export function messageText(message: { content: MessageContent | null | undefined }): string {
|
|
32
|
+
return processMessage(message.content ? getTextFromContent(message.content) : '', 'app').text;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
/**
|
|
26
36
|
* Extract image URLs from multi-part message content.
|
|
27
37
|
*/
|