@theoremjs/react 0.3.4-next.421.g4bac1c7 → 0.3.4-next.425.gd676bc4
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/README.md +1 -0
- package/package.json +3 -3
- package/ui/ChatComposerBar.js +23 -11
- package/ui/TheoremChat.d.ts +2 -0
- package/ui/TheoremChat.js +2 -1
package/README.md
CHANGED
|
@@ -170,6 +170,7 @@ The profile, the keys, and the system prompt stay on the server. The browser rec
|
|
|
170
170
|
| `conversation`, `conversationView`, `onConversationChange` | Resume a conversation that the server keeps, or open one you started. See below. |
|
|
171
171
|
| `initialText` | The text that the composer starts with. The user can send it or change it. The component reads it once, when it mounts. |
|
|
172
172
|
| `initialFiles` | The files that the composer starts with, staged to send. The component reads them once, when it mounts. |
|
|
173
|
+
| `initialVoice` | The voice notes that the composer starts with, staged to send. The component reads them once, when it mounts. |
|
|
173
174
|
| `initialPending` | The messages that the composer starts with set aside: queued, stashed, or steering, as `createComposerPendingMessage` from `@theoremjs/agents/interface` makes them. The component reads them once, when it mounts. |
|
|
174
175
|
| `chatRef` | Sends a message from your code. See below. |
|
|
175
176
|
| `composer` | `false` leaves the composer out. The chat is the transcript alone, and your own control sends through `chatRef`. Default: `true`. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.425.gd676bc4",
|
|
4
4
|
"description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@stylexjs/stylex": "0.19.0",
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0",
|
|
63
|
-
"@theoremjs/agents": "^0.3.4-next.
|
|
64
|
-
"@theoremjs/web": "^0.3.4-next.
|
|
63
|
+
"@theoremjs/agents": "^0.3.4-next.425.gd676bc4",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.425.gd676bc4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/ChatComposerBar.js
CHANGED
|
@@ -29,15 +29,23 @@ const NO_FOCUS_RING = { '--focus-outline-width': '0px' };
|
|
|
29
29
|
export const EFFORT_ICON = IconBrain;
|
|
30
30
|
const isImage = (file) => file.type.startsWith('image/');
|
|
31
31
|
const anyFile = () => true;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Object URLs for the files `keep` (a module-level predicate) accepts, revoked when the files change.
|
|
34
|
+
* why: Each URL is made in the effect that revokes it, so a remount (strict mode, a moved subtree)
|
|
35
|
+
* makes fresh ones instead of keeping revoked ones; until the effect runs the files have none.
|
|
36
|
+
*/
|
|
33
37
|
function useObjectUrls(files, keep) {
|
|
34
|
-
const
|
|
35
|
-
useEffect(() =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const [made, setMade] = useState();
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const urls = files.map((file) => (keep(file) ? URL.createObjectURL(file) : undefined));
|
|
41
|
+
setMade({ files, urls });
|
|
42
|
+
return () => {
|
|
43
|
+
for (const url of urls)
|
|
44
|
+
if (url)
|
|
45
|
+
URL.revokeObjectURL(url);
|
|
46
|
+
};
|
|
47
|
+
}, [files, keep]);
|
|
48
|
+
return made?.files === files ? made.urls : [];
|
|
41
49
|
}
|
|
42
50
|
/** Each way to send, by the same icon in the send menu and on a waiting message. */
|
|
43
51
|
const SEND_ICON = {
|
|
@@ -111,7 +119,11 @@ function composerStatus(args) {
|
|
|
111
119
|
/** Pending messages, then two uniform rows: 64px tiles (images, voice notes), then file tokens. */
|
|
112
120
|
function PendingDrawer(props) {
|
|
113
121
|
const t = useLabels();
|
|
114
|
-
return (_jsx(ChatComposerDrawer, { count: props.summary.count, label: composerDrawerLabel(t, props.summary), children: _jsxs(VStack, { gap: 2, width: "100%", children: [props.messages.map((message) => (_jsx(PendingRow, { message: message, ...props.pendingActions }, message.id))), props.imageFiles.length + props.voiceFiles.length > 0 ? (_jsxs(HStack, { gap: 2, wrap: "wrap", vAlign: "center", children: [props.imageFiles.map(({ file, index, preview }) => (_jsx(Thumbnail, { src: preview, alt: file.name, label: file.name, onRemove: () => props.onAttachmentRemove(index) }, `${file.name}:${String(index)}`))), props.voiceFiles.map((file, index) =>
|
|
122
|
+
return (_jsx(ChatComposerDrawer, { count: props.summary.count, label: composerDrawerLabel(t, props.summary), children: _jsxs(VStack, { gap: 2, width: "100%", children: [props.messages.map((message) => (_jsx(PendingRow, { message: message, ...props.pendingActions }, message.id))), props.imageFiles.length + props.voiceFiles.length > 0 ? (_jsxs(HStack, { gap: 2, wrap: "wrap", vAlign: "center", children: [props.imageFiles.map(({ file, index, preview }) => (_jsx(Thumbnail, { src: preview, alt: file.name, label: file.name, onRemove: () => props.onAttachmentRemove(index) }, `${file.name}:${String(index)}`))), props.voiceFiles.map((file, index) => {
|
|
123
|
+
const src = props.voiceUrls[index];
|
|
124
|
+
// why: An empty src makes the browser fetch the page again; a note draws once it has its URL.
|
|
125
|
+
return src === undefined ? null : (_jsx(VoiceNote, { src: src, mimeType: file.type || undefined, onRemove: props.onVoiceRemove }, `voice:${file.name}`));
|
|
126
|
+
})] })) : null, props.otherFiles.length > 0 ? (_jsx(HStack, { gap: 2, wrap: "wrap", children: props.otherFiles.map(({ file, index }) => (_jsx(Token, { label: file.name, onRemove: () => props.onAttachmentRemove(index) }, `${file.name}:${String(index)}`))) })) : null] }) }));
|
|
115
127
|
}
|
|
116
128
|
/** A hidden file input behind the paperclip button. */
|
|
117
129
|
function AttachFilesButton({ accept, onFiles, }) {
|
|
@@ -168,8 +180,8 @@ function useStagedFiles(pendingFiles, pendingVoice) {
|
|
|
168
180
|
preview: previews[index],
|
|
169
181
|
}));
|
|
170
182
|
return {
|
|
171
|
-
imageFiles: staged.filter((entry) => entry.
|
|
172
|
-
otherFiles: staged.filter((entry) => entry.
|
|
183
|
+
imageFiles: staged.filter((entry) => isImage(entry.file)),
|
|
184
|
+
otherFiles: staged.filter((entry) => !isImage(entry.file)),
|
|
173
185
|
voiceUrls,
|
|
174
186
|
};
|
|
175
187
|
}
|
package/ui/TheoremChat.d.ts
CHANGED
|
@@ -87,6 +87,8 @@ export type TheoremChatProps = {
|
|
|
87
87
|
initialText?: string;
|
|
88
88
|
/** The files the composer starts with, staged to send. Read once, when the chat mounts. */
|
|
89
89
|
initialFiles?: readonly File[];
|
|
90
|
+
/** The voice notes the composer starts with, staged to send. Read once, when the chat mounts. */
|
|
91
|
+
initialVoice?: readonly File[];
|
|
90
92
|
/**
|
|
91
93
|
* The messages the composer starts with set aside: queued, stashed or steering, as
|
|
92
94
|
* `createComposerPendingMessage` makes them. Read once, when the chat mounts.
|
package/ui/TheoremChat.js
CHANGED
|
@@ -53,7 +53,7 @@ function ChatComposerForChat({ chat, iface, placeholder, inputRef, }) {
|
|
|
53
53
|
void chat.handleSendNow(message);
|
|
54
54
|
} }));
|
|
55
55
|
}
|
|
56
|
-
function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth = DEFAULT_CHAT_MAX_WIDTH, scrollRef, trace, usage, className, style, conversation, conversationView, initialText, initialFiles, initialPending, onConversationChange, chatRef, composer: hasComposer = true, slots, onSlotsChange, context, pageTools, onTurnEvent, }) {
|
|
56
|
+
function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth = DEFAULT_CHAT_MAX_WIDTH, scrollRef, trace, usage, className, style, conversation, conversationView, initialText, initialFiles, initialVoice, initialPending, onConversationChange, chatRef, composer: hasComposer = true, slots, onSlotsChange, context, pageTools, onTurnEvent, }) {
|
|
57
57
|
const t = useLabels();
|
|
58
58
|
const chat = useTheoremChat({
|
|
59
59
|
transport,
|
|
@@ -62,6 +62,7 @@ function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth
|
|
|
62
62
|
conversationView,
|
|
63
63
|
initialText,
|
|
64
64
|
initialFiles,
|
|
65
|
+
initialVoice,
|
|
65
66
|
initialPending,
|
|
66
67
|
onConversationChange,
|
|
67
68
|
slots,
|