@devicai/ui 0.41.1 → 0.43.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/README.md +126 -4
- package/dist/cjs/api/assistantInfo.js +68 -0
- package/dist/cjs/api/assistantInfo.js.map +1 -0
- package/dist/cjs/api/types.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js +60 -16
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js +6 -1
- package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/styles.css +1 -1
- package/dist/esm/api/assistantInfo.d.ts +30 -0
- package/dist/esm/api/assistantInfo.js +65 -0
- package/dist/esm/api/assistantInfo.js.map +1 -0
- package/dist/esm/api/types.d.ts +15 -0
- package/dist/esm/api/types.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.js +61 -17
- package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.d.ts +10 -1
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js +7 -2
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ React component library for integrating Devic AI assistants into your applicatio
|
|
|
7
7
|
- **ChatDrawer** - A ready-to-use chat drawer component
|
|
8
8
|
- **AICommandBar** - A spotlight-style command bar for quick AI interactions
|
|
9
9
|
- **AIGenerationButton** - A button for triggering AI generation with modal, tooltip, or direct modes
|
|
10
|
+
- **Tenant sessions** - Short-lived signed tokens, so the page never carries an API key
|
|
10
11
|
- **useDevicChat** - Hook for building custom chat UIs
|
|
11
12
|
- **Model Interface Protocol** - Support for client-side tool execution
|
|
12
13
|
- **Message Feedback** - Built-in thumbs up/down feedback with comments
|
|
@@ -89,7 +90,7 @@ Context provider for global configuration.
|
|
|
89
90
|
|
|
90
91
|
```tsx
|
|
91
92
|
<DevicProvider
|
|
92
|
-
apiKey="devic-xxx" //
|
|
93
|
+
apiKey="devic-xxx" // Optional when getTenantSession is supplied
|
|
93
94
|
baseUrl="https://api.devic.ai"
|
|
94
95
|
tenantId="tenant-123" // Optional global tenant
|
|
95
96
|
tenantMetadata={{ ... }} // Optional global metadata
|
|
@@ -98,6 +99,9 @@ Context provider for global configuration.
|
|
|
98
99
|
</DevicProvider>
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
One of `apiKey` and `getTenantSession` has to be there. A page using sessions
|
|
103
|
+
has no reason to carry a key, and should not: see below.
|
|
104
|
+
|
|
101
105
|
#### Tenant sessions — proving who the end user is
|
|
102
106
|
|
|
103
107
|
With an API key alone, the tenant is whatever the page says it is. The key sits
|
|
@@ -120,7 +124,25 @@ on its own before it expires:
|
|
|
120
124
|
</DevicProvider>
|
|
121
125
|
```
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
`getTenantSession` may return the token as a bare string or as
|
|
128
|
+
`{ token, expiresAt }` / `{ token, expiresIn }`. With none of the three, the
|
|
129
|
+
expiry is read out of the token itself, so a bare string works.
|
|
130
|
+
|
|
131
|
+
On your server, with a **server-side** API key (not the one in your bundle).
|
|
132
|
+
With [`@devicai/sdk`](https://www.npmjs.com/package/@devicai/sdk):
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { Devic } from '@devicai/sdk';
|
|
136
|
+
|
|
137
|
+
const devic = new Devic({ apiKey: process.env.DEVIC_API_KEY });
|
|
138
|
+
|
|
139
|
+
app.post('/api/devic-session', requireLogin, async (req, res) => {
|
|
140
|
+
// From YOUR session, never from the request body.
|
|
141
|
+
res.json(await devic.auth(req.user.organisationId, req.user.id).session());
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Or over HTTP, with no dependency:
|
|
124
146
|
|
|
125
147
|
```ts
|
|
126
148
|
app.post('/api/devic-session', requireLogin, async (req, res) => {
|
|
@@ -168,8 +190,56 @@ Do set `onSessionExpired` in this mode. There is nothing to renew from, so
|
|
|
168
190
|
without it the widget just stops answering — at the exact moment the user's own
|
|
169
191
|
login has expired too.
|
|
170
192
|
|
|
171
|
-
|
|
172
|
-
|
|
193
|
+
**Making the sessions compulsory.** Everything above is still only a
|
|
194
|
+
convention until the key that mints them is unable to do anything else. In the
|
|
195
|
+
Devic console, an API key has an identity mode:
|
|
196
|
+
|
|
197
|
+
| Mode | What the key can do |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `open` (default) | Anything it is allowed, declaring whichever tenant it likes beside itself. |
|
|
200
|
+
| `signed` | Mint tenant sessions, and nothing else. Every other `/api/v1` call with the key alone answers `401`. |
|
|
201
|
+
|
|
202
|
+
A `signed` key belongs on **your server** — it is the one in the snippet above.
|
|
203
|
+
It is not the key that used to go in your bundle; with sessions, the bundle
|
|
204
|
+
carries no key at all. The console reflects that: choosing `signed` narrows the
|
|
205
|
+
key to `/api/v1/tenant-sessions` and drops allowed domains, because there is no
|
|
206
|
+
browser origin to check.
|
|
207
|
+
|
|
208
|
+
A session cannot mint another session, so nothing that reaches the page can
|
|
209
|
+
widen itself back.
|
|
210
|
+
|
|
211
|
+
If you already ship an `open` key in a bundle, switching *that* key to `signed`
|
|
212
|
+
takes the page down. Mint a second key for the server, move the page to
|
|
213
|
+
`getTenantSession`, and only then revoke the old one.
|
|
214
|
+
|
|
215
|
+
You can also require sessions per assistant: an assistant with
|
|
216
|
+
`identityMode: 'signed'` refuses unsigned callers outright for connected apps.
|
|
217
|
+
|
|
218
|
+
#### One session for the whole tree
|
|
219
|
+
|
|
220
|
+
Every component builds its own API client, so a tree with a drawer, a command
|
|
221
|
+
bar and a modal would ask your backend for three tokens on load. `DevicProvider`
|
|
222
|
+
already shares one between everything below it — you get this for free.
|
|
223
|
+
|
|
224
|
+
Outside a provider, or when your own code needs the same token the widgets are
|
|
225
|
+
using, `createSharedSession` wraps your minting function with the same
|
|
226
|
+
behaviour: one in-flight request, reused until it is close to expiry, and
|
|
227
|
+
re-fetched when a client passes `force` after the API refused the token it just
|
|
228
|
+
used.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { createSharedSession, DevicApiClient } from '@devicai/ui';
|
|
232
|
+
|
|
233
|
+
const session = createSharedSession(() =>
|
|
234
|
+
fetch('/api/devic-session', { credentials: 'include' }).then((r) => r.json())
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const client = new DevicApiClient({
|
|
238
|
+
baseUrl: 'https://api.devic.ai',
|
|
239
|
+
getTenantSession: session, // no apiKey
|
|
240
|
+
onSessionExpired: () => location.assign('/login'),
|
|
241
|
+
});
|
|
242
|
+
```
|
|
173
243
|
|
|
174
244
|
### ChatDrawer
|
|
175
245
|
|
|
@@ -305,6 +375,17 @@ stays out of the way — no button, no request — when it does not:
|
|
|
305
375
|
/>
|
|
306
376
|
```
|
|
307
377
|
|
|
378
|
+
It knows without asking because the assistant says so: the API returns
|
|
379
|
+
`tenantIntegrations: { enabled, count }` on the assistant the drawer already
|
|
380
|
+
fetches for its header, so an assistant that offers nothing costs no extra
|
|
381
|
+
request. While the listing is on its way, `count` holds the header's place with
|
|
382
|
+
that many dimmed circles, instead of the control appearing a moment later and
|
|
383
|
+
pushing the title sideways.
|
|
384
|
+
|
|
385
|
+
An older deployment omits the field entirely. That is read as *cannot tell*, not
|
|
386
|
+
as *no* — the listing is requested and the control appears if there is anything
|
|
387
|
+
behind it, exactly as before.
|
|
388
|
+
|
|
308
389
|
Connected apps come first in the stack and unconnected ones are dimmed, so it
|
|
309
390
|
doubles as the status.
|
|
310
391
|
|
|
@@ -703,6 +784,42 @@ const {
|
|
|
703
784
|
});
|
|
704
785
|
```
|
|
705
786
|
|
|
787
|
+
### useAssistantInfo
|
|
788
|
+
|
|
789
|
+
What the API says about an assistant — name, avatar, whether it offers connected
|
|
790
|
+
apps — fetched **at most once per assistant**, however many components ask.
|
|
791
|
+
|
|
792
|
+
The drawer uses it for its header. Export exists because a host that builds its
|
|
793
|
+
own header, or its own connected-apps control, needs the same answer, and asking
|
|
794
|
+
for it twice is what this avoids. The promise is cached, not the result, so a
|
|
795
|
+
second caller arriving mid-request waits for the first one.
|
|
796
|
+
|
|
797
|
+
```tsx
|
|
798
|
+
import { useAssistantInfo, forgetAssistant } from '@devicai/ui';
|
|
799
|
+
|
|
800
|
+
const { assistant, settled } = useAssistantInfo({
|
|
801
|
+
assistantId: 'my-assistant',
|
|
802
|
+
client, // DevicApiClient
|
|
803
|
+
baseUrl: 'https://api.devic.ai',
|
|
804
|
+
credential: apiKey ?? 'session', // separates accounts in the cache
|
|
805
|
+
enabled: true,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
// Gate on `settled`, never on `assistant`: a null before it has settled only
|
|
809
|
+
// means "not yet", and reading it as "no" makes controls flicker.
|
|
810
|
+
if (settled && assistant?.tenantIntegrations?.enabled) {
|
|
811
|
+
...
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
A failure resolves to `assistant: null` with `settled: true` — not knowing is an
|
|
816
|
+
ordinary outcome, and every caller should have something reasonable to do
|
|
817
|
+
without the answer.
|
|
818
|
+
|
|
819
|
+
`forgetAssistant(baseUrl, assistantId, credential?)` drops the cached answer, so
|
|
820
|
+
the next ask reaches the API. Use it after changing the assistant from your own
|
|
821
|
+
admin UI; a page that only chats never needs it.
|
|
822
|
+
|
|
706
823
|
### useModelInterface
|
|
707
824
|
|
|
708
825
|
Hook for implementing the Model Interface Protocol.
|
|
@@ -890,9 +1007,14 @@ import type {
|
|
|
890
1007
|
|
|
891
1008
|
// API types
|
|
892
1009
|
RealtimeChatHistory,
|
|
1010
|
+
AssistantSpecialization,
|
|
1011
|
+
DevicApiClientConfig,
|
|
1012
|
+
TenantSessionToken,
|
|
893
1013
|
|
|
894
1014
|
// Hook types
|
|
895
1015
|
UseDevicChatOptions,
|
|
1016
|
+
UseAssistantInfoOptions,
|
|
1017
|
+
AssistantInfoState,
|
|
896
1018
|
} from '@devicai/ui';
|
|
897
1019
|
```
|
|
898
1020
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One in-flight request per assistant, shared by everything that asks.
|
|
7
|
+
*
|
|
8
|
+
* An assistant's description does not depend on who is looking or on anything
|
|
9
|
+
* that changes while a page is open, so asking twice can only ever produce the
|
|
10
|
+
* same answer. It was being asked more than twice: the header wants the avatar,
|
|
11
|
+
* the connected-apps control wants to know whether it should exist, and a host
|
|
12
|
+
* that remounts the drawer to reset a conversation — which the console does —
|
|
13
|
+
* made every one of them ask again.
|
|
14
|
+
*
|
|
15
|
+
* Cached as the promise rather than the result, so callers that arrive while
|
|
16
|
+
* the first request is still open wait for it instead of starting a second.
|
|
17
|
+
*/
|
|
18
|
+
const inFlight = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Same assistant, same deployment, same credential — anything else is a
|
|
21
|
+
* different answer. The credential is part of it because an API key carries
|
|
22
|
+
* the account: two keys can each have an assistant under the same identifier.
|
|
23
|
+
*/
|
|
24
|
+
function cacheKey(baseUrl, assistantId, credential) {
|
|
25
|
+
return `${baseUrl}|${credential}|${assistantId}`;
|
|
26
|
+
}
|
|
27
|
+
/** Forget what is known about an assistant, so the next ask reaches the API. */
|
|
28
|
+
function forgetAssistant(baseUrl, assistantId, credential = 'session') {
|
|
29
|
+
inFlight.delete(cacheKey(baseUrl, assistantId, credential));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What the API says about an assistant, fetched at most once per assistant.
|
|
33
|
+
*
|
|
34
|
+
* A failure resolves to `null` and `settled: true`: not knowing is an ordinary
|
|
35
|
+
* outcome here, and every caller is expected to have something reasonable to do
|
|
36
|
+
* without the answer.
|
|
37
|
+
*/
|
|
38
|
+
function useAssistantInfo(options) {
|
|
39
|
+
const { assistantId, client, baseUrl, credential = 'session', enabled = true, } = options;
|
|
40
|
+
const [state, setState] = React.useState({
|
|
41
|
+
assistant: null,
|
|
42
|
+
settled: false,
|
|
43
|
+
});
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!enabled || !client || !assistantId)
|
|
46
|
+
return;
|
|
47
|
+
const key = cacheKey(baseUrl, assistantId, credential);
|
|
48
|
+
let cancelled = false;
|
|
49
|
+
let request = inFlight.get(key);
|
|
50
|
+
if (!request) {
|
|
51
|
+
request = client.getAssistant(assistantId).catch(() => null);
|
|
52
|
+
inFlight.set(key, request);
|
|
53
|
+
}
|
|
54
|
+
setState((prev) => (prev.settled ? { ...prev, settled: false } : prev));
|
|
55
|
+
void request.then((assistant) => {
|
|
56
|
+
if (!cancelled)
|
|
57
|
+
setState({ assistant, settled: true });
|
|
58
|
+
});
|
|
59
|
+
return () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
};
|
|
62
|
+
}, [enabled, client, assistantId, baseUrl, credential]);
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
exports.forgetAssistant = forgetAssistant;
|
|
67
|
+
exports.useAssistantInfo = useAssistantInfo;
|
|
68
|
+
//# sourceMappingURL=assistantInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantInfo.js","sources":["../../../../src/api/assistantInfo.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { DevicApiClient } from './client';\nimport type { AssistantSpecialization } from './types';\n\n/**\n * One in-flight request per assistant, shared by everything that asks.\n *\n * An assistant's description does not depend on who is looking or on anything\n * that changes while a page is open, so asking twice can only ever produce the\n * same answer. It was being asked more than twice: the header wants the avatar,\n * the connected-apps control wants to know whether it should exist, and a host\n * that remounts the drawer to reset a conversation — which the console does —\n * made every one of them ask again.\n *\n * Cached as the promise rather than the result, so callers that arrive while\n * the first request is still open wait for it instead of starting a second.\n */\nconst inFlight = new Map<string, Promise<AssistantSpecialization | null>>();\n\n/**\n * Same assistant, same deployment, same credential — anything else is a\n * different answer. The credential is part of it because an API key carries\n * the account: two keys can each have an assistant under the same identifier.\n */\nfunction cacheKey(\n baseUrl: string,\n assistantId: string,\n credential: string\n): string {\n return `${baseUrl}|${credential}|${assistantId}`;\n}\n\n/** Forget what is known about an assistant, so the next ask reaches the API. */\nexport function forgetAssistant(\n baseUrl: string,\n assistantId: string,\n credential = 'session'\n): void {\n inFlight.delete(cacheKey(baseUrl, assistantId, credential));\n}\n\nexport interface UseAssistantInfoOptions {\n assistantId: string;\n client: DevicApiClient | null;\n baseUrl: string;\n /** Distinguishes accounts in the cache key. */\n credential?: string;\n /** Ask only when this is true. */\n enabled?: boolean;\n}\n\nexport interface AssistantInfoState {\n assistant: AssistantSpecialization | null;\n /**\n * True once an answer — or a failure — has arrived. Callers that gate on the\n * assistant must wait for this rather than for `assistant`, or they would\n * read a null that only means \"not yet\".\n */\n settled: boolean;\n}\n\n/**\n * What the API says about an assistant, fetched at most once per assistant.\n *\n * A failure resolves to `null` and `settled: true`: not knowing is an ordinary\n * outcome here, and every caller is expected to have something reasonable to do\n * without the answer.\n */\nexport function useAssistantInfo(\n options: UseAssistantInfoOptions\n): AssistantInfoState {\n const {\n assistantId,\n client,\n baseUrl,\n credential = 'session',\n enabled = true,\n } = options;\n\n const [state, setState] = useState<AssistantInfoState>({\n assistant: null,\n settled: false,\n });\n\n useEffect(() => {\n if (!enabled || !client || !assistantId) return;\n\n const key = cacheKey(baseUrl, assistantId, credential);\n let cancelled = false;\n\n let request = inFlight.get(key);\n if (!request) {\n request = client.getAssistant(assistantId).catch(() => null);\n inFlight.set(key, request);\n }\n\n setState((prev) => (prev.settled ? { ...prev, settled: false } : prev));\n void request.then((assistant) => {\n if (!cancelled) setState({ assistant, settled: true });\n });\n\n return () => {\n cancelled = true;\n };\n }, [enabled, client, assistantId, baseUrl, credential]);\n\n return state;\n}\n"],"names":["useState","useEffect"],"mappings":";;;;AAIA;;;;;;;;;;;;AAYG;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmD;AAE3E;;;;AAIG;AACH,SAAS,QAAQ,CACf,OAAe,EACf,WAAmB,EACnB,UAAkB,EAAA;AAElB,IAAA,OAAO,GAAG,OAAO,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,EAAI,WAAW,EAAE;AAClD;AAEA;AACM,SAAU,eAAe,CAC7B,OAAe,EACf,WAAmB,EACnB,UAAU,GAAG,SAAS,EAAA;AAEtB,IAAA,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;AAC7D;AAsBA;;;;;;AAMG;AACG,SAAU,gBAAgB,CAC9B,OAAgC,EAAA;AAEhC,IAAA,MAAM,EACJ,WAAW,EACX,MAAM,EACN,OAAO,EACP,UAAU,GAAG,SAAS,EACtB,OAAO,GAAG,IAAI,GACf,GAAG,OAAO;AAEX,IAAA,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAGA,cAAQ,CAAqB;AACrD,QAAA,SAAS,EAAE,IAAI;AACf,QAAA,OAAO,EAAE,KAAK;AACf,KAAA,CAAC;IAEFC,eAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW;YAAE;QAEzC,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC;QACtD,IAAI,SAAS,GAAG,KAAK;QAErB,IAAI,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE;AACZ,YAAA,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC;AAC5D,YAAA,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;QAC5B;QAEA,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;AACvE,QAAA,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,KAAI;AAC9B,YAAA,IAAI,CAAC,SAAS;gBAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACxD,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,MAAK;YACV,SAAS,GAAG,IAAI;AAClB,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEvD,IAAA,OAAO,KAAK;AACd;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../../src/api/types.ts"],"sourcesContent":["import type React from 'react';\n\n/**\n * File attachment for messages\n */\nexport interface ChatFile {\n name: string;\n downloadUrl?: string;\n fileType?: 'image' | 'document' | 'audio' | 'video' | 'other';\n}\n\n/**\n * Attachment as it appears on a message.\n *\n * Two shapes reach the UI for the same thing: the optimistic message built\n * locally on send uses `url`/`type`, while the history returned by the API\n * carries the stored `downloadUrl`/`fileType`. Both are accepted here; use\n * `normalizeMessageFile` before reading them.\n */\nexport interface MessageFile {\n name: string;\n url?: string;\n type?: string;\n downloadUrl?: string;\n fileType?: string;\n}\n\n/**\n * Message content structure\n */\nexport interface MessageContent {\n message?: string;\n data?: any;\n files?: MessageFile[];\n}\n\n/** Collapse either attachment shape into a single one the UI can render. */\nexport function normalizeMessageFile(file: MessageFile): {\n name: string;\n url: string;\n type: string;\n} {\n return {\n name: file.name,\n url: file.url || file.downloadUrl || '',\n type: file.type || file.fileType || 'other',\n };\n}\n\n/**\n * Tool call from the model\n */\nexport interface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * Chat message structure\n */\nexport interface ChatMessage {\n uid: string;\n role: 'user' | 'assistant' | 'developer' | 'system' | 'tool';\n content: MessageContent;\n timestamp: number;\n chatUid?: string;\n tool_calls?: ToolCall[];\n tool_call_id?: string;\n summary?: string;\n /**\n * Where `content.message` came from, when the model did not write it.\n * `'finish_tool'`: the assistant is configured to require a tool call to\n * finish (\"Require Tool Use to Finish\") and the backend lifted the reply from\n * the finish tool's `message` argument, so it can be read without parsing\n * tool calls. Absent on replies the model wrote itself — use it to label the\n * bubble as produced by the tool.\n */\n contentSource?: string;\n /**\n * Id of a speech-to-text transcript (from POST /api/v1/whisper) that seeded\n * this message. Present on user messages dictated by voice; the chat can use\n * it to fetch the source audio (GET /api/v1/whisper/:transcriptId) and offer\n * playback.\n */\n transcriptId?: string;\n /**\n * Original server uid, present when the UI adopted an optimistic uid for\n * this message to keep React keys stable. Server-side references (e.g.\n * memory recall anchors) match against it.\n */\n serverUid?: string;\n}\n\n/**\n * Previous conversation message for initialization\n */\nexport interface PreviousMessage {\n message: string;\n role: 'user' | 'assistant';\n}\n\n/**\n * Model interface tool schema following OpenAI function calling format\n */\nexport interface ModelInterfaceToolSchema {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, any>;\n required?: string[];\n };\n };\n}\n\n/**\n * Props passed to a response widget component.\n * The widget is responsible for collecting the user's response and\n * calling `submit` with the payload to resolve the tool call.\n */\nexport interface ResponseWidgetProps {\n /** The tool call this widget is responding to */\n toolCall: ToolCall;\n /** Parsed arguments from the tool call */\n params: any;\n /** Submit the tool response payload (sent as the tool call result to the model) */\n submit: (response: any) => void;\n /** Cancel the tool call. Sends an error response so the model can continue. */\n cancel?: (reason?: string) => void;\n /** Whether the widget is currently submitting */\n isSubmitting?: boolean;\n}\n\n/**\n * Interactive response widget configuration for a client-side tool.\n *\n * When the model calls a tool configured with a `responseWidget`, the\n * widget is rendered in the chat UI instead of executing a callback.\n * The user interacts with the widget, which calls `submit(response)` to\n * define the tool response sent back to the model.\n *\n * - `render: 'inline'` renders the widget in the message thread at the\n * position of the tool call. The text input remains enabled.\n * - `render: 'input'` replaces the chat input area with the widget\n * while it is pending. The text input is disabled until submission.\n */\nexport interface ResponseWidgetConfig {\n /** Where to render the widget */\n render: 'inline' | 'input';\n /** The widget component */\n component: React.ComponentType<ResponseWidgetProps>;\n}\n\n/**\n * Model interface tool definition for client-side tools.\n *\n * A tool must provide either a `callback` (executed automatically when\n * the model invokes the tool) or a `responseWidget` (renders UI for\n * the user to produce the tool response). Providing both is an error.\n */\nexport interface ModelInterfaceTool {\n toolName: string;\n schema: ModelInterfaceToolSchema;\n /** Executed automatically when the model calls this tool */\n callback?: (params: any) => Promise<any> | any;\n /** Interactive widget that collects the user's tool response */\n responseWidget?: ResponseWidgetConfig;\n}\n\n/**\n * Tool call response to send back to the API\n */\nexport interface ToolCallResponse {\n tool_call_id: string;\n content: any;\n role: 'tool';\n}\n\n/**\n * DTO for sending messages to the assistant\n */\nexport interface ProcessMessageDto {\n message: string;\n chatUid?: string;\n userName?: string;\n files?: ChatFile[];\n /** Tags to associate with this chat (top-level, distinct from `metadata`). */\n tags?: string[];\n metadata?: {\n promptTemplateParams?: Record<string, any>;\n tenantToken?: string;\n [key: string]: any;\n };\n tenantId?: string;\n previousConversation?: PreviousMessage[];\n enabledTools?: string[];\n provider?: string;\n model?: string;\n // Model interface protocol fields\n tools?: ModelInterfaceToolSchema[];\n applicationState?: Record<string, any>;\n skipSummarization?: boolean;\n /**\n * Id of a speech-to-text transcript (from POST /api/v1/whisper) that seeded\n * this message. Sent so the conversation keeps a link to the original audio.\n */\n transcriptId?: string;\n}\n\n/**\n * Response from the /whisper speech-to-text endpoint.\n */\nexport interface WhisperTranscriptionResponse {\n /** Public id of the transcript; send it back as ProcessMessageDto.transcriptId. */\n transcriptId: string;\n /** Transcribed text. */\n text: string;\n /** Language hint used, if any. */\n language?: string;\n /** Download URL of the source audio. */\n audioUrl?: string;\n /** Transcription model used. */\n model?: string;\n}\n\n/**\n * Response from the assistant\n */\nexport interface AssistantResponse {\n messages: ChatMessage[];\n chatUid: string;\n inputTokens?: number;\n outputTokens?: number;\n}\n\n/**\n * Async mode response\n */\nexport interface AsyncResponse {\n chatUid: string;\n message?: string;\n error?: string;\n}\n\n/**\n * Real-time chat history status.\n * `limit_exceeded` means the message was blocked before reaching the LLM\n * because a configured tenant/subtenant usage limit was reached.\n */\nexport type RealtimeStatus =\n | 'processing'\n | 'completed'\n | 'error'\n | 'waiting_for_tool_response'\n | 'handed_off'\n | 'limit_exceeded';\n\n/**\n * Details of a tenant/subtenant usage limit that blocked a message.\n * Returned on the realtime endpoint when status is `limit_exceeded`, and on\n * the HTTP 429 body (`details`) when a synchronous request is blocked.\n */\nexport interface TenantLimitExceeded {\n /** Human-readable message describing the block. */\n message?: string;\n /** The rule that triggered the block (scope, metric, window, limit…). */\n blockingRule?: {\n scope?: 'tenant' | 'subtenant';\n subtenantId?: string;\n metric?: 'tokens' | 'cost';\n windowUnit?: 'hour' | 'day' | 'week' | 'month';\n windowEvery?: number;\n limit?: number;\n };\n /** Current consumption in the blocking window. */\n current?: number;\n /** The limit that was reached. */\n limit?: number;\n /** Epoch ms when the blocking window resets and usage is allowed again. */\n resetsAt?: number;\n}\n\n/** One fact a long-term-memory recall surfaced. */\nexport interface RecalledMemoryFact {\n fact: string;\n relation: string;\n /** Source entity name of the graph edge, when the fact connects two. */\n source: string | null;\n /** Target entity name of the graph edge, when the fact connects two. */\n target: string | null;\n /** ISO date the fact became valid, if known. */\n validAt: string | null;\n}\n\n/** One graph entity a long-term-memory recall surfaced. */\nexport interface RecalledMemoryEntity {\n id: string;\n name: string;\n type: string;\n summary: string | null;\n}\n\n/** One previous-session turn a conversation-start recall carried over. */\nexport interface RecalledMemoryTurn {\n role: string;\n content: string;\n}\n\n/**\n * One structured long-term-memory recall event of a conversation: the facts,\n * entities and previous-session turns a recall surfaced, plus the uid of the\n * message that brought it in (`messageUid`: the initial user message, or the\n * assistant message carrying the memory tool call — resolvable through\n * `toolCallId` while the run is still in flight).\n */\nexport interface RecalledMemoryRecord {\n uid: string;\n messageUid?: string;\n toolCallId?: string;\n source:\n | 'conversation_start'\n | 'search_memory'\n | 'search_memory_nodes'\n | 'explore_memory_graph';\n query?: string;\n facts?: RecalledMemoryFact[];\n entities?: RecalledMemoryEntity[];\n turns?: RecalledMemoryTurn[];\n timestampMs: number;\n}\n\n/**\n * Snapshot of the core-memory block a conversation saw (audit trail): the\n * render revision plus the injected entries as structured items.\n */\nexport interface CoreMemorySnapshot {\n uid: string;\n revision: string;\n items: Array<{\n id: number;\n section: string;\n content: string;\n pinned: boolean;\n source: string;\n }>;\n entries: number;\n omitted: number;\n chars: number;\n timestampMs: number;\n}\n\n/**\n * Real-time chat history response\n */\nexport interface RealtimeChatHistory {\n chatUID: string;\n clientUID: string;\n chatHistory: ChatMessage[];\n status: RealtimeStatus;\n lastUpdatedAt: number;\n pendingToolCalls?: ToolCall[];\n handedOffSubThreadId?: string;\n /** Present only when status is `limit_exceeded`. */\n limitExceeded?: TenantLimitExceeded;\n /**\n * Memory-recall events of the in-flight run — lets the UI show what the\n * assistant is recalling while the response is still processing.\n */\n recalledMemories?: RecalledMemoryRecord[];\n}\n\n/**\n * A single usage rule with its current consumption (from GET\n * /api/v1/tenant-usage/:tenantId[/subtenants/:subtenantId]).\n */\nexport interface TenantUsageRule {\n scope: 'tenant' | 'subtenant';\n subtenantId?: string;\n metric: 'tokens' | 'cost';\n windowUnit: 'hour' | 'day' | 'week' | 'month';\n windowEvery: number;\n /** Configured limit for the window. */\n limit: number;\n /** Current consumption in the active window. */\n current: number;\n /** Utilization percentage (0..100, capped). */\n percent: number;\n /** Epoch ms when the active window resets. */\n resetsAt?: number;\n /** Where the rule comes from ('tier' | 'adhoc'). */\n origin?: string;\n /** Tier the rule belongs to, if any. */\n tierId?: string;\n}\n\n/**\n * Response of GET /api/v1/tenant-usage/:tenantId[/subtenants/:subtenantId]:\n * the effective usage rules with their current consumption + the active tier.\n */\nexport interface TenantUsage {\n tenantId: string;\n subtenantId?: string;\n tierId?: string;\n usage: TenantUsageRule[];\n}\n\n/**\n * A durable per-window usage history row (from GET\n * /api/v1/tenant-usage/:tenantId/history).\n */\nexport interface TenantUsageHistoryRow {\n clientUID: string;\n tenantId: string;\n subtenantId: string;\n scope: 'tenant' | 'subtenant';\n metric: 'tokens' | 'cost';\n windowUnit: 'hour' | 'day' | 'week' | 'month';\n windowEvery: number;\n windowKey: string;\n windowStart: number;\n windowEnd: number;\n /** Counted consumption (enforced). */\n consumption: number;\n /** Exempt consumption that did not count toward the limit, if any. */\n exemptConsumption?: number;\n limit: number;\n percent: number;\n tierId?: string;\n origin?: string;\n capturedAt: number;\n}\n\n/**\n * Options for querying tenant usage history.\n */\nexport interface TenantUsageHistoryQuery {\n subtenantId?: string;\n scope?: 'tenant' | 'subtenant';\n metric?: 'tokens' | 'cost';\n windowUnit?: 'hour' | 'day' | 'week' | 'month';\n /** Epoch ms lower bound (windowEnd >= from). */\n from?: number;\n /** Epoch ms upper bound (windowEnd <= to). */\n to?: number;\n limit?: number;\n skip?: number;\n}\n\n/**\n * Chat history structure\n */\nexport interface ChatHistory {\n chatUID: string;\n clientUID: string;\n userUID: string;\n chatContent: ChatMessage[];\n name?: string;\n assistantSpecializationIdentifier: string;\n creationTimestampMs: number;\n lastEditTimestampMs?: number;\n llm?: string;\n inputTokens?: number;\n outputTokens?: number;\n metadata?: Record<string, any>;\n tenantId?: string;\n handedOff?: boolean;\n handedOffSubThreadId?: string;\n handedOffToolCallId?: string;\n /** Structured long-term-memory recall events of the conversation. */\n recalledMemories?: RecalledMemoryRecord[];\n /** Audit trail of the core-memory blocks the conversation saw. */\n coreMemories?: CoreMemorySnapshot[];\n}\n\n/** One core memory entry (the always-injected tier), as returned by the memory API. */\nexport interface CoreMemoryEntry {\n id: number;\n section: string;\n content: string;\n source: string;\n pinned: boolean;\n supersedes: number | null;\n archivedAt: string | null;\n createdAt?: string;\n updatedAt?: string;\n}\n\n/** Deployment caps of the core memory tier. */\nexport interface CoreMemoryLimits {\n maxChars: number;\n maxEntries: number;\n maxEntryChars: number;\n}\n\n/**\n * Response of GET /api/v1/memory/assistants/:identifier/core — the entries\n * of the bucket the assistant resolves for a tenant/subtenant combination.\n */\nexport interface CoreMemoryList {\n /** False when the assistant does not have the core memory tier enabled. */\n enabled: boolean;\n /** The resolved bucket tuple (tenant/subtenant/owner dimensions). */\n bucket: { tenantId?: string; subtenantId?: string; entityId?: string };\n entries: CoreMemoryEntry[];\n limits?: CoreMemoryLimits;\n}\n\n/**\n * Assistant specialization info\n */\nexport interface AssistantSpecialization {\n identifier: string;\n name: string;\n description: string;\n state: 'active' | 'inactive' | 'coming_soon';\n imgUrl?: string;\n availableToolsGroups?: Array<{\n name: string;\n description?: string;\n uid?: string;\n iconUrl?: string;\n tools?: Array<{\n name: string;\n description: string;\n }>;\n }>;\n model?: string;\n isCustom?: boolean;\n creationTimestampMs?: number;\n}\n\n/**\n * Summary of a conversation for listing\n */\nexport interface ConversationSummary {\n chatUID: string;\n name?: string;\n creationTimestampMs: number;\n lastEditTimestampMs?: number;\n}\n\nexport interface ListConversationsResponse {\n histories: ConversationSummary[];\n total: number;\n offset: number;\n limit: number;\n}\n\n/**\n * API error response\n */\nexport interface ApiError {\n statusCode: number;\n message: string;\n error?: string;\n /** Optional structured details (e.g. usage-limit blocking info on a 429). */\n details?: any;\n}\n\n/**\n * Feedback submission request\n */\nexport interface FeedbackSubmission {\n messageId: string;\n feedback?: boolean;\n feedbackComment?: string;\n feedbackData?: Record<string, any>;\n}\n\n/**\n * Feedback entry response\n */\nexport interface FeedbackEntry {\n _id: string;\n requestId: string;\n chatUID?: string;\n threadId?: string;\n agentId?: string;\n feedback?: boolean;\n feedbackComment?: string;\n feedbackData?: Record<string, any>;\n creationTimestamp: string;\n lastEditTimestamp?: string;\n}\n\n/**\n * Agent thread states\n */\nexport enum AgentThreadState {\n QUEUED = 'queued',\n PROCESSING = 'processing',\n COMPLETED = 'completed',\n FAILED = 'failed',\n TERMINATED = 'terminated',\n PAUSED = 'paused',\n PAUSED_FOR_APPROVAL = 'paused_for_approval',\n APPROVAL_REJECTED = 'approval_rejected',\n WAITING_FOR_RESPONSE = 'waiting_for_response',\n PAUSED_FOR_RESUME = 'paused_for_resume',\n HANDED_OFF = 'handed_off',\n GUARDRAIL_TRIGGER = 'guardrail_trigger',\n}\n\n/**\n * Task within an agent thread\n */\nexport interface AgentTaskDto {\n _id?: string;\n title?: string;\n description?: string;\n completed: boolean;\n}\n\n/**\n * Agent thread DTO\n */\nexport interface AgentThreadDto {\n _id?: string;\n agentId: string;\n state: AgentThreadState;\n threadContent: ChatMessage[];\n tasks?: AgentTaskDto[];\n finishReason?: string;\n pausedReason?: string;\n name?: string;\n creationTimestampMs?: number;\n lastEditTimestampMs?: number;\n pauseUntil?: number;\n isSubthread?: boolean;\n parentThreadId?: string;\n subThreadToolCallId?: string;\n parentAgentId?: string;\n}\n\n/**\n * Agent details\n */\nexport interface AgentDto {\n _id?: string;\n name: string;\n description?: string;\n imgUrl?: string;\n agentId?: string;\n}\n\n/**\n * Hand-off tool response content\n */\nexport interface HandOffToolResponse {\n response: string;\n subthreadId: string;\n}\n\n/**\n * Represents a single tool call within a tool group\n */\nexport interface ToolGroupCall {\n name: string;\n input: any;\n output: any;\n toolCallId: string;\n}\n\n/**\n * Configuration for grouping consecutive tool calls under a single renderer\n */\nexport interface ToolGroupConfig {\n tools: string[];\n renderer: (calls: ToolGroupCall[]) => React.ReactNode;\n}\n\n/**\n * One of the end user's connected accounts for an app.\n *\n * The provider's own identifiers do not travel here beyond `id`, which the\n * client needs in order to name the account it wants disconnected — and which\n * the server re-checks against the caller's tenant on the way back in.\n */\nexport interface IntegrationAccount {\n id: string;\n status: string;\n connectedAt?: string;\n updatedAt?: string;\n /** True when the account exists but can no longer run tools. */\n needsReconnect?: boolean;\n statusReason?: string;\n}\n\n/**\n * An app the assistant offers to its tenants, with the accounts THIS tenant\n * has connected. Never another tenant's.\n */\nexport interface Integration {\n /** App slug, e.g. `gmail`. */\n app: string;\n name: string;\n description?: string;\n logo?: string;\n /** True when at least one account is active. */\n connected: boolean;\n accounts: IntegrationAccount[];\n /** Event types the developer allows this tenant to switch on. */\n availableTriggers?: string[];\n}\n"],"names":["AgentThreadState"],"mappings":";;AAoCA;AACM,SAAU,oBAAoB,CAAC,IAAiB,EAAA;IAKpD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE;QACvC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,OAAO;KAC5C;AACH;AAgiBA;;AAEG;AACSA;AAAZ,CAAA,UAAY,gBAAgB,EAAA;AAC1B,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,WAAA,CAAA,GAAA,WAAuB;AACvB,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,qBAAA,CAAA,GAAA,qBAA2C;AAC3C,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACvC,IAAA,gBAAA,CAAA,sBAAA,CAAA,GAAA,sBAA6C;AAC7C,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACvC,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACzC,CAAC,EAbWA,wBAAgB,KAAhBA,wBAAgB,GAAA,EAAA,CAAA,CAAA;;;;"}
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/api/types.ts"],"sourcesContent":["import type React from 'react';\n\n/**\n * File attachment for messages\n */\nexport interface ChatFile {\n name: string;\n downloadUrl?: string;\n fileType?: 'image' | 'document' | 'audio' | 'video' | 'other';\n}\n\n/**\n * Attachment as it appears on a message.\n *\n * Two shapes reach the UI for the same thing: the optimistic message built\n * locally on send uses `url`/`type`, while the history returned by the API\n * carries the stored `downloadUrl`/`fileType`. Both are accepted here; use\n * `normalizeMessageFile` before reading them.\n */\nexport interface MessageFile {\n name: string;\n url?: string;\n type?: string;\n downloadUrl?: string;\n fileType?: string;\n}\n\n/**\n * Message content structure\n */\nexport interface MessageContent {\n message?: string;\n data?: any;\n files?: MessageFile[];\n}\n\n/** Collapse either attachment shape into a single one the UI can render. */\nexport function normalizeMessageFile(file: MessageFile): {\n name: string;\n url: string;\n type: string;\n} {\n return {\n name: file.name,\n url: file.url || file.downloadUrl || '',\n type: file.type || file.fileType || 'other',\n };\n}\n\n/**\n * Tool call from the model\n */\nexport interface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * Chat message structure\n */\nexport interface ChatMessage {\n uid: string;\n role: 'user' | 'assistant' | 'developer' | 'system' | 'tool';\n content: MessageContent;\n timestamp: number;\n chatUid?: string;\n tool_calls?: ToolCall[];\n tool_call_id?: string;\n summary?: string;\n /**\n * Where `content.message` came from, when the model did not write it.\n * `'finish_tool'`: the assistant is configured to require a tool call to\n * finish (\"Require Tool Use to Finish\") and the backend lifted the reply from\n * the finish tool's `message` argument, so it can be read without parsing\n * tool calls. Absent on replies the model wrote itself — use it to label the\n * bubble as produced by the tool.\n */\n contentSource?: string;\n /**\n * Id of a speech-to-text transcript (from POST /api/v1/whisper) that seeded\n * this message. Present on user messages dictated by voice; the chat can use\n * it to fetch the source audio (GET /api/v1/whisper/:transcriptId) and offer\n * playback.\n */\n transcriptId?: string;\n /**\n * Original server uid, present when the UI adopted an optimistic uid for\n * this message to keep React keys stable. Server-side references (e.g.\n * memory recall anchors) match against it.\n */\n serverUid?: string;\n}\n\n/**\n * Previous conversation message for initialization\n */\nexport interface PreviousMessage {\n message: string;\n role: 'user' | 'assistant';\n}\n\n/**\n * Model interface tool schema following OpenAI function calling format\n */\nexport interface ModelInterfaceToolSchema {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, any>;\n required?: string[];\n };\n };\n}\n\n/**\n * Props passed to a response widget component.\n * The widget is responsible for collecting the user's response and\n * calling `submit` with the payload to resolve the tool call.\n */\nexport interface ResponseWidgetProps {\n /** The tool call this widget is responding to */\n toolCall: ToolCall;\n /** Parsed arguments from the tool call */\n params: any;\n /** Submit the tool response payload (sent as the tool call result to the model) */\n submit: (response: any) => void;\n /** Cancel the tool call. Sends an error response so the model can continue. */\n cancel?: (reason?: string) => void;\n /** Whether the widget is currently submitting */\n isSubmitting?: boolean;\n}\n\n/**\n * Interactive response widget configuration for a client-side tool.\n *\n * When the model calls a tool configured with a `responseWidget`, the\n * widget is rendered in the chat UI instead of executing a callback.\n * The user interacts with the widget, which calls `submit(response)` to\n * define the tool response sent back to the model.\n *\n * - `render: 'inline'` renders the widget in the message thread at the\n * position of the tool call. The text input remains enabled.\n * - `render: 'input'` replaces the chat input area with the widget\n * while it is pending. The text input is disabled until submission.\n */\nexport interface ResponseWidgetConfig {\n /** Where to render the widget */\n render: 'inline' | 'input';\n /** The widget component */\n component: React.ComponentType<ResponseWidgetProps>;\n}\n\n/**\n * Model interface tool definition for client-side tools.\n *\n * A tool must provide either a `callback` (executed automatically when\n * the model invokes the tool) or a `responseWidget` (renders UI for\n * the user to produce the tool response). Providing both is an error.\n */\nexport interface ModelInterfaceTool {\n toolName: string;\n schema: ModelInterfaceToolSchema;\n /** Executed automatically when the model calls this tool */\n callback?: (params: any) => Promise<any> | any;\n /** Interactive widget that collects the user's tool response */\n responseWidget?: ResponseWidgetConfig;\n}\n\n/**\n * Tool call response to send back to the API\n */\nexport interface ToolCallResponse {\n tool_call_id: string;\n content: any;\n role: 'tool';\n}\n\n/**\n * DTO for sending messages to the assistant\n */\nexport interface ProcessMessageDto {\n message: string;\n chatUid?: string;\n userName?: string;\n files?: ChatFile[];\n /** Tags to associate with this chat (top-level, distinct from `metadata`). */\n tags?: string[];\n metadata?: {\n promptTemplateParams?: Record<string, any>;\n tenantToken?: string;\n [key: string]: any;\n };\n tenantId?: string;\n previousConversation?: PreviousMessage[];\n enabledTools?: string[];\n provider?: string;\n model?: string;\n // Model interface protocol fields\n tools?: ModelInterfaceToolSchema[];\n applicationState?: Record<string, any>;\n skipSummarization?: boolean;\n /**\n * Id of a speech-to-text transcript (from POST /api/v1/whisper) that seeded\n * this message. Sent so the conversation keeps a link to the original audio.\n */\n transcriptId?: string;\n}\n\n/**\n * Response from the /whisper speech-to-text endpoint.\n */\nexport interface WhisperTranscriptionResponse {\n /** Public id of the transcript; send it back as ProcessMessageDto.transcriptId. */\n transcriptId: string;\n /** Transcribed text. */\n text: string;\n /** Language hint used, if any. */\n language?: string;\n /** Download URL of the source audio. */\n audioUrl?: string;\n /** Transcription model used. */\n model?: string;\n}\n\n/**\n * Response from the assistant\n */\nexport interface AssistantResponse {\n messages: ChatMessage[];\n chatUid: string;\n inputTokens?: number;\n outputTokens?: number;\n}\n\n/**\n * Async mode response\n */\nexport interface AsyncResponse {\n chatUid: string;\n message?: string;\n error?: string;\n}\n\n/**\n * Real-time chat history status.\n * `limit_exceeded` means the message was blocked before reaching the LLM\n * because a configured tenant/subtenant usage limit was reached.\n */\nexport type RealtimeStatus =\n | 'processing'\n | 'completed'\n | 'error'\n | 'waiting_for_tool_response'\n | 'handed_off'\n | 'limit_exceeded';\n\n/**\n * Details of a tenant/subtenant usage limit that blocked a message.\n * Returned on the realtime endpoint when status is `limit_exceeded`, and on\n * the HTTP 429 body (`details`) when a synchronous request is blocked.\n */\nexport interface TenantLimitExceeded {\n /** Human-readable message describing the block. */\n message?: string;\n /** The rule that triggered the block (scope, metric, window, limit…). */\n blockingRule?: {\n scope?: 'tenant' | 'subtenant';\n subtenantId?: string;\n metric?: 'tokens' | 'cost';\n windowUnit?: 'hour' | 'day' | 'week' | 'month';\n windowEvery?: number;\n limit?: number;\n };\n /** Current consumption in the blocking window. */\n current?: number;\n /** The limit that was reached. */\n limit?: number;\n /** Epoch ms when the blocking window resets and usage is allowed again. */\n resetsAt?: number;\n}\n\n/** One fact a long-term-memory recall surfaced. */\nexport interface RecalledMemoryFact {\n fact: string;\n relation: string;\n /** Source entity name of the graph edge, when the fact connects two. */\n source: string | null;\n /** Target entity name of the graph edge, when the fact connects two. */\n target: string | null;\n /** ISO date the fact became valid, if known. */\n validAt: string | null;\n}\n\n/** One graph entity a long-term-memory recall surfaced. */\nexport interface RecalledMemoryEntity {\n id: string;\n name: string;\n type: string;\n summary: string | null;\n}\n\n/** One previous-session turn a conversation-start recall carried over. */\nexport interface RecalledMemoryTurn {\n role: string;\n content: string;\n}\n\n/**\n * One structured long-term-memory recall event of a conversation: the facts,\n * entities and previous-session turns a recall surfaced, plus the uid of the\n * message that brought it in (`messageUid`: the initial user message, or the\n * assistant message carrying the memory tool call — resolvable through\n * `toolCallId` while the run is still in flight).\n */\nexport interface RecalledMemoryRecord {\n uid: string;\n messageUid?: string;\n toolCallId?: string;\n source:\n | 'conversation_start'\n | 'search_memory'\n | 'search_memory_nodes'\n | 'explore_memory_graph';\n query?: string;\n facts?: RecalledMemoryFact[];\n entities?: RecalledMemoryEntity[];\n turns?: RecalledMemoryTurn[];\n timestampMs: number;\n}\n\n/**\n * Snapshot of the core-memory block a conversation saw (audit trail): the\n * render revision plus the injected entries as structured items.\n */\nexport interface CoreMemorySnapshot {\n uid: string;\n revision: string;\n items: Array<{\n id: number;\n section: string;\n content: string;\n pinned: boolean;\n source: string;\n }>;\n entries: number;\n omitted: number;\n chars: number;\n timestampMs: number;\n}\n\n/**\n * Real-time chat history response\n */\nexport interface RealtimeChatHistory {\n chatUID: string;\n clientUID: string;\n chatHistory: ChatMessage[];\n status: RealtimeStatus;\n lastUpdatedAt: number;\n pendingToolCalls?: ToolCall[];\n handedOffSubThreadId?: string;\n /** Present only when status is `limit_exceeded`. */\n limitExceeded?: TenantLimitExceeded;\n /**\n * Memory-recall events of the in-flight run — lets the UI show what the\n * assistant is recalling while the response is still processing.\n */\n recalledMemories?: RecalledMemoryRecord[];\n}\n\n/**\n * A single usage rule with its current consumption (from GET\n * /api/v1/tenant-usage/:tenantId[/subtenants/:subtenantId]).\n */\nexport interface TenantUsageRule {\n scope: 'tenant' | 'subtenant';\n subtenantId?: string;\n metric: 'tokens' | 'cost';\n windowUnit: 'hour' | 'day' | 'week' | 'month';\n windowEvery: number;\n /** Configured limit for the window. */\n limit: number;\n /** Current consumption in the active window. */\n current: number;\n /** Utilization percentage (0..100, capped). */\n percent: number;\n /** Epoch ms when the active window resets. */\n resetsAt?: number;\n /** Where the rule comes from ('tier' | 'adhoc'). */\n origin?: string;\n /** Tier the rule belongs to, if any. */\n tierId?: string;\n}\n\n/**\n * Response of GET /api/v1/tenant-usage/:tenantId[/subtenants/:subtenantId]:\n * the effective usage rules with their current consumption + the active tier.\n */\nexport interface TenantUsage {\n tenantId: string;\n subtenantId?: string;\n tierId?: string;\n usage: TenantUsageRule[];\n}\n\n/**\n * A durable per-window usage history row (from GET\n * /api/v1/tenant-usage/:tenantId/history).\n */\nexport interface TenantUsageHistoryRow {\n clientUID: string;\n tenantId: string;\n subtenantId: string;\n scope: 'tenant' | 'subtenant';\n metric: 'tokens' | 'cost';\n windowUnit: 'hour' | 'day' | 'week' | 'month';\n windowEvery: number;\n windowKey: string;\n windowStart: number;\n windowEnd: number;\n /** Counted consumption (enforced). */\n consumption: number;\n /** Exempt consumption that did not count toward the limit, if any. */\n exemptConsumption?: number;\n limit: number;\n percent: number;\n tierId?: string;\n origin?: string;\n capturedAt: number;\n}\n\n/**\n * Options for querying tenant usage history.\n */\nexport interface TenantUsageHistoryQuery {\n subtenantId?: string;\n scope?: 'tenant' | 'subtenant';\n metric?: 'tokens' | 'cost';\n windowUnit?: 'hour' | 'day' | 'week' | 'month';\n /** Epoch ms lower bound (windowEnd >= from). */\n from?: number;\n /** Epoch ms upper bound (windowEnd <= to). */\n to?: number;\n limit?: number;\n skip?: number;\n}\n\n/**\n * Chat history structure\n */\nexport interface ChatHistory {\n chatUID: string;\n clientUID: string;\n userUID: string;\n chatContent: ChatMessage[];\n name?: string;\n assistantSpecializationIdentifier: string;\n creationTimestampMs: number;\n lastEditTimestampMs?: number;\n llm?: string;\n inputTokens?: number;\n outputTokens?: number;\n metadata?: Record<string, any>;\n tenantId?: string;\n handedOff?: boolean;\n handedOffSubThreadId?: string;\n handedOffToolCallId?: string;\n /** Structured long-term-memory recall events of the conversation. */\n recalledMemories?: RecalledMemoryRecord[];\n /** Audit trail of the core-memory blocks the conversation saw. */\n coreMemories?: CoreMemorySnapshot[];\n}\n\n/** One core memory entry (the always-injected tier), as returned by the memory API. */\nexport interface CoreMemoryEntry {\n id: number;\n section: string;\n content: string;\n source: string;\n pinned: boolean;\n supersedes: number | null;\n archivedAt: string | null;\n createdAt?: string;\n updatedAt?: string;\n}\n\n/** Deployment caps of the core memory tier. */\nexport interface CoreMemoryLimits {\n maxChars: number;\n maxEntries: number;\n maxEntryChars: number;\n}\n\n/**\n * Response of GET /api/v1/memory/assistants/:identifier/core — the entries\n * of the bucket the assistant resolves for a tenant/subtenant combination.\n */\nexport interface CoreMemoryList {\n /** False when the assistant does not have the core memory tier enabled. */\n enabled: boolean;\n /** The resolved bucket tuple (tenant/subtenant/owner dimensions). */\n bucket: { tenantId?: string; subtenantId?: string; entityId?: string };\n entries: CoreMemoryEntry[];\n limits?: CoreMemoryLimits;\n}\n\n/**\n * Assistant specialization info\n */\nexport interface AssistantSpecialization {\n identifier: string;\n name: string;\n description: string;\n state: 'active' | 'inactive' | 'coming_soon';\n imgUrl?: string;\n availableToolsGroups?: Array<{\n name: string;\n description?: string;\n uid?: string;\n iconUrl?: string;\n tools?: Array<{\n name: string;\n description: string;\n }>;\n }>;\n model?: string;\n isCustom?: boolean;\n creationTimestampMs?: number;\n /**\n * Whether this assistant offers connected apps to its tenants.\n *\n * **Absent means \"cannot tell\", not \"no\"** — an API older than this field\n * says nothing, and treating silence as a no would hide the connected-apps\n * button from anyone whose deployment has not caught up yet.\n */\n tenantIntegrations?: {\n enabled: boolean;\n /**\n * How many apps the catalogue offers. An upper bound — the listing drops\n * any the provider cannot resolve — and enough to size a placeholder.\n */\n count?: number;\n };\n}\n\n/**\n * Summary of a conversation for listing\n */\nexport interface ConversationSummary {\n chatUID: string;\n name?: string;\n creationTimestampMs: number;\n lastEditTimestampMs?: number;\n}\n\nexport interface ListConversationsResponse {\n histories: ConversationSummary[];\n total: number;\n offset: number;\n limit: number;\n}\n\n/**\n * API error response\n */\nexport interface ApiError {\n statusCode: number;\n message: string;\n error?: string;\n /** Optional structured details (e.g. usage-limit blocking info on a 429). */\n details?: any;\n}\n\n/**\n * Feedback submission request\n */\nexport interface FeedbackSubmission {\n messageId: string;\n feedback?: boolean;\n feedbackComment?: string;\n feedbackData?: Record<string, any>;\n}\n\n/**\n * Feedback entry response\n */\nexport interface FeedbackEntry {\n _id: string;\n requestId: string;\n chatUID?: string;\n threadId?: string;\n agentId?: string;\n feedback?: boolean;\n feedbackComment?: string;\n feedbackData?: Record<string, any>;\n creationTimestamp: string;\n lastEditTimestamp?: string;\n}\n\n/**\n * Agent thread states\n */\nexport enum AgentThreadState {\n QUEUED = 'queued',\n PROCESSING = 'processing',\n COMPLETED = 'completed',\n FAILED = 'failed',\n TERMINATED = 'terminated',\n PAUSED = 'paused',\n PAUSED_FOR_APPROVAL = 'paused_for_approval',\n APPROVAL_REJECTED = 'approval_rejected',\n WAITING_FOR_RESPONSE = 'waiting_for_response',\n PAUSED_FOR_RESUME = 'paused_for_resume',\n HANDED_OFF = 'handed_off',\n GUARDRAIL_TRIGGER = 'guardrail_trigger',\n}\n\n/**\n * Task within an agent thread\n */\nexport interface AgentTaskDto {\n _id?: string;\n title?: string;\n description?: string;\n completed: boolean;\n}\n\n/**\n * Agent thread DTO\n */\nexport interface AgentThreadDto {\n _id?: string;\n agentId: string;\n state: AgentThreadState;\n threadContent: ChatMessage[];\n tasks?: AgentTaskDto[];\n finishReason?: string;\n pausedReason?: string;\n name?: string;\n creationTimestampMs?: number;\n lastEditTimestampMs?: number;\n pauseUntil?: number;\n isSubthread?: boolean;\n parentThreadId?: string;\n subThreadToolCallId?: string;\n parentAgentId?: string;\n}\n\n/**\n * Agent details\n */\nexport interface AgentDto {\n _id?: string;\n name: string;\n description?: string;\n imgUrl?: string;\n agentId?: string;\n}\n\n/**\n * Hand-off tool response content\n */\nexport interface HandOffToolResponse {\n response: string;\n subthreadId: string;\n}\n\n/**\n * Represents a single tool call within a tool group\n */\nexport interface ToolGroupCall {\n name: string;\n input: any;\n output: any;\n toolCallId: string;\n}\n\n/**\n * Configuration for grouping consecutive tool calls under a single renderer\n */\nexport interface ToolGroupConfig {\n tools: string[];\n renderer: (calls: ToolGroupCall[]) => React.ReactNode;\n}\n\n/**\n * One of the end user's connected accounts for an app.\n *\n * The provider's own identifiers do not travel here beyond `id`, which the\n * client needs in order to name the account it wants disconnected — and which\n * the server re-checks against the caller's tenant on the way back in.\n */\nexport interface IntegrationAccount {\n id: string;\n status: string;\n connectedAt?: string;\n updatedAt?: string;\n /** True when the account exists but can no longer run tools. */\n needsReconnect?: boolean;\n statusReason?: string;\n}\n\n/**\n * An app the assistant offers to its tenants, with the accounts THIS tenant\n * has connected. Never another tenant's.\n */\nexport interface Integration {\n /** App slug, e.g. `gmail`. */\n app: string;\n name: string;\n description?: string;\n logo?: string;\n /** True when at least one account is active. */\n connected: boolean;\n accounts: IntegrationAccount[];\n /** Event types the developer allows this tenant to switch on. */\n availableTriggers?: string[];\n}\n"],"names":["AgentThreadState"],"mappings":";;AAoCA;AACM,SAAU,oBAAoB,CAAC,IAAiB,EAAA;IAKpD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE;QACvC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,OAAO;KAC5C;AACH;AA+iBA;;AAEG;AACSA;AAAZ,CAAA,UAAY,gBAAgB,EAAA;AAC1B,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,WAAA,CAAA,GAAA,WAAuB;AACvB,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,QAAA,CAAA,GAAA,QAAiB;AACjB,IAAA,gBAAA,CAAA,qBAAA,CAAA,GAAA,qBAA2C;AAC3C,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACvC,IAAA,gBAAA,CAAA,sBAAA,CAAA,GAAA,sBAA6C;AAC7C,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACvC,IAAA,gBAAA,CAAA,YAAA,CAAA,GAAA,YAAyB;AACzB,IAAA,gBAAA,CAAA,mBAAA,CAAA,GAAA,mBAAuC;AACzC,CAAC,EAbWA,wBAAgB,KAAhBA,wBAAgB,GAAA,EAAA,CAAA,CAAA;;;;"}
|
|
@@ -5,6 +5,7 @@ var React = require('react');
|
|
|
5
5
|
var useDevicChat = require('../../hooks/useDevicChat.js');
|
|
6
6
|
var DevicContext = require('../../provider/DevicContext.js');
|
|
7
7
|
var client = require('../../api/client.js');
|
|
8
|
+
var assistantInfo = require('../../api/assistantInfo.js');
|
|
8
9
|
var ChatMessages = require('./ChatMessages.js');
|
|
9
10
|
var ChatInput = require('./ChatInput.js');
|
|
10
11
|
var ConversationSelector = require('./ConversationSelector.js');
|
|
@@ -185,10 +186,50 @@ function ChatDrawerInner({ assistantId, chatUid: initialChatUid, options = {}, e
|
|
|
185
186
|
const resolvedTenantSession = context?.getTenantSession;
|
|
186
187
|
const onSessionExpired = context?.onSessionExpired;
|
|
187
188
|
const resolvedBaseUrl = baseUrl || context?.baseUrl || 'https://api.devic.ai';
|
|
188
|
-
const [avatarUrl, setAvatarUrl] = React.useState(null);
|
|
189
189
|
const [coreMemoryOpen, setCoreMemoryOpen] = React.useState(false);
|
|
190
190
|
const [integrationsOpen, setIntegrationsOpen] = React.useState(false);
|
|
191
|
-
const
|
|
191
|
+
const infoClient = React.useMemo(() => resolvedApiKey || resolvedTenantSession
|
|
192
|
+
? new client.DevicApiClient({
|
|
193
|
+
apiKey: resolvedApiKey,
|
|
194
|
+
baseUrl: resolvedBaseUrl,
|
|
195
|
+
getTenantSession: resolvedTenantSession,
|
|
196
|
+
onSessionExpired,
|
|
197
|
+
})
|
|
198
|
+
: null, [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]);
|
|
199
|
+
// Asked for only when something on screen depends on it, and then at most
|
|
200
|
+
// once per assistant however many times this drawer is mounted — a host that
|
|
201
|
+
// remounts it to start a fresh conversation used to pay for the answer again
|
|
202
|
+
// every time.
|
|
203
|
+
const assistantInfo$1 = assistantInfo.useAssistantInfo({
|
|
204
|
+
assistantId,
|
|
205
|
+
client: infoClient,
|
|
206
|
+
baseUrl: resolvedBaseUrl,
|
|
207
|
+
credential: resolvedApiKey || 'session',
|
|
208
|
+
enabled: !!mergedOptions.showAvatar ||
|
|
209
|
+
(mergedOptions.showIntegrationsButton !== false && isOpen),
|
|
210
|
+
});
|
|
211
|
+
const avatarUrl = mergedOptions.showAvatar
|
|
212
|
+
? (assistantInfo$1.assistant?.imgUrl ?? null)
|
|
213
|
+
: null;
|
|
214
|
+
/**
|
|
215
|
+
* Whether to ask which apps this assistant offers.
|
|
216
|
+
*
|
|
217
|
+
* The listing is worth a request only when there is something to list, and
|
|
218
|
+
* most assistants offer nothing — for those, the call existed purely to be
|
|
219
|
+
* refused, once per page load. The assistant now says so itself, so a plain
|
|
220
|
+
* `false` settles it without asking.
|
|
221
|
+
*
|
|
222
|
+
* Anything else asks, exactly as before: a field that is absent means the API
|
|
223
|
+
* is older than it, not that the answer is no, and a failed lookup means we
|
|
224
|
+
* could not find out. Hiding the button on either would lose the feature for
|
|
225
|
+
* a deployment that has it, which is far worse than one spare request.
|
|
226
|
+
*
|
|
227
|
+
* Where there are apps to show, this puts the two requests in sequence rather
|
|
228
|
+
* than at once, so the listing lands later than it used to. The header holds
|
|
229
|
+
* its place in the meantime — see `pendingIntegrations`.
|
|
230
|
+
*/
|
|
231
|
+
const mayOfferIntegrations = assistantInfo$1.settled &&
|
|
232
|
+
assistantInfo$1.assistant?.tenantIntegrations?.enabled !== false;
|
|
192
233
|
// The apps this assistant offers its tenants. Loaded once, here, because the
|
|
193
234
|
// header control cannot decide whether to exist without it — and lent to the
|
|
194
235
|
// modal so opening it does not ask for the same listing again. Nothing is
|
|
@@ -199,20 +240,23 @@ function ChatDrawerInner({ assistantId, chatUid: initialChatUid, options = {}, e
|
|
|
199
240
|
subtenantId,
|
|
200
241
|
apiKey: resolvedApiKey,
|
|
201
242
|
baseUrl: resolvedBaseUrl,
|
|
202
|
-
enabled: mergedOptions.showIntegrationsButton !== false &&
|
|
243
|
+
enabled: mergedOptions.showIntegrationsButton !== false &&
|
|
244
|
+
isOpen &&
|
|
245
|
+
mayOfferIntegrations,
|
|
203
246
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Placeholder chips to hold while the listing is in flight.
|
|
249
|
+
*
|
|
250
|
+
* Only when the assistant has said outright that it offers apps, and how
|
|
251
|
+
* many: on a maybe there would be nothing to hold the place of half the time,
|
|
252
|
+
* and a control that appears and then vanishes is worse than one that arrives
|
|
253
|
+
* late. So this stays at zero for an API that does not say, which is also the
|
|
254
|
+
* behaviour every version until now had.
|
|
255
|
+
*/
|
|
256
|
+
const pendingIntegrations = assistantInfo$1.assistant?.tenantIntegrations?.enabled === true &&
|
|
257
|
+
!integrationsState.settled
|
|
258
|
+
? (assistantInfo$1.assistant.tenantIntegrations.count ?? 0)
|
|
259
|
+
: 0;
|
|
216
260
|
// Tenant/subtenant resolution mirrors useDevicChat (prop overrides provider).
|
|
217
261
|
const resolvedTenantId = tenantId || context?.tenantId;
|
|
218
262
|
const resolvedSubtenantId = subtenantId || context?.subtenantId;
|
|
@@ -487,7 +531,7 @@ function ChatDrawerInner({ assistantId, chatUid: initialChatUid, options = {}, e
|
|
|
487
531
|
[mergedOptions.position]: 20,
|
|
488
532
|
bottom: 20,
|
|
489
533
|
}), [mergedOptions.zIndex, mergedOptions.position]);
|
|
490
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isInline && (jsxRuntime.jsx("div", { className: "devic-drawer-overlay", "data-open": isOpen, style: overlayStyle, onClick: handleClose })), jsxRuntime.jsxs("div", { ref: drawerRef, className: `devic-chat-drawer ${className || ''}`, "data-position": mergedOptions.position, "data-open": isOpen, "data-mode": mode, style: drawerStyle, children: [mergedOptions.resizable && (jsxRuntime.jsx("div", { className: "devic-resize-handle", "data-position": mergedOptions.position, onMouseDown: handleResizeStart })), jsxRuntime.jsxs("div", { className: "devic-drawer-header", children: [avatarUrl && (jsxRuntime.jsx("img", { className: "devic-drawer-avatar", src: avatarUrl, alt: "", "aria-hidden": "true" })), jsxRuntime.jsx("h2", { className: "devic-drawer-title", children: mergedOptions.title }), jsxRuntime.jsx(ConversationSelector.ConversationSelector, { assistantId: assistantId, currentChatUid: chat.chatUid, onSelect: handleConversationSelect, onNewChat: handleNewChat, apiKey: apiKey, baseUrl: baseUrl, tenantId: tenantId, subtenantId: subtenantId, conversationPreview: mergedOptions.conversationPreview }), jsxRuntime.jsxs("div", { className: "devic-drawer-header-actions", children: [mergedOptions.showIntegrationsButton !== false && (jsxRuntime.jsx(IntegrationsLauncher.IntegrationsLauncher, { state: integrationsState, onClick: () => setIntegrationsOpen(true), label: mergedOptions.integrationsLabel, maxLogos: mergedOptions.maxIntegrationLogos, dark: theme.isDarkTheme(modalTheme) })), mergedOptions.showCoreMemoryButton && (jsxRuntime.jsx("button", { className: "devic-new-chat-btn", onClick: () => setCoreMemoryOpen(true), type: "button", "aria-label": "Assistant memory", title: "Assistant memory", children: jsxRuntime.jsx(BrainIcon, {}) })), jsxRuntime.jsx("button", { className: "devic-new-chat-btn", onClick: handleNewChat, type: "button", "aria-label": "New chat", title: "New chat", children: jsxRuntime.jsx(PlusIcon, {}) }), !isInline && (jsxRuntime.jsx("button", { className: "devic-drawer-close", onClick: handleClose, type: "button", "aria-label": "Close chat", children: jsxRuntime.jsx(CloseIcon, {}) }))] })] }), chat.error && (jsxRuntime.jsx("div", { className: "devic-error", children: chat.error.message })), jsxRuntime.jsx(ChatMessages.ChatMessages, { messages: chat.messages, allMessages: chat.messages, isLoading: chat.isLoading, welcomeMessage: mergedOptions.welcomeMessage, suggestedMessages: mergedOptions.suggestedMessages, onSuggestedClick: handleSuggestedClick, showToolTimeline: mergedOptions.showToolTimeline, toolRenderers: mergedOptions.toolRenderers, toolIcons: mergedOptions.toolIcons, loadingIndicator: mergedOptions.loadingIndicator, showFeedback: mergedOptions.showFeedback, feedbackMap: feedbackMap, onFeedback: handleFeedback, handedOffSubThreadId: chat.handedOffSubThreadId || undefined, onHandoffCompleted: chat.onHandoffCompleted, handoffWidgetRenderer: mergedOptions.handoffWidgetRenderer, toolGroups: mergedOptions.toolGroups, userMessageRenderer: mergedOptions.userMessageRenderer, assistantMessageRenderer: mergedOptions.assistantMessageRenderer, apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl, pendingInlineWidgets: inlineWidgets, onSubmitWidget: chat.submitWidgetResponse, onCancelWidget: chat.cancelWidgetCall, recalledMemories: mergedOptions.showRecalledMemories
|
|
534
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isInline && (jsxRuntime.jsx("div", { className: "devic-drawer-overlay", "data-open": isOpen, style: overlayStyle, onClick: handleClose })), jsxRuntime.jsxs("div", { ref: drawerRef, className: `devic-chat-drawer ${className || ''}`, "data-position": mergedOptions.position, "data-open": isOpen, "data-mode": mode, style: drawerStyle, children: [mergedOptions.resizable && (jsxRuntime.jsx("div", { className: "devic-resize-handle", "data-position": mergedOptions.position, onMouseDown: handleResizeStart })), jsxRuntime.jsxs("div", { className: "devic-drawer-header", children: [avatarUrl && (jsxRuntime.jsx("img", { className: "devic-drawer-avatar", src: avatarUrl, alt: "", "aria-hidden": "true" })), jsxRuntime.jsx("h2", { className: "devic-drawer-title", children: mergedOptions.title }), jsxRuntime.jsx(ConversationSelector.ConversationSelector, { assistantId: assistantId, currentChatUid: chat.chatUid, onSelect: handleConversationSelect, onNewChat: handleNewChat, apiKey: apiKey, baseUrl: baseUrl, tenantId: tenantId, subtenantId: subtenantId, conversationPreview: mergedOptions.conversationPreview }), jsxRuntime.jsxs("div", { className: "devic-drawer-header-actions", children: [mergedOptions.showIntegrationsButton !== false && (jsxRuntime.jsx(IntegrationsLauncher.IntegrationsLauncher, { state: integrationsState, onClick: () => setIntegrationsOpen(true), label: mergedOptions.integrationsLabel, maxLogos: mergedOptions.maxIntegrationLogos, dark: theme.isDarkTheme(modalTheme), placeholders: pendingIntegrations })), mergedOptions.showCoreMemoryButton && (jsxRuntime.jsx("button", { className: "devic-new-chat-btn", onClick: () => setCoreMemoryOpen(true), type: "button", "aria-label": "Assistant memory", title: "Assistant memory", children: jsxRuntime.jsx(BrainIcon, {}) })), jsxRuntime.jsx("button", { className: "devic-new-chat-btn", onClick: handleNewChat, type: "button", "aria-label": "New chat", title: "New chat", children: jsxRuntime.jsx(PlusIcon, {}) }), !isInline && (jsxRuntime.jsx("button", { className: "devic-drawer-close", onClick: handleClose, type: "button", "aria-label": "Close chat", children: jsxRuntime.jsx(CloseIcon, {}) }))] })] }), chat.error && (jsxRuntime.jsx("div", { className: "devic-error", children: chat.error.message })), jsxRuntime.jsx(ChatMessages.ChatMessages, { messages: chat.messages, allMessages: chat.messages, isLoading: chat.isLoading, welcomeMessage: mergedOptions.welcomeMessage, suggestedMessages: mergedOptions.suggestedMessages, onSuggestedClick: handleSuggestedClick, showToolTimeline: mergedOptions.showToolTimeline, toolRenderers: mergedOptions.toolRenderers, toolIcons: mergedOptions.toolIcons, loadingIndicator: mergedOptions.loadingIndicator, showFeedback: mergedOptions.showFeedback, feedbackMap: feedbackMap, onFeedback: handleFeedback, handedOffSubThreadId: chat.handedOffSubThreadId || undefined, onHandoffCompleted: chat.onHandoffCompleted, handoffWidgetRenderer: mergedOptions.handoffWidgetRenderer, toolGroups: mergedOptions.toolGroups, userMessageRenderer: mergedOptions.userMessageRenderer, assistantMessageRenderer: mergedOptions.assistantMessageRenderer, apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl, pendingInlineWidgets: inlineWidgets, onSubmitWidget: chat.submitWidgetResponse, onCancelWidget: chat.cancelWidgetCall, recalledMemories: mergedOptions.showRecalledMemories
|
|
491
535
|
? chat.recalledMemories
|
|
492
536
|
: undefined, recalledMemoriesRenderer: mergedOptions.recalledMemoriesRenderer }), mergedOptions.customPromptBox ? (jsxRuntime.jsxs("div", { className: "devic-input-area", children: [limitBannerNode, usageBarNode, integrationsHintNode, mergedOptions.customPromptBox({
|
|
493
537
|
sendMessage: handleSend,
|