@datalayer/agent-runtimes 1.3.40 → 1.3.42
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/lib/chat/ChatFloating.d.ts +30 -1
- package/lib/chat/ChatFloating.js +60 -14
- package/lib/chat/presence/Presence.d.ts +9 -0
- package/lib/chat/presence/Presence.js +11 -2
- package/lib/loop/apps/AppRenderer.d.ts +30 -6
- package/lib/loop/apps/AppRenderer.js +29 -8
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/apps/records.d.ts +42 -0
- package/lib/loop/apps/records.js +81 -0
- package/lib/loop/apps/rules.d.ts +13 -0
- package/lib/loop/apps/rules.js +36 -0
- package/lib/loop/embed/AppEmbed.d.ts +2 -0
- package/lib/loop/embed/AppEmbed.js +47 -119
- package/lib/loop/embed/LoopEmbed.js +1 -0
- package/lib/loop/index.d.ts +2 -0
- package/lib/loop/index.js +2 -0
- package/lib/loop/plugins/app-activity/AppActivity.d.ts +21 -0
- package/lib/loop/plugins/app-activity/AppActivity.js +63 -0
- package/lib/loop/plugins/app-activity/index.d.ts +11 -0
- package/lib/loop/plugins/app-activity/index.js +35 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +40 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +50 -0
- package/lib/loop/plugins/app-rules/index.d.ts +7 -0
- package/lib/loop/plugins/app-rules/index.js +31 -0
- package/lib/loop/plugins/chat/ChatView.js +23 -3
- package/lib/loop/plugins/chat/index.d.ts +23 -0
- package/lib/loop/presets.d.ts +2 -0
- package/lib/loop/presets.js +2 -1
- package/package.json +2 -2
|
@@ -8,13 +8,36 @@
|
|
|
8
8
|
* 1. AG-UI mode: When `endpoint` is provided
|
|
9
9
|
* 2. Store mode: When `useStore` is true
|
|
10
10
|
* 3. Any protocol supported by ChatBase (AG-UI, A2A, ACP, Vercel AI)
|
|
11
|
+
* 4. A conversation its host draws (`conversation`): the LOOP workspace,
|
|
12
|
+
* which the embed's bubble, panel and assistant hold (LOOP R-01) — the
|
|
13
|
+
* floating chrome around it, told by the host what it is doing and what
|
|
14
|
+
* it last said
|
|
11
15
|
*
|
|
12
16
|
* @module chat/ChatFloating
|
|
13
17
|
*/
|
|
14
18
|
import React from 'react';
|
|
15
19
|
import { type AssistantCharacter } from './assistant/characters';
|
|
16
20
|
import type { AssistantCharacterData } from './assistant/formats/types';
|
|
21
|
+
import { type AssistantSaying } from './assistant/state';
|
|
22
|
+
import { type PresenceState } from './presence/presenceStatus';
|
|
17
23
|
import type { ChatCommonProps, ChatViewMode, ThemeOverrides } from '../types';
|
|
24
|
+
/**
|
|
25
|
+
* A conversation the host draws in the floating window, in place of the
|
|
26
|
+
* chat: the LOOP workspace (LOOP R-01). The window keeps its chrome — the
|
|
27
|
+
* button, its blink and its balloon, the panel, the assistant, dragging and
|
|
28
|
+
* dismissing — and is told by the host what the conversation is doing and
|
|
29
|
+
* what it last said, which it would otherwise read from its own chat.
|
|
30
|
+
*/
|
|
31
|
+
export type FloatingConversation = {
|
|
32
|
+
/** What the window holds, under its header (the face, the title, close). */
|
|
33
|
+
body: React.ReactNode;
|
|
34
|
+
/** What it is doing: idle, thinking, working, waiting for the person. */
|
|
35
|
+
presence: PresenceState;
|
|
36
|
+
/** Its newest words, said in the balloon until heard. */
|
|
37
|
+
saying?: AssistantSaying;
|
|
38
|
+
/** Whether its newest item is the answer being written. */
|
|
39
|
+
answering: boolean;
|
|
40
|
+
};
|
|
18
41
|
/**
|
|
19
42
|
* ChatFloating props — extends ChatCommonProps with floating/popup-specific configuration.
|
|
20
43
|
*/
|
|
@@ -110,10 +133,16 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
110
133
|
* @default false
|
|
111
134
|
*/
|
|
112
135
|
showPanelBackdrop?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* A conversation the host draws in the window instead of the chat — the
|
|
138
|
+
* LOOP workspace, as the embed's floating modes do (LOOP R-01). The chat's
|
|
139
|
+
* own props (`protocol`, `suggestions`, …) are then not read.
|
|
140
|
+
*/
|
|
141
|
+
conversation?: FloatingConversation;
|
|
113
142
|
}
|
|
114
143
|
/**
|
|
115
144
|
* ChatFloating component
|
|
116
145
|
* A floating chat window built on ChatBase
|
|
117
146
|
*/
|
|
118
|
-
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, }: ChatFloatingProps): React.JSX.Element;
|
|
147
|
+
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, conversation, }: ChatFloatingProps): React.JSX.Element;
|
|
119
148
|
export default ChatFloating;
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
@@ -13,6 +13,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
13
13
|
* 1. AG-UI mode: When `endpoint` is provided
|
|
14
14
|
* 2. Store mode: When `useStore` is true
|
|
15
15
|
* 3. Any protocol supported by ChatBase (AG-UI, A2A, ACP, Vercel AI)
|
|
16
|
+
* 4. A conversation its host draws (`conversation`): the LOOP workspace,
|
|
17
|
+
* which the embed's bubble, panel and assistant hold (LOOP R-01) — the
|
|
18
|
+
* floating chrome around it, told by the host what it is doing and what
|
|
19
|
+
* it last said
|
|
16
20
|
*
|
|
17
21
|
* @module chat/ChatFloating
|
|
18
22
|
*/
|
|
@@ -59,7 +63,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
|
|
|
59
63
|
* ChatFloating component
|
|
60
64
|
* A floating chat window built on ChatBase
|
|
61
65
|
*/
|
|
62
|
-
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, }) {
|
|
66
|
+
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, conversation, }) {
|
|
63
67
|
// Store-based state
|
|
64
68
|
const storeIsOpen = useChatOpen();
|
|
65
69
|
const storeMessages = useChatMessages();
|
|
@@ -121,7 +125,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
121
125
|
open: false,
|
|
122
126
|
pendingApproval: false,
|
|
123
127
|
});
|
|
124
|
-
const [
|
|
128
|
+
const [ownAnswering, setAnswering] = useState(false);
|
|
125
129
|
const [arriving, setArriving] = useState(false);
|
|
126
130
|
const [leaving, setLeaving] = useState(false);
|
|
127
131
|
const [assistantAway, setAssistantAway] = useState(keptAway);
|
|
@@ -130,8 +134,13 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
130
134
|
keepAway('none');
|
|
131
135
|
setAssistantAway('none');
|
|
132
136
|
}, []);
|
|
133
|
-
/*
|
|
134
|
-
|
|
137
|
+
/*
|
|
138
|
+
* The agent's newest words, said in the balloon until heard (T-23): the
|
|
139
|
+
* chat's own, or what the host's conversation says it said.
|
|
140
|
+
*/
|
|
141
|
+
const [ownSaying, setSaying] = useState();
|
|
142
|
+
const saying = conversation ? conversation.saying : ownSaying;
|
|
143
|
+
const answering = conversation ? conversation.answering : ownAnswering;
|
|
135
144
|
const [heardId, setHeardId] = useState();
|
|
136
145
|
const [freshSaying, setFreshSaying] = useState(false);
|
|
137
146
|
useEffect(() => {
|
|
@@ -146,7 +155,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
146
155
|
if (isOpen && saying) {
|
|
147
156
|
setHeardId(saying.id);
|
|
148
157
|
}
|
|
149
|
-
}, [isOpen, saying]);
|
|
158
|
+
}, [isOpen, saying?.id]);
|
|
150
159
|
const stageRef = useRef(null);
|
|
151
160
|
const stageDrag = useViewportDrag(stageRef, { whole: true });
|
|
152
161
|
useEffect(() => {
|
|
@@ -157,7 +166,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
157
166
|
const timer = setTimeout(() => setArriving(false), 1800);
|
|
158
167
|
return () => clearTimeout(timer);
|
|
159
168
|
}, [assistantShown]);
|
|
160
|
-
const assistantState = assistantStateOf(presenceState(chatBusy, chatTool), {
|
|
169
|
+
const assistantState = assistantStateOf(conversation ? conversation.presence : presenceState(chatBusy, chatTool), {
|
|
161
170
|
arriving,
|
|
162
171
|
leaving,
|
|
163
172
|
speaking: answering,
|
|
@@ -271,11 +280,16 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
271
280
|
const measure = () => {
|
|
272
281
|
const rect = host.getBoundingClientRect();
|
|
273
282
|
setPanelHostRect(prev => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
283
|
+
// A host that holds nothing of the page — an embed's own element,
|
|
284
|
+
// everything in it fixed to the viewport — has no height to align
|
|
285
|
+
// to: the panel is then the viewport's right edge, at full height.
|
|
286
|
+
const next = rect.height < 1
|
|
287
|
+
? { top: 0, height: window.innerHeight, right: 0 }
|
|
288
|
+
: {
|
|
289
|
+
top: rect.top,
|
|
290
|
+
height: rect.height,
|
|
291
|
+
right: Math.max(0, window.innerWidth - rect.right),
|
|
292
|
+
};
|
|
279
293
|
if (prev &&
|
|
280
294
|
Math.abs(prev.top - next.top) < 1 &&
|
|
281
295
|
Math.abs(prev.height - next.height) < 1 &&
|
|
@@ -459,6 +473,22 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
459
473
|
document.addEventListener('keydown', handleKeyDown);
|
|
460
474
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
461
475
|
}, [escapeToClose, isOpen, handleToggle]);
|
|
476
|
+
/*
|
|
477
|
+
* A host's conversation takes the caret when the window opens, as the
|
|
478
|
+
* chat's own does (`autoFocus`): its composer, once the window shows.
|
|
479
|
+
*/
|
|
480
|
+
const hostsConversation = Boolean(conversation);
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
if (!hostsConversation || !isOpen) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
const timer = setTimeout(() => {
|
|
486
|
+
popupRef.current
|
|
487
|
+
?.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"], textarea')
|
|
488
|
+
?.focus();
|
|
489
|
+
}, animationDuration);
|
|
490
|
+
return () => clearTimeout(timer);
|
|
491
|
+
}, [hostsConversation, isOpen, animationDuration, focusTrigger]);
|
|
462
492
|
// Click outside to close
|
|
463
493
|
useEffect(() => {
|
|
464
494
|
if (!clickOutsideToClose || !isOpen)
|
|
@@ -698,7 +728,23 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
698
728
|
borderColor: 'border.muted',
|
|
699
729
|
touchAction: 'none',
|
|
700
730
|
'&:active': { cursor: 'grabbing' },
|
|
701
|
-
}, children: _jsx(GrabberIcon, { size: 16 }) })),
|
|
731
|
+
}, children: _jsx(GrabberIcon, { size: 16 }) })), conversation ? (_jsxs(_Fragment, { children: [showHeader ? (_jsxs(Box, { sx: {
|
|
732
|
+
display: 'flex',
|
|
733
|
+
alignItems: 'center',
|
|
734
|
+
gap: 2,
|
|
735
|
+
flexShrink: 0,
|
|
736
|
+
px: 3,
|
|
737
|
+
py: 2,
|
|
738
|
+
borderBottom: '1px solid',
|
|
739
|
+
borderColor: 'border.default',
|
|
740
|
+
}, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), _jsx(Text, { sx: {
|
|
741
|
+
flex: 1,
|
|
742
|
+
minWidth: 0,
|
|
743
|
+
fontWeight: 'semibold',
|
|
744
|
+
overflow: 'hidden',
|
|
745
|
+
textOverflow: 'ellipsis',
|
|
746
|
+
whiteSpace: 'nowrap',
|
|
747
|
+
}, children: title }), closeButton] })) : null, _jsx(Box, { "data-floating-conversation": true, sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: conversation.body })] })) : (_jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, themeVariant: themeVariant, themeOverrides: themeOverrides, colorMode: colorMode, autoFocus: isOpen, focusTrigger: focusTrigger, launching: launching, launchingMessage: launchingMessage, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
|
|
702
748
|
showNewChat: showNewChatButton,
|
|
703
749
|
showClear: showClearButton && messages.length > 0,
|
|
704
750
|
showSettings: showSettingsButton && !!onSettingsClick,
|
|
@@ -714,7 +760,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
714
760
|
...poweredByProps,
|
|
715
761
|
}, renderToolResult: renderToolResult, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
|
|
716
762
|
showAvatars: true,
|
|
717
|
-
}, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children })] }));
|
|
763
|
+
}, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children }))] }));
|
|
718
764
|
/*
|
|
719
765
|
* The floating popup's button. Words not yet heard make it blink for the
|
|
720
766
|
* person's attention, until the chat is opened; hovered, they show in a
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ReactNode } from 'react';
|
|
11
11
|
import { type PresenceState } from './presenceStatus';
|
|
12
|
+
/**
|
|
13
|
+
* A face at `size`: an emoji drawn in Fluent Emoji, the same on every
|
|
14
|
+
* platform (LOOP T-20) — the system's, as text, only for one Datalayer ships
|
|
15
|
+
* no drawing of — or a host's own drawing, left as it is.
|
|
16
|
+
*/
|
|
17
|
+
export declare function FaceDrawing({ face, size }: {
|
|
18
|
+
face: ReactNode;
|
|
19
|
+
size: number;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
12
21
|
export declare function PresenceFace({ face, size, state, }: {
|
|
13
22
|
/**
|
|
14
23
|
* The face: an emoji, drawn at `size`, or a host's own drawing of it — an
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from '@primer/react';
|
|
3
|
+
import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
|
|
3
4
|
import { PRESENCE_LINES } from './presenceStatus';
|
|
4
5
|
const ACCENT = 'var(--loop-accent, var(--fgColor-accent))';
|
|
6
|
+
/**
|
|
7
|
+
* A face at `size`: an emoji drawn in Fluent Emoji, the same on every
|
|
8
|
+
* platform (LOOP T-20) — the system's, as text, only for one Datalayer ships
|
|
9
|
+
* no drawing of — or a host's own drawing, left as it is.
|
|
10
|
+
*/
|
|
11
|
+
export function FaceDrawing({ face, size }) {
|
|
12
|
+
return typeof face === 'string' ? (_jsx(FluentEmoji, { emoji: face, size: size, label: "" })) : (_jsx(_Fragment, { children: face }));
|
|
13
|
+
}
|
|
5
14
|
export function PresenceFace({ face, size, state, }) {
|
|
6
15
|
const active = state === 'thinking' || state === 'working';
|
|
7
16
|
return (_jsxs(Box, { as: "span", "aria-hidden": true, "data-presence": state, sx: {
|
|
@@ -31,7 +40,7 @@ export function PresenceFace({ face, size, state, }) {
|
|
|
31
40
|
'@media (prefers-reduced-motion: reduce)': {
|
|
32
41
|
animation: 'none',
|
|
33
42
|
},
|
|
34
|
-
} }), face] }));
|
|
43
|
+
} }), _jsx(FaceDrawing, { face: face, size: size })] }));
|
|
35
44
|
}
|
|
36
45
|
export function PresenceLine({ state }) {
|
|
37
46
|
return (_jsx(Text, { role: "status", "aria-live": "polite", "data-presence-line": state, sx: {
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
* page its host draws (`page`, R-02) — the Studio's run page, at its
|
|
28
28
|
* address and embedded;
|
|
29
29
|
* - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
|
|
30
|
-
* window, its title the application's face and name
|
|
30
|
+
* window, its title the application's face and name;
|
|
31
|
+
* - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
|
|
32
|
+
* the approvals waiting for the person (`app-rules`), and what it did
|
|
33
|
+
* (`app-activity`), beside its page — what its builder reads in the
|
|
34
|
+
* Studio's Preview.
|
|
31
35
|
*
|
|
32
36
|
* Which plugins a kind needs, and how its workspace is laid out, is one
|
|
33
37
|
* function: `appPreset`, the preset per kind, as `loopPlugins` is for the
|
|
@@ -42,6 +46,7 @@ import type { ThemeOverrides } from '../../types/chat';
|
|
|
42
46
|
import { type AppHostPageProps } from '../plugins/app-page';
|
|
43
47
|
import { type LoopEmbedProps } from '../embed/LoopEmbed';
|
|
44
48
|
import type { LoopPresetOptions } from '../presets';
|
|
49
|
+
import type { ChatSaid } from '../plugins/chat';
|
|
45
50
|
import type { PresenceState } from '../../chat/presence/presenceStatus';
|
|
46
51
|
/** The id of an agent or a Cog, without its version. */
|
|
47
52
|
export declare const agentIdOf: (app: Pick<AppSpec, "agent" | "team">) => string;
|
|
@@ -78,6 +83,20 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
|
|
|
78
83
|
* stable function, such as a state setter.
|
|
79
84
|
*/
|
|
80
85
|
onPresence?: (state: PresenceState) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Told what the application last said, and whether its newest words are
|
|
88
|
+
* an answer — for a host that says them outside the conversation: the
|
|
89
|
+
* embed's floating chrome, its balloon and its blink (R-01). A stable
|
|
90
|
+
* function, such as a state setter.
|
|
91
|
+
*/
|
|
92
|
+
onSaying?: (said: ChatSaid) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Draw its rules and the approvals waiting for the person, and its
|
|
95
|
+
* activity, beside its page (R-01b): what its builder reads in the
|
|
96
|
+
* Studio's Preview. The activity is read from its record, kept under
|
|
97
|
+
* `instance.appUid`.
|
|
98
|
+
*/
|
|
99
|
+
sidebar?: boolean;
|
|
81
100
|
/**
|
|
82
101
|
* Draw the application in a window (the `window-frame` plugin), its title
|
|
83
102
|
* the application's face and name. A host's own `frameTitle` wins.
|
|
@@ -127,8 +146,9 @@ export type AppPreset = {
|
|
|
127
146
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
128
147
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
129
148
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
130
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
131
|
-
*
|
|
149
|
+
* comment on each answer when its record keeps feedback (V-18); with
|
|
150
|
+
* `sidebar`, its rules and approvals card and its activity feed (R-01b);
|
|
151
|
+
* laid out as `interface.layout` says;
|
|
132
152
|
* - a **decision**: the page its host draws (`page`), as the one view of a
|
|
133
153
|
* workspace without a conversation (R-02).
|
|
134
154
|
*
|
|
@@ -138,14 +158,18 @@ export type AppPreset = {
|
|
|
138
158
|
export declare function appPreset(app: AppSpec, options?: {
|
|
139
159
|
page?: ComponentType<AppHostPageProps>;
|
|
140
160
|
pluginsOff?: readonly string[];
|
|
161
|
+
/** Its rules and approvals, and its activity, beside its page. */
|
|
162
|
+
sidebar?: boolean;
|
|
163
|
+
/** What its record is kept under: its activity's. */
|
|
164
|
+
appUid?: string;
|
|
141
165
|
}): AppPreset;
|
|
142
166
|
/**
|
|
143
167
|
* What an application's agent is created with on a Datalayer runtime: under
|
|
144
168
|
* the application's id, over AG-UI, with the application's own document —
|
|
145
169
|
* so that the runtime registers it and decides every tool call by its rules
|
|
146
170
|
* (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
|
|
147
|
-
* place it runs
|
|
148
|
-
*
|
|
171
|
+
* place it runs, all of them the workspace (`AppRenderer`): the Studio's
|
|
172
|
+
* Preview, the hosted page, and the embed inline and floating.
|
|
149
173
|
*/
|
|
150
174
|
export declare function appDatalayerCreatePayload(app: AppSpec, instance?: AppInstance): Record<string, unknown>;
|
|
151
175
|
/**
|
|
@@ -164,5 +188,5 @@ export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
|
|
|
164
188
|
* what the page around it set does not reach in.
|
|
165
189
|
*/
|
|
166
190
|
export declare function appThemeOverrides(app: AppSpec): ThemeOverrides | undefined;
|
|
167
|
-
export declare function AppRenderer({ app, plugins, instance, onPresence, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
|
|
191
|
+
export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, sidebar, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
|
|
168
192
|
export default AppRenderer;
|
|
@@ -32,7 +32,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
32
32
|
* page its host draws (`page`, R-02) — the Studio's run page, at its
|
|
33
33
|
* address and embedded;
|
|
34
34
|
* - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
|
|
35
|
-
* window, its title the application's face and name
|
|
35
|
+
* window, its title the application's face and name;
|
|
36
|
+
* - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
|
|
37
|
+
* the approvals waiting for the person (`app-rules`), and what it did
|
|
38
|
+
* (`app-activity`), beside its page — what its builder reads in the
|
|
39
|
+
* Studio's Preview.
|
|
36
40
|
*
|
|
37
41
|
* Which plugins a kind needs, and how its workspace is laid out, is one
|
|
38
42
|
* function: `appPreset`, the preset per kind, as `loopPlugins` is for the
|
|
@@ -47,6 +51,8 @@ import { APP_PAGE_SURFACE, defineAppHostPagePlugin, defineAppPagePlugin, hasAppP
|
|
|
47
51
|
import { CANVAS_BLOCK_PLUGINS, canvasBlocksPluginName, } from '../plugins/canvas-blocks';
|
|
48
52
|
import { LoopEmbed } from '../embed/LoopEmbed';
|
|
49
53
|
import { dumpAppspec } from './appspec';
|
|
54
|
+
import { defineAppActivityPlugin } from '../plugins/app-activity';
|
|
55
|
+
import { defineAppRulesPlugin } from '../plugins/app-rules';
|
|
50
56
|
import { defineAppFeedbackPlugin } from './AppFeedback';
|
|
51
57
|
import { keepsFeedback } from './feedback';
|
|
52
58
|
/** The id of an agent or a Cog, without its version. */
|
|
@@ -134,8 +140,9 @@ export function appLayoutOptions(app) {
|
|
|
134
140
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
135
141
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
136
142
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
137
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
138
|
-
*
|
|
143
|
+
* comment on each answer when its record keeps feedback (V-18); with
|
|
144
|
+
* `sidebar`, its rules and approvals card and its activity feed (R-01b);
|
|
145
|
+
* laid out as `interface.layout` says;
|
|
139
146
|
* - a **decision**: the page its host draws (`page`), as the one view of a
|
|
140
147
|
* workspace without a conversation (R-02).
|
|
141
148
|
*
|
|
@@ -167,6 +174,14 @@ export function appPreset(app, options = {}) {
|
|
|
167
174
|
// A thumb and a comment on each answer, kept in its record (LOOP
|
|
168
175
|
// V-18): only for an application whose record keeps feedback.
|
|
169
176
|
...(keepsFeedback(app) ? [defineAppFeedbackPlugin(app)] : []),
|
|
177
|
+
// What its builder reads beside its page (R-01b): its rules and the
|
|
178
|
+
// approvals waiting, and what it did.
|
|
179
|
+
...(options.sidebar
|
|
180
|
+
? [
|
|
181
|
+
defineAppRulesPlugin(app),
|
|
182
|
+
defineAppActivityPlugin(app, options.appUid),
|
|
183
|
+
]
|
|
184
|
+
: []),
|
|
170
185
|
],
|
|
171
186
|
workspace: appLayoutOptions(app),
|
|
172
187
|
};
|
|
@@ -176,8 +191,8 @@ export function appPreset(app, options = {}) {
|
|
|
176
191
|
* the application's id, over AG-UI, with the application's own document —
|
|
177
192
|
* so that the runtime registers it and decides every tool call by its rules
|
|
178
193
|
* (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
|
|
179
|
-
* place it runs
|
|
180
|
-
*
|
|
194
|
+
* place it runs, all of them the workspace (`AppRenderer`): the Studio's
|
|
195
|
+
* Preview, the hosted page, and the embed inline and floating.
|
|
181
196
|
*/
|
|
182
197
|
export function appDatalayerCreatePayload(app, instance) {
|
|
183
198
|
return {
|
|
@@ -227,7 +242,7 @@ export function appThemeOverrides(app) {
|
|
|
227
242
|
}
|
|
228
243
|
: undefined;
|
|
229
244
|
}
|
|
230
|
-
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, page, pluginsOff, ...embed }) {
|
|
245
|
+
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, onSaying, sidebar = false, frame = false, page, pluginsOff, ...embed }) {
|
|
231
246
|
/*
|
|
232
247
|
* The kind's plugins, made once per application. `LoopEmbed` rebuilds its
|
|
233
248
|
* whole reactor when its plugins change, so a new plugin on every render
|
|
@@ -238,7 +253,12 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
|
|
|
238
253
|
const off = [...(pluginsOff ?? [])].sort().join(',');
|
|
239
254
|
const preset = useMemo(() => {
|
|
240
255
|
try {
|
|
241
|
-
return appPreset(app, {
|
|
256
|
+
return appPreset(app, {
|
|
257
|
+
page,
|
|
258
|
+
pluginsOff,
|
|
259
|
+
sidebar,
|
|
260
|
+
appUid: instance?.appUid,
|
|
261
|
+
});
|
|
242
262
|
}
|
|
243
263
|
catch (error) {
|
|
244
264
|
return {
|
|
@@ -247,7 +267,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
|
|
|
247
267
|
}
|
|
248
268
|
},
|
|
249
269
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
250
|
-
[source, page, off]);
|
|
270
|
+
[source, page, off, sidebar, instance?.appUid]);
|
|
251
271
|
const allPlugins = useMemo(() => ('problem' in preset ? plugins : [...preset.plugins, ...plugins]), [preset, plugins]);
|
|
252
272
|
/*
|
|
253
273
|
* On Datalayer, the application runs on a runtime: allocated with a plain
|
|
@@ -296,6 +316,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
|
|
|
296
316
|
face: app.emoji,
|
|
297
317
|
welcome: app.interface.welcome || app.description,
|
|
298
318
|
onPresence,
|
|
319
|
+
onSaying,
|
|
299
320
|
}, showTokenUsage: false, datalayerCreatePayload: datalayerCreatePayload, ...embed, plugins: allPlugins }));
|
|
300
321
|
}
|
|
301
322
|
export default AppRenderer;
|
package/lib/loop/apps/index.d.ts
CHANGED
package/lib/loop/apps/index.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record of an application (LOOP R-07), as ai-agents keeps it: what it
|
|
3
|
+
* did, session by session, written by the runtime it ran on — read here for
|
|
4
|
+
* its activity feed (R-01b, R-15): the sessions, newest first, and what each
|
|
5
|
+
* one did, step by step.
|
|
6
|
+
*
|
|
7
|
+
* Pure but for `fetch`: no React.
|
|
8
|
+
*
|
|
9
|
+
* @module loop/apps/records
|
|
10
|
+
*/
|
|
11
|
+
/** What a record entry is. */
|
|
12
|
+
export type RecordKind = 'session' | 'tool_call' | 'decision' | 'check' | 'approval' | 'output' | 'feedback'
|
|
13
|
+
/** A turn of a conversation: what was asked, what it answered. */
|
|
14
|
+
| 'turn';
|
|
15
|
+
/** One entry of an application's record. */
|
|
16
|
+
export type RecordEntry = {
|
|
17
|
+
uid: string;
|
|
18
|
+
sessionUid: string;
|
|
19
|
+
deploymentUid: string;
|
|
20
|
+
version: number;
|
|
21
|
+
kind: RecordKind;
|
|
22
|
+
summary: string;
|
|
23
|
+
payload: Record<string, unknown>;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
};
|
|
26
|
+
/** Where the record is read, and as whom. */
|
|
27
|
+
export type RecordsContext = {
|
|
28
|
+
/** The ai-agents service. */
|
|
29
|
+
aiAgentsUrl: string;
|
|
30
|
+
/** The reader's token: the record is its owner's. */
|
|
31
|
+
token: string;
|
|
32
|
+
};
|
|
33
|
+
/** Each kind in a word, as the feed labels it. */
|
|
34
|
+
export declare const RECORD_KIND_WORDS: Record<RecordKind, string>;
|
|
35
|
+
/** An entry as ai-agents answers with it. */
|
|
36
|
+
export declare function entryOf(raw: Record<string, unknown>): RecordEntry;
|
|
37
|
+
/** A session in a sentence: how many calls, what stopped it, how it ended. */
|
|
38
|
+
export declare function sessionSentence(entries: readonly RecordEntry[]): string;
|
|
39
|
+
/** The sessions of an application, newest first: their opening entries. */
|
|
40
|
+
export declare function listSessions(context: RecordsContext, appUid: string, limit?: number): Promise<RecordEntry[]>;
|
|
41
|
+
/** What one session did, in order. */
|
|
42
|
+
export declare function readSession(context: RecordsContext, sessionUid: string): Promise<RecordEntry[]>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** Each kind in a word, as the feed labels it. */
|
|
6
|
+
export const RECORD_KIND_WORDS = {
|
|
7
|
+
session: 'Session',
|
|
8
|
+
tool_call: 'Tool call',
|
|
9
|
+
decision: 'Rule',
|
|
10
|
+
check: 'Check',
|
|
11
|
+
approval: 'Approval',
|
|
12
|
+
output: 'Answer',
|
|
13
|
+
feedback: 'Feedback',
|
|
14
|
+
turn: 'Turn',
|
|
15
|
+
};
|
|
16
|
+
/** An entry as ai-agents answers with it. */
|
|
17
|
+
export function entryOf(raw) {
|
|
18
|
+
return {
|
|
19
|
+
uid: String(raw.uid ?? ''),
|
|
20
|
+
sessionUid: String(raw.session_uid ?? ''),
|
|
21
|
+
deploymentUid: String(raw.deployment_uid ?? ''),
|
|
22
|
+
version: Number(raw.version) || 0,
|
|
23
|
+
kind: String(raw.kind ?? 'output'),
|
|
24
|
+
summary: String(raw.summary ?? ''),
|
|
25
|
+
payload: raw.payload && typeof raw.payload === 'object'
|
|
26
|
+
? raw.payload
|
|
27
|
+
: {},
|
|
28
|
+
createdAt: String(raw.created_at ?? ''),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** A session in a sentence: how many calls, what stopped it, how it ended. */
|
|
32
|
+
export function sessionSentence(entries) {
|
|
33
|
+
const calls = entries.filter(entry => entry.kind === 'tool_call').length;
|
|
34
|
+
const checks = entries.filter(entry => entry.kind === 'check');
|
|
35
|
+
const outputs = entries.filter(entry => entry.kind === 'output');
|
|
36
|
+
const last = outputs[outputs.length - 1];
|
|
37
|
+
const parts = [
|
|
38
|
+
calls === 0
|
|
39
|
+
? 'No tool called'
|
|
40
|
+
: `${calls} tool call${calls === 1 ? '' : 's'}`,
|
|
41
|
+
];
|
|
42
|
+
if (checks.length) {
|
|
43
|
+
parts.push(`${checks.length} step${checks.length === 1 ? '' : 's'} a check did not let pass`);
|
|
44
|
+
}
|
|
45
|
+
if (last?.summary.startsWith('Stopped:')) {
|
|
46
|
+
parts.push('stopped');
|
|
47
|
+
}
|
|
48
|
+
else if (outputs.length) {
|
|
49
|
+
parts.push(`${outputs.length} answer${outputs.length === 1 ? '' : 's'}`);
|
|
50
|
+
}
|
|
51
|
+
return `${parts.join(', ')}.`;
|
|
52
|
+
}
|
|
53
|
+
async function read(context, path, failure) {
|
|
54
|
+
if (!context.aiAgentsUrl) {
|
|
55
|
+
throw new Error('The record service is not configured.');
|
|
56
|
+
}
|
|
57
|
+
const response = await fetch(`${context.aiAgentsUrl.replace(/\/+$/, '')}/api/ai-agents/v1/apps${path}`, {
|
|
58
|
+
headers: context.token
|
|
59
|
+
? { Authorization: `Bearer ${context.token}` }
|
|
60
|
+
: {},
|
|
61
|
+
});
|
|
62
|
+
const body = (await response.json().catch(() => ({})));
|
|
63
|
+
if (!response.ok || body.success === false) {
|
|
64
|
+
throw new Error(`${failure}: ${String(body.detail ?? response.status)}`);
|
|
65
|
+
}
|
|
66
|
+
return body;
|
|
67
|
+
}
|
|
68
|
+
/** The sessions of an application, newest first: their opening entries. */
|
|
69
|
+
export async function listSessions(context, appUid, limit = 20) {
|
|
70
|
+
const body = await read(context, `/sessions?app_uid=${encodeURIComponent(appUid)}&limit=${limit}`, 'The sessions could not be read');
|
|
71
|
+
return Array.isArray(body.sessions)
|
|
72
|
+
? body.sessions.map(entryOf)
|
|
73
|
+
: [];
|
|
74
|
+
}
|
|
75
|
+
/** What one session did, in order. */
|
|
76
|
+
export async function readSession(context, sessionUid) {
|
|
77
|
+
const body = await read(context, `/records?session_uid=${encodeURIComponent(sessionUid)}&limit=500`, 'The session could not be read');
|
|
78
|
+
return Array.isArray(body.entries)
|
|
79
|
+
? body.entries.map(entryOf)
|
|
80
|
+
: [];
|
|
81
|
+
}
|
package/lib/loop/apps/rules.d.ts
CHANGED
|
@@ -76,3 +76,16 @@ export declare function behaviourFor(app: Pick<AppSpec, 'connections' | 'rules'>
|
|
|
76
76
|
export declare function toolBehaviours(app: Pick<AppSpec, 'connections' | 'rules'>): Record<string, AppBehaviour>;
|
|
77
77
|
/** Where what a tool is asked changes what the application does about it. */
|
|
78
78
|
export declare function toolEscalations(app: Pick<AppSpec, 'connections' | 'rules'>): Record<string, AppEscalation[]>;
|
|
79
|
+
/**
|
|
80
|
+
* The four behaviours, as a person reads them: what the Studio's rules card
|
|
81
|
+
* and an application's own rules and approvals card (R-01b) say. The same
|
|
82
|
+
* words as the Studio's vocabulary.
|
|
83
|
+
*/
|
|
84
|
+
export declare const BEHAVIOUR_WORDS: Record<AppBehaviour, {
|
|
85
|
+
says: string;
|
|
86
|
+
means: string;
|
|
87
|
+
}>;
|
|
88
|
+
/** What a class of action is, as a person reads it. */
|
|
89
|
+
export declare const ACTION_WORDS: Record<ActionClass, string>;
|
|
90
|
+
/** What a rule's target is, in words: a class of action, or a tool by its name. */
|
|
91
|
+
export declare function coverOf(target: string): string;
|
package/lib/loop/apps/rules.js
CHANGED
|
@@ -311,3 +311,39 @@ export function toolEscalations(app) {
|
|
|
311
311
|
}
|
|
312
312
|
return escalations;
|
|
313
313
|
}
|
|
314
|
+
// --- in words ----------------------------------------------------------------------
|
|
315
|
+
/**
|
|
316
|
+
* The four behaviours, as a person reads them: what the Studio's rules card
|
|
317
|
+
* and an application's own rules and approvals card (R-01b) say. The same
|
|
318
|
+
* words as the Studio's vocabulary.
|
|
319
|
+
*/
|
|
320
|
+
export const BEHAVIOUR_WORDS = {
|
|
321
|
+
do_it: { says: 'Do it', means: 'It does it without asking.' },
|
|
322
|
+
if_asked: {
|
|
323
|
+
says: 'Do it if I asked',
|
|
324
|
+
means: 'Only what you approved in advance.',
|
|
325
|
+
},
|
|
326
|
+
ask_first: { says: 'Ask me first', means: 'It waits for your yes.' },
|
|
327
|
+
leave_to_me: {
|
|
328
|
+
says: 'Leave it to me',
|
|
329
|
+
means: 'It never does it, and hands it to you.',
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
/** What a class of action is, as a person reads it. */
|
|
333
|
+
export const ACTION_WORDS = {
|
|
334
|
+
read: 'Read',
|
|
335
|
+
write: 'Create or change',
|
|
336
|
+
send: 'Send',
|
|
337
|
+
buy: 'Buy',
|
|
338
|
+
delete: 'Delete',
|
|
339
|
+
publish: 'Share or publish',
|
|
340
|
+
};
|
|
341
|
+
/** What a rule's target is, in words: a class of action, or a tool by its name. */
|
|
342
|
+
export function coverOf(target) {
|
|
343
|
+
const words = own(ACTION_WORDS, target);
|
|
344
|
+
if (words) {
|
|
345
|
+
return words;
|
|
346
|
+
}
|
|
347
|
+
const [server, name] = splitRef(target);
|
|
348
|
+
return server ? `${name} (${server})` : name;
|
|
349
|
+
}
|
|
@@ -45,6 +45,8 @@ export type AppEmbedProps = {
|
|
|
45
45
|
* no person's choice, since a visitor to another product has none here.
|
|
46
46
|
*/
|
|
47
47
|
export declare function embedAssistantCharacter(app: AppSpec, plugins?: PluginRef[]): AssistantCharacterChosen;
|
|
48
|
+
/** What a floating application says to a visitor Datalayer does not know. */
|
|
49
|
+
export declare const signedOutSentence: (app: Pick<AppSpec, "name">) => string;
|
|
48
50
|
/** The `loop` theme, with the application's (or the host's) accent and face. */
|
|
49
51
|
export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
|
|
50
52
|
accent: AppAccent;
|