fauna-api 1.0.780 → 1.0.783
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/dist/src/cicada/conversations.d.ts +2 -0
- package/dist/src/marmot/browser.d.ts +1 -3
- package/dist/src/marmot/browser.js +2 -3
- package/dist/src/termite/os.js +3 -3
- package/dist/src/ui/browser.d.ts +2 -0
- package/dist/src/ui/browser.js +4 -1
- package/dist/src/ui/chat.js +27 -13
- package/dist/src/ui/chatPanel.js +14 -0
- package/dist/src/ui/chatSession.d.ts +26 -0
- package/dist/src/ui/chatSession.js +241 -0
- package/dist/src/ui/ui.css +94 -0
- package/package.json +1 -1
|
@@ -37,6 +37,8 @@ export type Message = {
|
|
|
37
37
|
attachments: FaunaFile[];
|
|
38
38
|
/** Sent by the inbox agent. */
|
|
39
39
|
agent: boolean;
|
|
40
|
+
/** Why it never reached them, when its carrier refused it or it failed to send. */
|
|
41
|
+
undelivered?: string;
|
|
40
42
|
};
|
|
41
43
|
export declare function listInbox(authorization: Credentials | ApiKey, inboxId?: string): Promise<Conversation[]>;
|
|
42
44
|
export declare function listArchived(authorization: Credentials | ApiKey, inboxId?: string): Promise<Conversation[]>;
|
|
@@ -113,7 +113,7 @@ export declare class PageRoom<T extends BrowserTool = BrowserTool> extends Room
|
|
|
113
113
|
parrotTools(): Tool[];
|
|
114
114
|
handleEvent(packet: any): boolean;
|
|
115
115
|
}
|
|
116
|
-
/** A live web page in
|
|
116
|
+
/** A live web page in Marmot's browser: every browser tool. */
|
|
117
117
|
export declare class BrowserRoom extends PageRoom<BrowserTool> {
|
|
118
118
|
static connect(authorization: Credentials | ApiKey, target: {
|
|
119
119
|
reference: string;
|
|
@@ -125,13 +125,11 @@ export declare class BrowserRoom extends PageRoom<BrowserTool> {
|
|
|
125
125
|
room: BrowserRoom;
|
|
126
126
|
self: Presence;
|
|
127
127
|
}>;
|
|
128
|
-
/** `endpoint` is where the page's room lives: Marmot's by default, or e.g. Ant's /timeline/browser/live. */
|
|
129
128
|
static join(authorization: Credentials | ApiKey, target: {
|
|
130
129
|
accessCode: string;
|
|
131
130
|
}, opts?: {
|
|
132
131
|
tools?: BrowserTool["kind"][];
|
|
133
132
|
hooks?: BrowserRoomHooks;
|
|
134
|
-
endpoint?: string;
|
|
135
133
|
}): Promise<{
|
|
136
134
|
room: BrowserRoom;
|
|
137
135
|
self: Presence;
|
|
@@ -337,12 +337,11 @@ export class PageRoom extends Room {
|
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
/** A live web page in
|
|
340
|
+
/** A live web page in Marmot's browser: every browser tool. */
|
|
341
341
|
export class BrowserRoom extends PageRoom {
|
|
342
342
|
static async connect(authorization, target, opts) {
|
|
343
343
|
return openBrowser(authorization, { server: target.reference, group: opts?.group?.id }, opts);
|
|
344
344
|
}
|
|
345
|
-
/** `endpoint` is where the page's room lives: Marmot's by default, or e.g. Ant's /timeline/browser/live. */
|
|
346
345
|
static async join(authorization, target, opts) {
|
|
347
346
|
return openBrowser(authorization, { accessCode: target.accessCode }, opts);
|
|
348
347
|
}
|
|
@@ -352,7 +351,7 @@ async function openBrowser(authorization, target, opts) {
|
|
|
352
351
|
const auth = "accessCode" in target
|
|
353
352
|
? { authorization, accessCode: target.accessCode }
|
|
354
353
|
: { authorization, server: target.server, ...(target.group ? { group: target.group } : {}) };
|
|
355
|
-
const endpoint =
|
|
354
|
+
const endpoint = `${Marmot.getURL()}/browser/live`;
|
|
356
355
|
let room;
|
|
357
356
|
const notify = () => { if (room)
|
|
358
357
|
room.emitPresence(); };
|
package/dist/src/termite/os.js
CHANGED
|
@@ -192,18 +192,18 @@ export function desktopTool(repository) {
|
|
|
192
192
|
if (repository) {
|
|
193
193
|
return {
|
|
194
194
|
name: "use_desktop",
|
|
195
|
-
description: `Do something on the repository's desktop that needs a screen, like using
|
|
195
|
+
description: `Do something on the repository's desktop that needs a screen, like using an app on the machine or working across its windows. A desktop agent does it and reports back. For a website alone, use_browser is quicker. ${DESKTOP_CAPTURE}`,
|
|
196
196
|
parameters: { type: "OBJECT", properties, required: ["goal"] },
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
return {
|
|
200
200
|
name: "use_desktop",
|
|
201
|
-
description: `Do something on a computer that needs a screen, like
|
|
201
|
+
description: `Do something on a computer that needs a screen, like running an app or working across windows. This chat has no computer yet, so the person is asked first to make a repository for it: a Linux machine with a desktop. If they agree, a desktop agent does it and reports back; if they don't, do that request without a computer. For a website alone, use use_browser, which needs no computer. ${DESKTOP_CAPTURE}`,
|
|
202
202
|
parameters: {
|
|
203
203
|
type: "OBJECT",
|
|
204
204
|
properties: {
|
|
205
205
|
...properties,
|
|
206
|
-
name: { type: "STRING", description: "What to call the repository this makes: two to four words about the task, like
|
|
206
|
+
name: { type: "STRING", description: "What to call the repository this makes: two to four words about the task, like Invoice spreadsheet cleanup" },
|
|
207
207
|
},
|
|
208
208
|
required: ["goal", "name"],
|
|
209
209
|
},
|
package/dist/src/ui/browser.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export type BrowserMountOptions<R extends PageRoom<any> = PageRoom> = {
|
|
|
48
48
|
/** The page's real pointer moved or changed shape. The browser takes the room's
|
|
49
49
|
* `onCursor` hook, so follow the pointer here. */
|
|
50
50
|
onCursor?: (pointer: HtmlPointer) => void;
|
|
51
|
+
/** The page closed. The browser takes the room's `onClosed` hook, so hear it here. */
|
|
52
|
+
onClosed?: () => void;
|
|
51
53
|
};
|
|
52
54
|
export declare function cursorSeed(options: {
|
|
53
55
|
seed?: string;
|
package/dist/src/ui/browser.js
CHANGED
|
@@ -466,7 +466,10 @@ export function createBrowser(container, options) {
|
|
|
466
466
|
room.watch({
|
|
467
467
|
onCursor: (_r, pointer) => applyCursor(pointer),
|
|
468
468
|
onTool: (_r, tool) => applyTool(tool),
|
|
469
|
-
onClosed: () => {
|
|
469
|
+
onClosed: () => {
|
|
470
|
+
badge.style.display = "none";
|
|
471
|
+
options.onClosed?.();
|
|
472
|
+
},
|
|
470
473
|
web: {
|
|
471
474
|
onBootstrap: (_web, meta) => { void applyBootstrap(meta.html, meta.viewport); },
|
|
472
475
|
onMutation: (_web, mutation) => {
|
package/dist/src/ui/chat.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Fauna } from "fauna-api";
|
|
|
7
7
|
import { mimeFromFileName } from "../marmot/media.js";
|
|
8
8
|
import { createTokenizedInput, parseRichText, toPlainText, hasTokens } from "./tokenized.js";
|
|
9
9
|
import { roomPartsToChatFields } from "./chatSequence.js";
|
|
10
|
+
import { SessionPopout } from "./chatSession.js";
|
|
10
11
|
import { ChatPanel, askForComputer, attachmentOf, buildAttachmentCard, chatToolLabel, createPanelBus, execOutput, mountRepositoryControl, openViewModal, paintPreview, roomHas, showPreview } from "./chatPanel.js";
|
|
11
12
|
export { RepositoryControl, askForComputer, pickedRepository } from "./chatPanel.js";
|
|
12
13
|
import { createTooltip } from "./hover.js";
|
|
@@ -105,8 +106,12 @@ function renderTool(call) {
|
|
|
105
106
|
}
|
|
106
107
|
return toolChip(call, 'build', status === 'error' ? `${name} failed` : name);
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
/** A tool call's element; one that opened a live room pops it out when its chip is clicked. */
|
|
110
|
+
function renderToolCallElement(call, override, sessions) {
|
|
111
|
+
const el = override?.(call) ?? renderTool(call);
|
|
112
|
+
if (call.session && call.id && sessions)
|
|
113
|
+
sessions.decorate(el, call.id, call.session);
|
|
114
|
+
return el;
|
|
110
115
|
}
|
|
111
116
|
function buildFileElement(file, iconMap, cards) {
|
|
112
117
|
const code = typeof file.part?.accessCode === 'string' ? file.part.accessCode : '';
|
|
@@ -277,7 +282,7 @@ function appendTextBlock(el, text, message, onTokenClick) {
|
|
|
277
282
|
});
|
|
278
283
|
el.appendChild(textEl);
|
|
279
284
|
}
|
|
280
|
-
async function buildMessageElement(message, iconMap, modelIcons, renderToolCall, mode, selfUserId, onTokenClick, fileCards) {
|
|
285
|
+
async function buildMessageElement(message, iconMap, modelIcons, renderToolCall, mode, selfUserId, onTokenClick, fileCards, sessions) {
|
|
281
286
|
const incoming = isIncomingAuthor(message, chatMode(mode), selfUserId);
|
|
282
287
|
const el = document.createElement('div');
|
|
283
288
|
el.className = (message.role === 'user' && !incoming) ? 'chat-message-user' : 'chat-message-model';
|
|
@@ -288,7 +293,7 @@ async function buildMessageElement(message, iconMap, modelIcons, renderToolCall,
|
|
|
288
293
|
if (block.type === 'text')
|
|
289
294
|
appendTextBlock(el, block.text, message, onTokenClick);
|
|
290
295
|
else if (block.type === 'tool')
|
|
291
|
-
el.appendChild(renderToolCallElement(block.call, renderToolCall));
|
|
296
|
+
el.appendChild(renderToolCallElement(block.call, renderToolCall, sessions));
|
|
292
297
|
else
|
|
293
298
|
el.appendChild(buildFileElement(block, iconMap, fileCards));
|
|
294
299
|
}
|
|
@@ -298,7 +303,7 @@ async function buildMessageElement(message, iconMap, modelIcons, renderToolCall,
|
|
|
298
303
|
appendTextBlock(el, message.text, message, onTokenClick);
|
|
299
304
|
if (message.toolCalls) {
|
|
300
305
|
for (const call of message.toolCalls) {
|
|
301
|
-
el.appendChild(renderToolCallElement(call, renderToolCall));
|
|
306
|
+
el.appendChild(renderToolCallElement(call, renderToolCall, sessions));
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
309
|
if (message.files) {
|
|
@@ -332,7 +337,7 @@ function autoScroll(messagesEl, stick = nearBottom(messagesEl)) {
|
|
|
332
337
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
|
-
function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
|
|
340
|
+
function createStreamRenderer(modelEl, messagesEl, renderToolCall, sessions) {
|
|
336
341
|
// Text is rendered in *segments* — each run of text between tool calls
|
|
337
342
|
// gets its own .chat-message-text div. This way tool chips persist in the
|
|
338
343
|
// DOM between segments, and the visual paragraph break between pre- and
|
|
@@ -427,7 +432,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
|
|
|
427
432
|
Object.assign(stored, call, { id });
|
|
428
433
|
if (call.status === 'error')
|
|
429
434
|
console.error('[chat] tool failed', call.name, call.result ?? '');
|
|
430
|
-
const el = renderToolCallElement(view, renderToolCall);
|
|
435
|
+
const el = renderToolCallElement(view, renderToolCall, sessions);
|
|
431
436
|
existing.replaceWith(el);
|
|
432
437
|
toolElements.set(id, el);
|
|
433
438
|
continue;
|
|
@@ -444,7 +449,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
|
|
|
444
449
|
}
|
|
445
450
|
const stored = { ...call, id };
|
|
446
451
|
toolCalls.push(stored);
|
|
447
|
-
const chip = renderToolCallElement(view, renderToolCall);
|
|
452
|
+
const chip = renderToolCallElement(view, renderToolCall, sessions);
|
|
448
453
|
toolElements.set(id, chip);
|
|
449
454
|
modelEl.appendChild(chip);
|
|
450
455
|
gsap.from(chip, { opacity: 0, x: -10, duration: 0.3, ease: 'power2.out' });
|
|
@@ -749,6 +754,13 @@ class ChatEngine {
|
|
|
749
754
|
openViewModal(attachment);
|
|
750
755
|
}
|
|
751
756
|
});
|
|
757
|
+
/** The live rooms tools open, popped out of their chips. */
|
|
758
|
+
Object.defineProperty(this, "sessions", {
|
|
759
|
+
enumerable: true,
|
|
760
|
+
configurable: true,
|
|
761
|
+
writable: true,
|
|
762
|
+
value: void 0
|
|
763
|
+
});
|
|
752
764
|
// ── Send button ──
|
|
753
765
|
Object.defineProperty(this, "sendTooltip", {
|
|
754
766
|
enumerable: true,
|
|
@@ -756,6 +768,7 @@ class ChatEngine {
|
|
|
756
768
|
writable: true,
|
|
757
769
|
value: null
|
|
758
770
|
});
|
|
771
|
+
this.sessions = new SessionPopout(refs.root, () => chatAuthorization(this.options));
|
|
759
772
|
const locked = lockedModels(options.models);
|
|
760
773
|
const start = options.model && (!locked || locked.includes(options.model)) ? options.model : Fauna.Parrot.Generative.Text.AUTO;
|
|
761
774
|
this.currentModel = locked?.length === 1 ? locked[0] : start;
|
|
@@ -784,7 +797,7 @@ class ChatEngine {
|
|
|
784
797
|
const gen = this.generation;
|
|
785
798
|
for (const message of options.initialHistory) {
|
|
786
799
|
this.history.push(message);
|
|
787
|
-
buildMessageElement(message, options.acceptedTypes, this.modelIcons, options.renderToolCall, options.mode, options.room?.self.profile.email, options.onTokenClick, this.fileCards).then((el) => {
|
|
800
|
+
buildMessageElement(message, options.acceptedTypes, this.modelIcons, options.renderToolCall, options.mode, options.room?.self.profile.email, options.onTokenClick, this.fileCards, this.sessions).then((el) => {
|
|
788
801
|
if (this.destroyed || this.generation !== gen)
|
|
789
802
|
return;
|
|
790
803
|
refs.messagesEl.appendChild(el);
|
|
@@ -1222,7 +1235,7 @@ class ChatEngine {
|
|
|
1222
1235
|
this.refs.messagesEl.appendChild(modelEl);
|
|
1223
1236
|
gsap.from(modelEl, { duration: 0.5, y: 100, opacity: 0, ease: 'power2.inOut' });
|
|
1224
1237
|
gsap.to(this.refs.messagesEl, { duration: 0.5, scrollTop: this.refs.messagesEl.scrollHeight, ease: 'power2.inOut' });
|
|
1225
|
-
const renderer = createStreamRenderer(modelEl, this.refs.messagesEl, this.options.renderToolCall);
|
|
1238
|
+
const renderer = createStreamRenderer(modelEl, this.refs.messagesEl, this.options.renderToolCall, this.sessions);
|
|
1226
1239
|
const isStale = () => this.destroyed || this.generation !== gen;
|
|
1227
1240
|
const usedModel = this.currentModel;
|
|
1228
1241
|
try {
|
|
@@ -1338,7 +1351,7 @@ class ChatEngine {
|
|
|
1338
1351
|
gsap.from(el, { duration: 0.4, y: 40, opacity: 0, ease: 'power2.out' });
|
|
1339
1352
|
}
|
|
1340
1353
|
else {
|
|
1341
|
-
buildMessageElement(chat, this.options.acceptedTypes, this.modelIcons, this.options.renderToolCall, this.options.mode, this.options.room?.self.profile.email, this.options.onTokenClick, this.fileCards).then((built) => {
|
|
1354
|
+
buildMessageElement(chat, this.options.acceptedTypes, this.modelIcons, this.options.renderToolCall, this.options.mode, this.options.room?.self.profile.email, this.options.onTokenClick, this.fileCards, this.sessions).then((built) => {
|
|
1342
1355
|
if (this.destroyed || this.generation !== gen)
|
|
1343
1356
|
return;
|
|
1344
1357
|
if (this.paintedMessage(messageId))
|
|
@@ -1718,7 +1731,7 @@ class ChatEngine {
|
|
|
1718
1731
|
stampModelAvatar(el, this.currentModel, this.modelIcons);
|
|
1719
1732
|
this.refs.messagesEl.appendChild(el);
|
|
1720
1733
|
gsap.from(el, { duration: 0.4, y: 40, opacity: 0, ease: 'power2.out' });
|
|
1721
|
-
this.roomStream = { turn, el, renderer: createStreamRenderer(el, this.refs.messagesEl, this.options.renderToolCall) };
|
|
1734
|
+
this.roomStream = { turn, el, renderer: createStreamRenderer(el, this.refs.messagesEl, this.options.renderToolCall, this.sessions) };
|
|
1722
1735
|
}
|
|
1723
1736
|
this.roomStream.renderer.syncToolCalls(this.toolsWithProgress(tools));
|
|
1724
1737
|
if (files?.length)
|
|
@@ -2035,7 +2048,7 @@ class ChatEngine {
|
|
|
2035
2048
|
model: role === 'model' ? this.currentModel : undefined,
|
|
2036
2049
|
};
|
|
2037
2050
|
this.history.push(message);
|
|
2038
|
-
buildMessageElement(message, this.options.acceptedTypes, this.modelIcons, this.options.renderToolCall, this.options.mode, this.options.room?.self.profile.email, this.options.onTokenClick, this.fileCards).then((el) => {
|
|
2051
|
+
buildMessageElement(message, this.options.acceptedTypes, this.modelIcons, this.options.renderToolCall, this.options.mode, this.options.room?.self.profile.email, this.options.onTokenClick, this.fileCards, this.sessions).then((el) => {
|
|
2039
2052
|
if (message.model)
|
|
2040
2053
|
el.dataset.model = message.model;
|
|
2041
2054
|
this.refs.messagesEl.appendChild(el);
|
|
@@ -2139,6 +2152,7 @@ class ChatEngine {
|
|
|
2139
2152
|
}
|
|
2140
2153
|
destroy() {
|
|
2141
2154
|
this.destroyed = true;
|
|
2155
|
+
this.sessions.destroy();
|
|
2142
2156
|
this.generation++;
|
|
2143
2157
|
this.generating = false;
|
|
2144
2158
|
this.steerTarget = null;
|
package/dist/src/ui/chatPanel.js
CHANGED
|
@@ -697,6 +697,15 @@ function short(text, max = 60) {
|
|
|
697
697
|
function baseName(path) {
|
|
698
698
|
return path.split("/").filter(Boolean).pop() ?? path;
|
|
699
699
|
}
|
|
700
|
+
/** A page's site, like example.com, for a chip. */
|
|
701
|
+
function siteOf(url) {
|
|
702
|
+
try {
|
|
703
|
+
return new URL(url).hostname || short(url);
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
return short(url) || "the page";
|
|
707
|
+
}
|
|
708
|
+
}
|
|
700
709
|
/** The icon and words for a chat's file and repository tools, or null for other tools. */
|
|
701
710
|
export function chatToolLabel(call) {
|
|
702
711
|
const args = call.args ?? {};
|
|
@@ -753,6 +762,11 @@ export function chatToolLabel(call) {
|
|
|
753
762
|
out = { icon: "save", label: pick(`Saving ${name}`, `Saved ${name} to the repository`, `Couldn't save ${name}`) };
|
|
754
763
|
break;
|
|
755
764
|
}
|
|
765
|
+
case "use_browser": {
|
|
766
|
+
const site = siteOf(String(args.url ?? ""));
|
|
767
|
+
out = { icon: "travel_explore", label: pick(`Browsing ${site}`, `Browsed ${site}`, `Couldn't open ${site}`) };
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
756
770
|
case "use_desktop":
|
|
757
771
|
if (done && call.result === Fauna.Termite.OS.DESKTOP_DECLINED)
|
|
758
772
|
out = { icon: "desktop_access_disabled", label: "Went without a computer" };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Fauna } from "fauna-api";
|
|
2
|
+
import type { ToolSession } from "./chatSequence.js";
|
|
3
|
+
type Authorization = Fauna.Authentification.Credentials | Fauna.Authentification.ApiKey;
|
|
4
|
+
/** What a session's panel is called: the site a browser is on, or the desktop. */
|
|
5
|
+
export declare function sessionTitle(session: ToolSession): string;
|
|
6
|
+
/**
|
|
7
|
+
* Pops the live room a tool opened (a browser page, a machine's desktop) out of its chip,
|
|
8
|
+
* inside the chat. One at a time; clicking the chip again, the close button or Escape puts it
|
|
9
|
+
* back. Chips are replaced as their tool's status changes, so each is found again by its tool id.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SessionPopout {
|
|
12
|
+
private root;
|
|
13
|
+
private authorization;
|
|
14
|
+
private open;
|
|
15
|
+
constructor(root: HTMLElement, authorization: () => Authorization | undefined);
|
|
16
|
+
/** Makes a tool's chip a button that pops out its room. */
|
|
17
|
+
decorate(element: HTMLElement, id: string, session: ToolSession): void;
|
|
18
|
+
private chip;
|
|
19
|
+
private mark;
|
|
20
|
+
toggle(id: string, session: ToolSession, chip: HTMLElement): void;
|
|
21
|
+
private show;
|
|
22
|
+
/** Puts the open panel back into its chip and leaves its room. */
|
|
23
|
+
close(): void;
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import gsap from "gsap";
|
|
2
|
+
import { Fauna } from "fauna-api";
|
|
3
|
+
import { createBrowser } from "./browser.js";
|
|
4
|
+
/** What a desktop draws at before its first frame says. */
|
|
5
|
+
const DESKTOP = { width: 1440, height: 900 };
|
|
6
|
+
const OPEN_SECONDS = 0.36;
|
|
7
|
+
const CLOSE_SECONDS = 0.26;
|
|
8
|
+
function reducedMotion() {
|
|
9
|
+
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
10
|
+
}
|
|
11
|
+
function host(url) {
|
|
12
|
+
try {
|
|
13
|
+
return new URL(url ?? "").hostname || "the page";
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return "the page";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** What a session's panel is called: the site a browser is on, or the desktop. */
|
|
20
|
+
export function sessionTitle(session) {
|
|
21
|
+
return session.kind === "desktop" ? "Desktop" : host(session.url);
|
|
22
|
+
}
|
|
23
|
+
/** The transform that puts `panel` where `chip` is, for growing out of it and back. */
|
|
24
|
+
function fromChip(panel, chip) {
|
|
25
|
+
const from = chip.getBoundingClientRect();
|
|
26
|
+
const to = panel.getBoundingClientRect();
|
|
27
|
+
return {
|
|
28
|
+
x: from.left - to.left,
|
|
29
|
+
y: from.top - to.top,
|
|
30
|
+
scaleX: from.width / Math.max(to.width, 1),
|
|
31
|
+
scaleY: from.height / Math.max(to.height, 1),
|
|
32
|
+
opacity: 0,
|
|
33
|
+
transformOrigin: "0 0",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pops the live room a tool opened (a browser page, a machine's desktop) out of its chip,
|
|
38
|
+
* inside the chat. One at a time; clicking the chip again, the close button or Escape puts it
|
|
39
|
+
* back. Chips are replaced as their tool's status changes, so each is found again by its tool id.
|
|
40
|
+
*/
|
|
41
|
+
export class SessionPopout {
|
|
42
|
+
constructor(root, authorization) {
|
|
43
|
+
Object.defineProperty(this, "root", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: root
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(this, "authorization", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: authorization
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "open", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: null
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Makes a tool's chip a button that pops out its room. */
|
|
63
|
+
decorate(element, id, session) {
|
|
64
|
+
const chip = element.classList.contains("chat-tool-chip")
|
|
65
|
+
? element
|
|
66
|
+
: element.querySelector(".chat-tool-chip");
|
|
67
|
+
if (!chip)
|
|
68
|
+
return;
|
|
69
|
+
const title = sessionTitle(session);
|
|
70
|
+
chip.classList.add("chat-tool-session");
|
|
71
|
+
chip.dataset.toolId = id;
|
|
72
|
+
chip.tabIndex = 0;
|
|
73
|
+
chip.setAttribute("role", "button");
|
|
74
|
+
chip.setAttribute("aria-label", `Watch ${title}`);
|
|
75
|
+
chip.setAttribute("aria-expanded", String(this.open?.id === id));
|
|
76
|
+
const icon = document.createElement("span");
|
|
77
|
+
icon.className = "material-symbols-outlined chat-tool-session-icon";
|
|
78
|
+
icon.textContent = "open_in_full";
|
|
79
|
+
chip.appendChild(icon);
|
|
80
|
+
const toggle = (event) => {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
event.stopPropagation();
|
|
83
|
+
this.toggle(id, session, chip);
|
|
84
|
+
};
|
|
85
|
+
chip.addEventListener("click", toggle);
|
|
86
|
+
chip.addEventListener("keydown", (event) => {
|
|
87
|
+
if (event.key === "Enter" || event.key === " ")
|
|
88
|
+
toggle(event);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
chip(id) {
|
|
92
|
+
return this.root.querySelector(`.chat-tool-session[data-tool-id="${CSS.escape(id)}"]`);
|
|
93
|
+
}
|
|
94
|
+
mark(id, expanded) {
|
|
95
|
+
this.chip(id)?.setAttribute("aria-expanded", String(expanded));
|
|
96
|
+
}
|
|
97
|
+
toggle(id, session, chip) {
|
|
98
|
+
const same = this.open?.id === id;
|
|
99
|
+
this.close();
|
|
100
|
+
if (!same)
|
|
101
|
+
this.show(id, session, chip);
|
|
102
|
+
}
|
|
103
|
+
show(id, session, chip) {
|
|
104
|
+
const panel = document.createElement("div");
|
|
105
|
+
panel.className = "chat-session";
|
|
106
|
+
panel.setAttribute("role", "dialog");
|
|
107
|
+
panel.setAttribute("aria-label", sessionTitle(session));
|
|
108
|
+
const bar = document.createElement("div");
|
|
109
|
+
bar.className = "chat-session-bar";
|
|
110
|
+
const icon = document.createElement("span");
|
|
111
|
+
icon.className = "material-symbols-outlined";
|
|
112
|
+
icon.textContent = session.kind === "desktop" ? "desktop_windows" : "travel_explore";
|
|
113
|
+
const heading = document.createElement("span");
|
|
114
|
+
heading.className = "chat-session-title";
|
|
115
|
+
heading.textContent = sessionTitle(session);
|
|
116
|
+
const close = document.createElement("button");
|
|
117
|
+
close.type = "button";
|
|
118
|
+
close.className = "chat-session-close";
|
|
119
|
+
close.setAttribute("aria-label", "Close");
|
|
120
|
+
close.innerHTML = '<span class="material-symbols-outlined">close_fullscreen</span>';
|
|
121
|
+
bar.append(icon, heading, close);
|
|
122
|
+
const area = document.createElement("div");
|
|
123
|
+
area.className = "chat-session-area";
|
|
124
|
+
const stage = document.createElement("div");
|
|
125
|
+
stage.className = session.kind === "desktop" ? "chat-session-stage chat-session-desktop" : "chat-session-stage";
|
|
126
|
+
area.appendChild(stage);
|
|
127
|
+
panel.append(bar, area);
|
|
128
|
+
this.root.appendChild(panel);
|
|
129
|
+
let room = null;
|
|
130
|
+
let browser = null;
|
|
131
|
+
let left = false;
|
|
132
|
+
const fit = () => {
|
|
133
|
+
if (session.kind !== "desktop")
|
|
134
|
+
return;
|
|
135
|
+
const size = room?.web.getBootstrap()?.viewport ?? DESKTOP;
|
|
136
|
+
const { width, height } = area.getBoundingClientRect();
|
|
137
|
+
const scale = Math.min(width / size.width, height / size.height) || 1;
|
|
138
|
+
stage.style.width = `${size.width}px`;
|
|
139
|
+
stage.style.height = `${size.height}px`;
|
|
140
|
+
stage.style.transform = `scale(${scale})`;
|
|
141
|
+
};
|
|
142
|
+
const sized = new ResizeObserver(fit);
|
|
143
|
+
sized.observe(area);
|
|
144
|
+
const ended = () => {
|
|
145
|
+
browser?.destroy();
|
|
146
|
+
browser = null;
|
|
147
|
+
stage.className = "chat-session-ended";
|
|
148
|
+
stage.removeAttribute("style");
|
|
149
|
+
stage.textContent = "This session has ended.";
|
|
150
|
+
};
|
|
151
|
+
const onKey = (event) => {
|
|
152
|
+
if (event.key === "Escape")
|
|
153
|
+
this.close();
|
|
154
|
+
};
|
|
155
|
+
document.addEventListener("keydown", onKey);
|
|
156
|
+
close.addEventListener("click", () => this.close());
|
|
157
|
+
this.open = {
|
|
158
|
+
id,
|
|
159
|
+
panel,
|
|
160
|
+
leave: () => {
|
|
161
|
+
left = true;
|
|
162
|
+
sized.disconnect();
|
|
163
|
+
document.removeEventListener("keydown", onKey);
|
|
164
|
+
browser?.destroy();
|
|
165
|
+
try {
|
|
166
|
+
room?.disconnect();
|
|
167
|
+
}
|
|
168
|
+
catch { /* already gone */ }
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
this.mark(id, true);
|
|
172
|
+
if (reducedMotion()) {
|
|
173
|
+
gsap.fromTo(panel, { opacity: 0 }, { opacity: 1, duration: 0.15 });
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
gsap.fromTo(panel, fromChip(panel, chip), {
|
|
177
|
+
x: 0, y: 0, scaleX: 1, scaleY: 1, opacity: 1,
|
|
178
|
+
duration: OPEN_SECONDS, ease: "power3.out", clearProps: "transform",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const authorization = this.authorization();
|
|
182
|
+
if (!authorization) {
|
|
183
|
+
ended();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
void (async () => {
|
|
187
|
+
try {
|
|
188
|
+
if (session.kind === "desktop") {
|
|
189
|
+
const joined = await Fauna.Termite.OS.MachineRoom.connect(authorization, { reference: session.reference });
|
|
190
|
+
if (left) {
|
|
191
|
+
joined.room.disconnect();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
room = joined.room;
|
|
195
|
+
browser = createBrowser(stage, { room: joined.room, pagePixels: true, onFrame: fit, onClosed: ended });
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const joined = await Fauna.Marmot.Browser.BrowserRoom.join(authorization, { accessCode: session.code ?? "" });
|
|
199
|
+
if (left) {
|
|
200
|
+
joined.room.disconnect();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
room = joined.room;
|
|
204
|
+
browser = createBrowser(stage, { room: joined.room, url: session.url, onClosed: ended });
|
|
205
|
+
}
|
|
206
|
+
fit();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
if (!left)
|
|
210
|
+
ended();
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
}
|
|
214
|
+
/** Puts the open panel back into its chip and leaves its room. */
|
|
215
|
+
close() {
|
|
216
|
+
const open = this.open;
|
|
217
|
+
if (!open)
|
|
218
|
+
return;
|
|
219
|
+
this.open = null;
|
|
220
|
+
open.leave();
|
|
221
|
+
this.mark(open.id, false);
|
|
222
|
+
const chip = this.chip(open.id);
|
|
223
|
+
const remove = () => open.panel.remove();
|
|
224
|
+
if (reducedMotion() || !chip) {
|
|
225
|
+
gsap.to(open.panel, { opacity: 0, duration: 0.15, onComplete: remove });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
gsap.to(open.panel, {
|
|
229
|
+
...fromChip(open.panel, chip),
|
|
230
|
+
duration: CLOSE_SECONDS,
|
|
231
|
+
ease: "power2.in",
|
|
232
|
+
onComplete: remove,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
destroy() {
|
|
236
|
+
const open = this.open;
|
|
237
|
+
this.open = null;
|
|
238
|
+
open?.leave();
|
|
239
|
+
open?.panel.remove();
|
|
240
|
+
}
|
|
241
|
+
}
|
package/dist/src/ui/ui.css
CHANGED
|
@@ -1305,6 +1305,100 @@ button.chat-message-file {
|
|
|
1305
1305
|
background: #fff7f6;
|
|
1306
1306
|
}
|
|
1307
1307
|
|
|
1308
|
+
/* A chip whose tool opened a live room: clicking it pops the browser or desktop out. */
|
|
1309
|
+
.chat-tool-session {
|
|
1310
|
+
cursor: pointer;
|
|
1311
|
+
transition: border-color 0.15s ease, color 0.15s ease;
|
|
1312
|
+
}
|
|
1313
|
+
.chat-tool-session:hover,
|
|
1314
|
+
.chat-tool-session:focus-visible,
|
|
1315
|
+
.chat-tool-session[aria-expanded="true"] {
|
|
1316
|
+
border-color: var(--chat-accent, #888);
|
|
1317
|
+
color: var(--chat-text, #1a1a1a);
|
|
1318
|
+
outline: none;
|
|
1319
|
+
}
|
|
1320
|
+
.chat-tool-chip .chat-tool-session-icon {
|
|
1321
|
+
font-size: 13px;
|
|
1322
|
+
opacity: 0.7;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.chat-session {
|
|
1326
|
+
position: absolute;
|
|
1327
|
+
inset: 16px 16px 96px;
|
|
1328
|
+
z-index: 30;
|
|
1329
|
+
display: flex;
|
|
1330
|
+
flex-direction: column;
|
|
1331
|
+
max-width: 960px;
|
|
1332
|
+
margin: 0 auto;
|
|
1333
|
+
border-radius: 16px;
|
|
1334
|
+
border: 1px solid var(--chat-border, #e0e0e0);
|
|
1335
|
+
overflow: hidden;
|
|
1336
|
+
background: var(--chat-input-bg, #fff);
|
|
1337
|
+
color: var(--chat-text, #1a1a1a);
|
|
1338
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 12px 32px rgba(0, 0, 0, 0.16), 0 32px 64px rgba(0, 0, 0, 0.1);
|
|
1339
|
+
}
|
|
1340
|
+
.chat-session-bar {
|
|
1341
|
+
display: flex;
|
|
1342
|
+
align-items: center;
|
|
1343
|
+
gap: 8px;
|
|
1344
|
+
padding: 8px 10px 8px 14px;
|
|
1345
|
+
border-bottom: 1px solid var(--chat-border, #e0e0e0);
|
|
1346
|
+
font-size: 13px;
|
|
1347
|
+
}
|
|
1348
|
+
.chat-session-bar > .material-symbols-outlined {
|
|
1349
|
+
font-size: 16px;
|
|
1350
|
+
color: var(--chat-accent, #888);
|
|
1351
|
+
}
|
|
1352
|
+
.chat-session-title {
|
|
1353
|
+
flex: 1;
|
|
1354
|
+
min-width: 0;
|
|
1355
|
+
overflow: hidden;
|
|
1356
|
+
text-overflow: ellipsis;
|
|
1357
|
+
white-space: nowrap;
|
|
1358
|
+
font-weight: 600;
|
|
1359
|
+
}
|
|
1360
|
+
.chat-session-close {
|
|
1361
|
+
display: inline-flex;
|
|
1362
|
+
padding: 4px;
|
|
1363
|
+
border: none;
|
|
1364
|
+
border-radius: 8px;
|
|
1365
|
+
background: none;
|
|
1366
|
+
color: var(--chat-accent, #888);
|
|
1367
|
+
cursor: pointer;
|
|
1368
|
+
}
|
|
1369
|
+
.chat-session-close:hover {
|
|
1370
|
+
background: var(--chat-secondary, #f5f5f5);
|
|
1371
|
+
color: var(--chat-text, #1a1a1a);
|
|
1372
|
+
}
|
|
1373
|
+
.chat-session-close .material-symbols-outlined {
|
|
1374
|
+
font-size: 18px;
|
|
1375
|
+
}
|
|
1376
|
+
.chat-session-area {
|
|
1377
|
+
position: relative;
|
|
1378
|
+
flex: 1;
|
|
1379
|
+
min-height: 0;
|
|
1380
|
+
overflow: hidden;
|
|
1381
|
+
background: var(--chat-secondary, #f5f5f5);
|
|
1382
|
+
}
|
|
1383
|
+
.chat-session-stage {
|
|
1384
|
+
position: absolute;
|
|
1385
|
+
inset: 0;
|
|
1386
|
+
}
|
|
1387
|
+
.chat-session-desktop {
|
|
1388
|
+
inset: auto;
|
|
1389
|
+
top: 0;
|
|
1390
|
+
left: 0;
|
|
1391
|
+
transform-origin: 0 0;
|
|
1392
|
+
}
|
|
1393
|
+
.chat-session-ended {
|
|
1394
|
+
display: flex;
|
|
1395
|
+
align-items: center;
|
|
1396
|
+
justify-content: center;
|
|
1397
|
+
height: 100%;
|
|
1398
|
+
font-size: 13px;
|
|
1399
|
+
color: var(--chat-accent, #888);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1308
1402
|
.chat-tool-web {
|
|
1309
1403
|
align-self: flex-start;
|
|
1310
1404
|
max-width: 100%;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fauna-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.783",
|
|
4
4
|
"description": "The Fauna SDK: accounts, Drive, groups, calendar, chats, payments and every Fauna app (Parrot, Marmot, Magpie, Cicada, Ant, Termite), plus React UI.",
|
|
5
5
|
"homepage": "https://docs.faunakit.com",
|
|
6
6
|
"repository": {
|