@signalcore/ui 0.1.0 → 1.0.0-beta.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 +896 -513
- package/dist/AgentAbout.d.ts +186 -0
- package/dist/AgentAbout.js +1020 -0
- package/dist/AgentAbout.js.map +1 -0
- package/dist/AgentCard.d.ts +49 -0
- package/dist/AgentCard.js +77 -0
- package/dist/AgentCard.js.map +1 -0
- package/dist/AgentChat.d.ts +233 -0
- package/dist/AgentChat.js +1119 -0
- package/dist/AgentChat.js.map +1 -0
- package/dist/AgentSpace.d.ts +112 -0
- package/dist/AgentSpace.js +339 -0
- package/dist/AgentSpace.js.map +1 -0
- package/dist/AgentTasks.d.ts +48 -0
- package/dist/AgentTasks.js +120 -0
- package/dist/AgentTasks.js.map +1 -0
- package/dist/BrowserView.d.ts +52 -0
- package/dist/BrowserView.js +328 -0
- package/dist/BrowserView.js.map +1 -0
- package/dist/FileView.d.ts +63 -0
- package/dist/FileView.js +449 -0
- package/dist/FileView.js.map +1 -0
- package/dist/Modes.d.ts +13 -0
- package/dist/Modes.js +75 -0
- package/dist/Modes.js.map +1 -0
- package/dist/VoiceCall.d.ts +91 -0
- package/dist/VoiceCall.js +302 -0
- package/dist/VoiceCall.js.map +1 -0
- package/dist/about.d.ts +212 -0
- package/dist/about.js +136 -0
- package/dist/about.js.map +1 -0
- package/dist/agentIcons.d.ts +33 -0
- package/dist/agentIcons.js +36 -0
- package/dist/agentIcons.js.map +1 -0
- package/dist/automation/Automation.d.ts +35 -0
- package/dist/automation/Automation.js +268 -0
- package/dist/automation/Automation.js.map +1 -0
- package/dist/automation/RunView.d.ts +25 -0
- package/dist/automation/RunView.js +187 -0
- package/dist/automation/RunView.js.map +1 -0
- package/dist/automation/cron.d.ts +41 -0
- package/dist/automation/cron.js +169 -0
- package/dist/automation/cron.js.map +1 -0
- package/dist/automation/marks.js +121 -0
- package/dist/automation/marks.js.map +1 -0
- package/dist/automation/messages.d.ts +113 -0
- package/dist/automation/messages.js +113 -0
- package/dist/automation/messages.js.map +1 -0
- package/dist/automation/record.d.ts +47 -0
- package/dist/automation/record.js +177 -0
- package/dist/automation/record.js.map +1 -0
- package/dist/automation/types.d.ts +143 -0
- package/dist/automation/when.d.ts +36 -0
- package/dist/automation/when.js +168 -0
- package/dist/automation/when.js.map +1 -0
- package/dist/blocks.d.ts +185 -0
- package/dist/browser.js +292 -0
- package/dist/browser.js.map +1 -0
- package/dist/call.d.ts +89 -0
- package/dist/call.js +218 -0
- package/dist/call.js.map +1 -0
- package/dist/channelParts.js +26 -0
- package/dist/channelParts.js.map +1 -0
- package/dist/classes.js +8 -3
- package/dist/classes.js.map +1 -0
- package/dist/client.d.ts +146 -54
- package/dist/client.js +204 -1
- package/dist/client.js.map +1 -0
- package/dist/conversationFiles.js +46 -0
- package/dist/conversationFiles.js.map +1 -0
- package/dist/credentials.d.ts +45 -0
- package/dist/credentials.js +98 -0
- package/dist/credentials.js.map +1 -0
- package/dist/dev.js +17 -0
- package/dist/dev.js.map +1 -0
- package/dist/es.d.ts +17 -0
- package/dist/es.js +382 -0
- package/dist/es.js.map +1 -0
- package/dist/files.d.ts +21 -0
- package/dist/files.js +214 -0
- package/dist/files.js.map +1 -0
- package/dist/frames.d.ts +115 -0
- package/dist/frames.js +219 -0
- package/dist/frames.js.map +1 -0
- package/dist/icons.js +29 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +63 -60
- package/dist/index.js +59 -45
- package/dist/kit/AudioPlayer.d.ts +20 -0
- package/dist/kit/AudioPlayer.js +116 -0
- package/dist/kit/AudioPlayer.js.map +1 -0
- package/dist/kit/Button.d.ts +29 -0
- package/dist/kit/Button.js +59 -0
- package/dist/kit/Button.js.map +1 -0
- package/dist/kit/Card.d.ts +16 -0
- package/dist/kit/Card.js +36 -0
- package/dist/kit/Card.js.map +1 -0
- package/dist/kit/Chart.js +152 -0
- package/dist/kit/Chart.js.map +1 -0
- package/dist/kit/Choices.d.ts +21 -0
- package/dist/kit/Choices.js +99 -0
- package/dist/kit/Choices.js.map +1 -0
- package/dist/kit/Combobox.d.ts +26 -0
- package/dist/kit/Combobox.js +190 -0
- package/dist/kit/Combobox.js.map +1 -0
- package/dist/kit/DateField.d.ts +17 -0
- package/dist/kit/DateField.js +133 -0
- package/dist/kit/DateField.js.map +1 -0
- package/dist/kit/Field.d.ts +40 -0
- package/dist/kit/Field.js +98 -0
- package/dist/kit/Field.js.map +1 -0
- package/dist/kit/FileField.d.ts +113 -0
- package/dist/kit/FileField.js +341 -0
- package/dist/kit/FileField.js.map +1 -0
- package/dist/kit/FileMark.d.ts +15 -0
- package/dist/kit/FileMark.js +39 -0
- package/dist/kit/FileMark.js.map +1 -0
- package/dist/kit/Inputs.d.ts +59 -0
- package/dist/kit/Inputs.js +205 -0
- package/dist/kit/Inputs.js.map +1 -0
- package/dist/kit/List.d.ts +47 -0
- package/dist/kit/List.js +85 -0
- package/dist/kit/List.js.map +1 -0
- package/dist/kit/Mark.d.ts +30 -0
- package/dist/kit/Mark.js +27 -0
- package/dist/kit/Mark.js.map +1 -0
- package/dist/kit/MarkStack.d.ts +15 -0
- package/dist/kit/MarkStack.js +26 -0
- package/dist/kit/MarkStack.js.map +1 -0
- package/dist/kit/Note.d.ts +11 -0
- package/dist/kit/Note.js +31 -0
- package/dist/kit/Note.js.map +1 -0
- package/dist/kit/Popover.js +90 -0
- package/dist/kit/Popover.js.map +1 -0
- package/dist/kit/RangeField.d.ts +14 -0
- package/dist/kit/RangeField.js +47 -0
- package/dist/kit/RangeField.js.map +1 -0
- package/dist/kit/RatingField.d.ts +14 -0
- package/dist/kit/RatingField.js +50 -0
- package/dist/kit/RatingField.js.map +1 -0
- package/dist/kit/Stat.d.ts +26 -0
- package/dist/kit/Stat.js +46 -0
- package/dist/kit/Stat.js.map +1 -0
- package/dist/kit/Switch.d.ts +14 -0
- package/dist/kit/Switch.js +21 -0
- package/dist/kit/Switch.js.map +1 -0
- package/dist/kit/Tag.d.ts +16 -0
- package/dist/kit/Tag.js +20 -0
- package/dist/kit/Tag.js.map +1 -0
- package/dist/kit/Tile.d.ts +34 -0
- package/dist/kit/Tile.js +52 -0
- package/dist/kit/Tile.js.map +1 -0
- package/dist/kit/TimeField.d.ts +31 -0
- package/dist/kit/TimeField.js +109 -0
- package/dist/kit/TimeField.js.map +1 -0
- package/dist/kit/context.d.ts +22 -0
- package/dist/kit/context.js +46 -0
- package/dist/kit/context.js.map +1 -0
- package/dist/kit/dates.d.ts +14 -0
- package/dist/kit/dates.js +129 -0
- package/dist/kit/dates.js.map +1 -0
- package/dist/kit/messages.d.ts +99 -0
- package/dist/kit/messages.js +66 -0
- package/dist/kit/messages.js.map +1 -0
- package/dist/kit/numbers.d.ts +12 -0
- package/dist/kit/numbers.js +40 -0
- package/dist/kit/numbers.js.map +1 -0
- package/dist/kit/options.d.ts +10 -0
- package/dist/kit/options.js +23 -0
- package/dist/kit/options.js.map +1 -0
- package/dist/kit/uploads.d.ts +42 -0
- package/dist/kit/uploads.js +165 -0
- package/dist/kit/uploads.js.map +1 -0
- package/dist/kit/waveform.js +76 -0
- package/dist/kit/waveform.js.map +1 -0
- package/dist/locale.d.ts +1 -0
- package/dist/locale.js +1 -0
- package/dist/markdown.js +84 -0
- package/dist/markdown.js.map +1 -0
- package/dist/marks.js +217 -0
- package/dist/marks.js.map +1 -0
- package/dist/reveal.js +48 -0
- package/dist/reveal.js.map +1 -0
- package/dist/stepGlyphs.js +62 -0
- package/dist/stepGlyphs.js.map +1 -0
- package/dist/surface/Form.js +264 -0
- package/dist/surface/Form.js.map +1 -0
- package/dist/surface/Markdown.js +42 -0
- package/dist/surface/Markdown.js.map +1 -0
- package/dist/surface/Surface.d.ts +31 -17
- package/dist/surface/Surface.js +480 -57
- package/dist/surface/Surface.js.map +1 -0
- package/dist/surface/scope.d.ts +32 -0
- package/dist/surface/scope.js +23 -0
- package/dist/surface/scope.js.map +1 -0
- package/dist/surface/values.d.ts +44 -0
- package/dist/surface/values.js +237 -0
- package/dist/surface/values.js.map +1 -0
- package/dist/tint.d.ts +10 -0
- package/dist/tint.js +19 -0
- package/dist/tint.js.map +1 -0
- package/dist/types.d.ts +534 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/useAgentFace.d.ts +18 -0
- package/dist/useAgentFace.js +45 -0
- package/dist/useAgentFace.js.map +1 -0
- package/dist/useCall.d.ts +39 -0
- package/dist/useCall.js +273 -0
- package/dist/useCall.js.map +1 -0
- package/dist/useConversation.d.ts +77 -0
- package/dist/useConversation.js +523 -0
- package/dist/useConversation.js.map +1 -0
- package/dist/useDictation.d.ts +17 -0
- package/dist/useDictation.js +68 -0
- package/dist/useDictation.js.map +1 -0
- package/dist/useLive.d.ts +26 -0
- package/dist/useLive.js +88 -0
- package/dist/useLive.js.map +1 -0
- package/dist/usePolled.js +68 -0
- package/dist/usePolled.js.map +1 -0
- package/dist/voice/Orb.d.ts +10 -9
- package/dist/voice/Orb.js +43 -39
- package/dist/voice/Orb.js.map +1 -0
- package/dist/voice/StillOrb.js +33 -0
- package/dist/voice/StillOrb.js.map +1 -0
- package/dist/voice/WebGLOrb.js +98 -0
- package/dist/voice/WebGLOrb.js.map +1 -0
- package/dist/voice/fluidDots.js +161 -0
- package/dist/voice/fluidDots.js.map +1 -0
- package/dist/voice/motion.js +84 -0
- package/dist/voice/motion.js.map +1 -0
- package/dist/voice/orbs.d.ts +32 -0
- package/dist/voice/orbs.js +16 -0
- package/dist/voice/orbs.js.map +1 -0
- package/dist/voice/useReducedMotion.js +16 -16
- package/dist/voice/useReducedMotion.js.map +1 -0
- package/dist/voice/wave.js +116 -0
- package/dist/voice/wave.js.map +1 -0
- package/dist/voice/webgl.js +177 -0
- package/dist/voice/webgl.js.map +1 -0
- package/dist/widget.js +284 -8
- package/package.json +49 -39
- package/src/AgentAbout.tsx +1621 -0
- package/src/AgentCard.tsx +123 -0
- package/src/AgentChat.tsx +1795 -0
- package/src/AgentSpace.tsx +540 -0
- package/src/AgentTasks.tsx +163 -0
- package/src/BrowserView.tsx +442 -0
- package/src/FileView.tsx +620 -0
- package/src/Modes.tsx +112 -0
- package/src/VoiceCall.tsx +411 -0
- package/src/about.ts +355 -0
- package/src/agentIcons.ts +56 -0
- package/src/automation/Automation.tsx +308 -0
- package/src/automation/RunView.tsx +213 -0
- package/src/automation/cron.ts +196 -0
- package/src/automation/marks.tsx +144 -0
- package/src/automation/messages.ts +231 -0
- package/src/automation/record.ts +206 -0
- package/src/automation/types.ts +146 -0
- package/src/automation/when.ts +195 -0
- package/src/blocks.ts +249 -0
- package/src/browser.ts +334 -0
- package/src/call.ts +300 -0
- package/src/channelParts.ts +17 -0
- package/src/classes.ts +10 -0
- package/src/client.ts +397 -0
- package/src/conversationFiles.ts +54 -0
- package/src/credentials.ts +158 -0
- package/src/dev.ts +20 -0
- package/src/es.ts +423 -0
- package/src/files.ts +238 -0
- package/src/frames.ts +291 -0
- package/src/icons.tsx +49 -0
- package/src/index.ts +270 -0
- package/src/kit/AudioPlayer.tsx +129 -0
- package/src/kit/Button.tsx +97 -0
- package/src/kit/Card.tsx +47 -0
- package/src/kit/Chart.tsx +129 -0
- package/src/kit/Choices.tsx +131 -0
- package/src/kit/Combobox.tsx +247 -0
- package/src/kit/DateField.tsx +156 -0
- package/src/kit/Field.tsx +137 -0
- package/src/kit/FileField.tsx +492 -0
- package/src/kit/FileMark.tsx +56 -0
- package/src/kit/Inputs.tsx +250 -0
- package/src/kit/List.tsx +135 -0
- package/src/kit/Mark.tsx +64 -0
- package/src/kit/MarkStack.tsx +29 -0
- package/src/kit/Note.tsx +42 -0
- package/src/kit/Popover.tsx +121 -0
- package/src/kit/RangeField.tsx +53 -0
- package/src/kit/RatingField.tsx +62 -0
- package/src/kit/Stat.tsx +60 -0
- package/src/kit/Switch.tsx +28 -0
- package/src/kit/Tag.tsx +32 -0
- package/src/kit/Tile.tsx +74 -0
- package/src/kit/TimeField.tsx +157 -0
- package/src/kit/context.tsx +246 -0
- package/src/kit/dates.ts +150 -0
- package/src/kit/messages.ts +160 -0
- package/src/kit/numbers.ts +44 -0
- package/src/kit/options.ts +30 -0
- package/src/kit/uploads.ts +244 -0
- package/src/kit/waveform.ts +84 -0
- package/src/locale.ts +6 -0
- package/src/markdown.ts +69 -0
- package/src/marks.tsx +328 -0
- package/src/reveal.ts +52 -0
- package/src/stepGlyphs.ts +83 -0
- package/src/styles.css +6961 -0
- package/src/surface/Form.tsx +299 -0
- package/src/surface/Markdown.tsx +55 -0
- package/src/surface/Surface.tsx +605 -0
- package/src/surface/scope.ts +82 -0
- package/src/surface/values.ts +361 -0
- package/src/tint.ts +14 -0
- package/src/types.ts +596 -0
- package/src/useAgentFace.ts +47 -0
- package/src/useCall.ts +292 -0
- package/src/useConversation.ts +627 -0
- package/src/useDictation.ts +83 -0
- package/src/useLive.ts +115 -0
- package/src/usePolled.ts +60 -0
- package/src/voice/Orb.tsx +45 -0
- package/src/voice/StillOrb.tsx +29 -0
- package/src/voice/WebGLOrb.tsx +98 -0
- package/src/voice/fluidDots.tsx +171 -0
- package/src/voice/motion.ts +112 -0
- package/src/voice/orbs.ts +39 -0
- package/src/voice/useReducedMotion.ts +24 -0
- package/src/voice/wave.tsx +129 -0
- package/src/voice/webgl.ts +209 -0
- package/src/widget/css.d.ts +5 -0
- package/src/widget/element.tsx +535 -0
- package/src/widget/index.ts +10 -0
- package/src/widget/mount.ts +27 -0
- package/src/widget/widget.css +253 -0
- package/dist/SignalcoreProvider.d.ts +0 -113
- package/dist/SignalcoreProvider.js +0 -84
- package/dist/automations/AppMarks.d.ts +0 -11
- package/dist/automations/AppMarks.js +0 -18
- package/dist/automations/AutomationCard.d.ts +0 -27
- package/dist/automations/AutomationCard.js +0 -53
- package/dist/automations/AutomationsWindow.d.ts +0 -38
- package/dist/automations/AutomationsWindow.js +0 -57
- package/dist/automations/Card.d.ts +0 -46
- package/dist/automations/Card.js +0 -45
- package/dist/automations/ScheduleMarks.d.ts +0 -13
- package/dist/automations/ScheduleMarks.js +0 -24
- package/dist/automations/presentationIcons.d.ts +0 -7
- package/dist/automations/presentationIcons.js +0 -67
- package/dist/automations/schedule.d.ts +0 -25
- package/dist/automations/schedule.js +0 -59
- package/dist/automations/time.d.ts +0 -3
- package/dist/automations/time.js +0 -25
- package/dist/browser/BrowserView.d.ts +0 -43
- package/dist/browser/BrowserView.js +0 -94
- package/dist/browser/room.d.ts +0 -21
- package/dist/browser/room.js +0 -33
- package/dist/browser/useBrowserSession.d.ts +0 -17
- package/dist/browser/useBrowserSession.js +0 -67
- package/dist/builder/BuilderChat.d.ts +0 -32
- package/dist/builder/BuilderChat.js +0 -41
- package/dist/builder/BuilderTranscript.d.ts +0 -49
- package/dist/builder/BuilderTranscript.js +0 -62
- package/dist/builder/StatusLine.d.ts +0 -8
- package/dist/builder/StatusLine.js +0 -17
- package/dist/builder/answer.d.ts +0 -7
- package/dist/builder/answer.js +0 -8
- package/dist/builder/blocks.d.ts +0 -31
- package/dist/builder/blocks.js +0 -74
- package/dist/builder/items.d.ts +0 -15
- package/dist/builder/items.js +0 -21
- package/dist/builder/steps.d.ts +0 -20
- package/dist/builder/steps.js +0 -120
- package/dist/builder/time.d.ts +0 -4
- package/dist/builder/time.js +0 -24
- package/dist/builder/useBuildSession.d.ts +0 -36
- package/dist/builder/useBuildSession.js +0 -130
- package/dist/builder/workingOn.d.ts +0 -7
- package/dist/builder/workingOn.js +0 -15
- package/dist/chat/AgentChat.d.ts +0 -37
- package/dist/chat/AgentChat.js +0 -21
- package/dist/chat/ChatView.d.ts +0 -25
- package/dist/chat/ChatView.js +0 -34
- package/dist/chat/Composer.d.ts +0 -20
- package/dist/chat/Composer.js +0 -22
- package/dist/chat/MessageRow.d.ts +0 -22
- package/dist/chat/MessageRow.js +0 -32
- package/dist/chat/arrival.d.ts +0 -15
- package/dist/chat/arrival.js +0 -25
- package/dist/chat/history.d.ts +0 -24
- package/dist/chat/history.js +0 -45
- package/dist/chat/moments.d.ts +0 -25
- package/dist/chat/moments.js +0 -72
- package/dist/chat/types.d.ts +0 -12
- package/dist/chat/types.js +0 -1
- package/dist/chat/useComposer.d.ts +0 -46
- package/dist/chat/useComposer.js +0 -62
- package/dist/chat/useConversation.d.ts +0 -35
- package/dist/chat/useConversation.js +0 -71
- package/dist/chat/useStickToBottom.d.ts +0 -9
- package/dist/chat/useStickToBottom.js +0 -33
- package/dist/chat/useTurns.d.ts +0 -49
- package/dist/chat/useTurns.js +0 -111
- package/dist/classes.d.ts +0 -2
- package/dist/development.d.ts +0 -5
- package/dist/development.js +0 -8
- package/dist/flow/FlowWindow.d.ts +0 -42
- package/dist/flow/FlowWindow.js +0 -126
- package/dist/flow/index.d.ts +0 -16
- package/dist/flow/index.js +0 -16
- package/dist/flow/kinds.d.ts +0 -23
- package/dist/flow/kinds.js +0 -48
- package/dist/flow/map.d.ts +0 -44
- package/dist/flow/map.js +0 -225
- package/dist/flow/nodePaths.d.ts +0 -7
- package/dist/flow/nodePaths.js +0 -17
- package/dist/flow/relations.d.ts +0 -42
- package/dist/flow/relations.js +0 -174
- package/dist/host.d.ts +0 -124
- package/dist/host.js +0 -1
- package/dist/live/LiveView.d.ts +0 -28
- package/dist/live/LiveView.js +0 -55
- package/dist/live/capture.d.ts +0 -20
- package/dist/live/capture.js +0 -100
- package/dist/live/follow.d.ts +0 -26
- package/dist/live/follow.js +0 -72
- package/dist/live/paint.d.ts +0 -13
- package/dist/live/paint.js +0 -77
- package/dist/live/useLiveStream.d.ts +0 -16
- package/dist/live/useLiveStream.js +0 -57
- package/dist/look.d.ts +0 -33
- package/dist/look.js +0 -241
- package/dist/messages/en.d.ts +0 -3
- package/dist/messages/en.js +0 -251
- package/dist/messages/es.d.ts +0 -3
- package/dist/messages/es.js +0 -251
- package/dist/messages/index.d.ts +0 -16
- package/dist/messages/index.js +0 -33
- package/dist/messages/types.d.ts +0 -246
- package/dist/messages/types.js +0 -1
- package/dist/parts/Button.d.ts +0 -18
- package/dist/parts/Button.js +0 -17
- package/dist/parts/Icon.d.ts +0 -39
- package/dist/parts/Icon.js +0 -37
- package/dist/parts/Input.d.ts +0 -6
- package/dist/parts/Input.js +0 -11
- package/dist/parts/Spinner.d.ts +0 -2
- package/dist/parts/Spinner.js +0 -7
- package/dist/patterns.d.ts +0 -15
- package/dist/patterns.js +0 -5
- package/dist/presets.d.ts +0 -82
- package/dist/presets.js +0 -81
- package/dist/primitives.d.ts +0 -22
- package/dist/primitives.js +0 -58
- package/dist/request/RequestCard.d.ts +0 -34
- package/dist/request/RequestCard.js +0 -101
- package/dist/request/approval/ApprovalPreview.d.ts +0 -5
- package/dist/request/approval/ApprovalPreview.js +0 -27
- package/dist/request/approval/shapes.d.ts +0 -32
- package/dist/request/approval/shapes.js +0 -68
- package/dist/request/apps.d.ts +0 -11
- package/dist/request/apps.js +0 -27
- package/dist/request/cards.d.ts +0 -12
- package/dist/request/cards.js +0 -24
- package/dist/request/deliver.d.ts +0 -12
- package/dist/request/deliver.js +0 -21
- package/dist/request/handOver.d.ts +0 -18
- package/dist/request/handOver.js +0 -33
- package/dist/request/kinds/Approval.d.ts +0 -3
- package/dist/request/kinds/Approval.js +0 -9
- package/dist/request/kinds/Choice.d.ts +0 -3
- package/dist/request/kinds/Choice.js +0 -8
- package/dist/request/kinds/Connect.d.ts +0 -9
- package/dist/request/kinds/Connect.js +0 -119
- package/dist/request/kinds/FreeText.d.ts +0 -3
- package/dist/request/kinds/FreeText.js +0 -20
- package/dist/request/kinds/HandOver.d.ts +0 -13
- package/dist/request/kinds/HandOver.js +0 -20
- package/dist/request/kinds/Secret.d.ts +0 -7
- package/dist/request/kinds/Secret.js +0 -49
- package/dist/request/kinds/Upload.d.ts +0 -6
- package/dist/request/kinds/Upload.js +0 -47
- package/dist/request/types.d.ts +0 -83
- package/dist/request/types.js +0 -1
- package/dist/safeUrl.d.ts +0 -6
- package/dist/safeUrl.js +0 -8
- package/dist/stream/builder.d.ts +0 -176
- package/dist/stream/builder.js +0 -202
- package/dist/stream/reduce.d.ts +0 -51
- package/dist/stream/reduce.js +0 -58
- package/dist/stream/untilAborted.d.ts +0 -7
- package/dist/stream/untilAborted.js +0 -32
- package/dist/stream/useRunStream.d.ts +0 -26
- package/dist/stream/useRunStream.js +0 -137
- package/dist/styles.css +0 -2318
- package/dist/surface/HostNode.d.ts +0 -14
- package/dist/surface/HostNode.js +0 -30
- package/dist/surface/Node.d.ts +0 -7
- package/dist/surface/Node.js +0 -49
- package/dist/surface/SurfaceBoundary.d.ts +0 -26
- package/dist/surface/SurfaceBoundary.js +0 -20
- package/dist/surface/bindings.d.ts +0 -14
- package/dist/surface/bindings.js +0 -37
- package/dist/surface/check.d.ts +0 -27
- package/dist/surface/check.js +0 -102
- package/dist/surface/format.d.ts +0 -10
- package/dist/surface/format.js +0 -32
- package/dist/surface/hosts.d.ts +0 -6
- package/dist/surface/hosts.js +0 -11
- package/dist/surface/scene.d.ts +0 -21
- package/dist/surface/scene.js +0 -8
- package/dist/surface/templates/Browser.d.ts +0 -6
- package/dist/surface/templates/Browser.js +0 -11
- package/dist/surface/templates/Chart.d.ts +0 -6
- package/dist/surface/templates/Chart.js +0 -13
- package/dist/surface/templates/Choices.d.ts +0 -3
- package/dist/surface/templates/Choices.js +0 -12
- package/dist/surface/templates/Form.d.ts +0 -3
- package/dist/surface/templates/Form.js +0 -44
- package/dist/surface/templates/KeyValue.d.ts +0 -3
- package/dist/surface/templates/KeyValue.js +0 -7
- package/dist/surface/templates/Layout.d.ts +0 -5
- package/dist/surface/templates/Layout.js +0 -16
- package/dist/surface/templates/Live.d.ts +0 -6
- package/dist/surface/templates/Live.js +0 -9
- package/dist/surface/templates/Media.d.ts +0 -5
- package/dist/surface/templates/Media.js +0 -13
- package/dist/surface/templates/Slots.d.ts +0 -3
- package/dist/surface/templates/Slots.js +0 -19
- package/dist/surface/templates/Table.d.ts +0 -3
- package/dist/surface/templates/Table.js +0 -12
- package/dist/surface/templates/Text.d.ts +0 -4
- package/dist/surface/templates/Text.js +0 -15
- package/dist/surface/templates/index.d.ts +0 -7
- package/dist/surface/templates/index.js +0 -32
- package/dist/surface/types.d.ts +0 -68
- package/dist/surface/types.js +0 -6
- package/dist/theme.d.ts +0 -62
- package/dist/theme.js +0 -9
- package/dist/useTokens.d.ts +0 -7
- package/dist/useTokens.js +0 -41
- package/dist/voice/VoiceWindow.d.ts +0 -18
- package/dist/voice/VoiceWindow.js +0 -35
- package/dist/voice/index.d.ts +0 -13
- package/dist/voice/index.js +0 -12
- package/dist/voice/meter.d.ts +0 -16
- package/dist/voice/meter.js +0 -52
- package/dist/voice/orbColors.d.ts +0 -16
- package/dist/voice/orbColors.js +0 -75
- package/dist/voice/pushable.d.ts +0 -16
- package/dist/voice/pushable.js +0 -56
- package/dist/voice/useDictation.d.ts +0 -24
- package/dist/voice/useDictation.js +0 -60
- package/dist/voice/useReducedMotion.d.ts +0 -5
- package/dist/voice/useVoice.d.ts +0 -70
- package/dist/voice/useVoice.js +0 -197
- package/dist/voice/useVoicePrimitives.d.ts +0 -7
- package/dist/voice/useVoicePrimitives.js +0 -11
- package/dist/voice/voice.d.ts +0 -55
- package/dist/voice/voice.js +0 -9
- package/dist/voice/webSpeech.d.ts +0 -33
- package/dist/voice/webSpeech.js +0 -258
- package/dist/widget.js.map +0 -1
- package/dist/windows/AgentSpace.d.ts +0 -78
- package/dist/windows/AgentSpace.js +0 -135
- package/dist/windows/Captions.d.ts +0 -13
- package/dist/windows/Captions.js +0 -22
- package/dist/windows/Dock.d.ts +0 -37
- package/dist/windows/Dock.js +0 -22
- package/dist/windows/Journal.d.ts +0 -15
- package/dist/windows/Journal.js +0 -19
- package/dist/windows/KindMark.d.ts +0 -7
- package/dist/windows/KindMark.js +0 -31
- package/dist/windows/Pending.d.ts +0 -8
- package/dist/windows/Pending.js +0 -17
- package/dist/windows/Stage.d.ts +0 -18
- package/dist/windows/Stage.js +0 -21
- package/dist/windows/StatusLabel.d.ts +0 -9
- package/dist/windows/StatusLabel.js +0 -7
- package/dist/windows/Strip.d.ts +0 -17
- package/dist/windows/Strip.js +0 -42
- package/dist/windows/SurfaceWindow.d.ts +0 -20
- package/dist/windows/SurfaceWindow.js +0 -12
- package/dist/windows/WindowFrame.d.ts +0 -23
- package/dist/windows/WindowFrame.js +0 -22
- package/dist/windows/hearing.d.ts +0 -11
- package/dist/windows/hearing.js +0 -26
- package/dist/windows/kinds.d.ts +0 -47
- package/dist/windows/kinds.js +0 -28
- package/dist/windows/reduce.d.ts +0 -98
- package/dist/windows/reduce.js +0 -151
- package/dist/windows/registry.d.ts +0 -18
- package/dist/windows/registry.js +0 -61
- package/dist/windows/reveal.d.ts +0 -9
- package/dist/windows/reveal.js +0 -6
- package/dist/windows/space.d.ts +0 -21
- package/dist/windows/space.js +0 -6
- package/dist/windows/status.d.ts +0 -11
- package/dist/windows/status.js +0 -30
- package/dist/windows/types.d.ts +0 -15
- package/dist/windows/types.js +0 -1
- package/dist/windows/useLoaded.d.ts +0 -44
- package/dist/windows/useLoaded.js +0 -58
- package/dist/windows/useUnread.d.ts +0 -7
- package/dist/windows/useUnread.js +0 -21
- package/dist/windows/useWindows.d.ts +0 -70
- package/dist/windows/useWindows.js +0 -60
package/README.md
CHANGED
|
@@ -1,567 +1,950 @@
|
|
|
1
1
|
# @signalcore/ui
|
|
2
2
|
|
|
3
|
-
SignalCore's
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
SignalCore's agents inside your product, for the people they serve: your customers talk to the
|
|
4
|
+
agent (`AgentChat`), call it (`VoiceCall`, with the orb), or meet it whole (`AgentSpace`: the
|
|
5
|
+
chat, and the call one switch away), in your React pages or, without React, as
|
|
6
|
+
`<signalcore-chat>`. Every piece looks like your product: your type, your colours, your CSS
|
|
7
|
+
([Theming](#theming)). Every
|
|
8
|
+
piece is built only on the public API (the HTTP routes and the conversation's socket,
|
|
9
|
+
[v3/README.md](../README.md)) and its own hooks; nothing in the platform knows it exists. Our own
|
|
10
|
+
app imports it like any host would; if a component ever needs a private import, the package is
|
|
11
|
+
incomplete. Building and configuring an agent stays in our app and in your backend.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @signalcore/ui react react-dom # pnpm add, yarn add alike
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`@signalcore/ui/voice` loads).
|
|
19
|
+
ESM with its types, one module per file (your bundler keeps what you use; a call's LiveKit loads
|
|
20
|
+
with the first call), and one stylesheet. React 19.2 or later. In Next.js's App Router, draw the
|
|
21
|
+
components from a client component (`"use client"`).
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const client = new Signalcore({ apiKey });
|
|
24
|
-
|
|
25
|
-
function Run({ runId }: { runId: string }) {
|
|
26
|
-
const run = useRunStream(runId);
|
|
27
|
-
return (
|
|
28
|
-
<>
|
|
29
|
-
{run.surfaces.map((surface) => (
|
|
30
|
-
<Surface key={surface.id} surface={surface} onAction={(action) => send(action)} />
|
|
31
|
-
))}
|
|
32
|
-
{run.requests.map((card) => (
|
|
33
|
-
<RequestCard key={card.request_id} card={card} />
|
|
34
|
-
))}
|
|
35
|
-
</>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
<SignalcoreProvider client={client}>
|
|
40
|
-
<Run runId="run_..." />
|
|
41
|
-
</SignalcoreProvider>;
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## The two chats
|
|
45
|
-
|
|
46
|
-
One renderer, two chats, split by who is talking (ADR 0009). Both reach the API through the
|
|
47
|
-
provider's `client`, and both are made of parts a host can compose.
|
|
48
|
-
|
|
49
|
-
**`AgentChat`** is the chat of the people an agent **serves**: their messages and the agent's, the
|
|
50
|
-
agent's surfaces drawn inline (with their text twin where they cannot be), and a box to write in.
|
|
51
|
-
It speaks to the agent as the person and can never change it.
|
|
23
|
+
Browsers: Chrome and Edge 123, Firefox 120 and Safari 17.5 (macOS and iOS), and later; `widget.js`
|
|
24
|
+
is built for them. These read `light-dark()`, which every default colour is. If your bundler lowers
|
|
25
|
+
CSS for older ones (Vite's default `build.cssTarget` does), set it to these: lowered,
|
|
26
|
+
`light-dark()` becomes a polyfill that ignores a `color-scheme` set anywhere its build did not
|
|
27
|
+
see, and the colours come out invalid.
|
|
52
28
|
|
|
53
29
|
```tsx
|
|
54
|
-
import {
|
|
55
|
-
import { AgentChat, SignalcoreProvider } from "@signalcore/ui";
|
|
30
|
+
import { AgentSpace, createClient } from "@signalcore/ui";
|
|
56
31
|
import "@signalcore/ui/styles.css";
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
const client = new Signalcore({ endUserToken });
|
|
60
|
-
|
|
61
|
-
<SignalcoreProvider client={client}>
|
|
62
|
-
<AgentChat agentId="agt_…" thread="order-7" placeholder="Ask about your order" />
|
|
63
|
-
</SignalcoreProvider>;
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
| Prop | What |
|
|
67
|
-
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
-
| `agentId` | The agent the person talks to. |
|
|
69
|
-
| `thread` | The conversation to speak in; the agent remembers it. The API's default if none. |
|
|
70
|
-
| `placeholder` | The box's hint. `autoFocus` focuses it. |
|
|
71
|
-
| `onSurfaceAction` | Tells you what the person picked or submitted. The chat has already sent it. |
|
|
72
|
-
| `loadHistory` | `(thread) => Promise<ChatMessage[]>`. Without it the chat reads the caller's own conversation through `endUsers.myConversation` when the client has it (an end user's token or a publishable key do), and otherwise starts empty. `messagesFromConversation` turns the API's messages into `ChatMessage`s. |
|
|
73
|
-
|
|
74
|
-
- A message is sent with `agents.invoke({ input, thread })`; a pick or a form on a surface goes
|
|
75
|
-
back as `agents.invoke({ action, thread })`, shown as what the person said ("Martes 10:00"). Only
|
|
76
|
-
the last thing said takes a pick: once the person answers, a surface is a record.
|
|
77
|
-
- **It never shows an approval, a connection or a secret.** When the answer has `waiting_for`, the
|
|
78
|
-
run paused for the owner to confirm something: the person sees a quiet "waiting for a
|
|
79
|
-
confirmation", the chat follows the run (`useRunStream`) until it is done, and then shows what
|
|
80
|
-
arrived. The send button is not usable while a turn runs; the box stays free to write in.
|
|
81
|
-
- Accessible: new words from the agent go to a live region, the box and the conversation are
|
|
82
|
-
labelled, and everything works from the keyboard (Enter sends, Shift+Enter breaks the line).
|
|
83
|
-
|
|
84
|
-
**`BuilderChat`** is the chat of the owner who **shapes** the agent: their messages, what the
|
|
85
|
-
builder says and does as it works (steps open to what came back), the surfaces it shows, the cards
|
|
86
|
-
it asks them to answer in place, and what changed.
|
|
33
|
+
const client = createClient({ baseUrl: "https://signalcore.ai/v1", getToken });
|
|
87
34
|
|
|
88
|
-
|
|
89
|
-
<BuilderChat agentId="agt_…" sessionId={sessionId} onSession={setSessionId} />
|
|
35
|
+
<AgentSpace client={client} agentId="agt_…" />;
|
|
90
36
|
```
|
|
91
37
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
38
|
+
In this repo the web app depends on it as `workspace:*` and reads its source through the
|
|
39
|
+
exports' `@signalcore/source` condition (`v3/web`'s Vite and TypeScript set it), so nothing waits
|
|
40
|
+
for a build. `pnpm build` writes `dist/` (`tsdown` for the library, Vite for `widget.js`);
|
|
41
|
+
`pnpm widget` serves `widget.html` with the widget over the source, at
|
|
42
|
+
`http://localhost:5176/widget.html?base=…&agent=…&key=…`.
|
|
43
|
+
|
|
44
|
+
Every component, live, is documented at [signalcore.ai/ui](https://signalcore.ai/ui): Storybook
|
|
45
|
+
over the source (`stories/`), on a demo API in the page, with each component's props read from
|
|
46
|
+
its types and a toolbar for the language, the theme, `--sc-accent` and `--sc-radius`.
|
|
47
|
+
`pnpm storybook` serves it at `http://localhost:6006`; `pnpm storybook:build` writes
|
|
48
|
+
`storybook-static/`, which the API serves at `/ui` (`v3/api/app/webapp.py`).
|
|
49
|
+
|
|
50
|
+
## Who the page speaks for
|
|
51
|
+
|
|
52
|
+
`createClient` takes exactly one way to sign in (v3/README.md, Credentials):
|
|
53
|
+
|
|
54
|
+
| Mode | Options | Requests send | The socket adds |
|
|
55
|
+
| ----------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
56
|
+
| Our own app | `{ baseUrl: "/api", credentials: "cookie" }` | the session cookie, on the page's origin | nothing |
|
|
57
|
+
| A person, token given | `{ baseUrl, token }` | `Authorization: Bearer <token>` | `?token=` |
|
|
58
|
+
| A person, token fetched | `{ baseUrl, getToken: () => Promise<string> }` | the same, asked of `getToken` when first needed and kept; on a 401 it is asked once more and the request sent again | `?token=`, a fresh one after a socket that couldn't open |
|
|
59
|
+
| An anonymous visitor | `{ baseUrl, publishableKey, visitorId? }` | `Authorization: Bearer pk_…` and `SignalCore-Visitor: <id>` | `?token=pk_…&visitor=<id>` |
|
|
60
|
+
|
|
61
|
+
`baseUrl` is the public API with its prefix (`https://signalcore.ai/v1`). Only our own app
|
|
62
|
+
sends the browser's cookie; every other mode sends none (`credentials: "omit"`). A visitor without
|
|
63
|
+
a `visitorId` is given one (32 random hex characters) kept in `localStorage` under
|
|
64
|
+
`signalcore.visitor` (`VISITOR_KEY`), so they find their conversation on their next visit; where
|
|
65
|
+
the page can keep nothing, the id lasts the page. The publishable key (`pk_…`, made in our app:
|
|
66
|
+
one agent, the sites it may be used from) is public; the visitor id is all that makes a
|
|
67
|
+
conversation theirs.
|
|
68
|
+
|
|
69
|
+
A person's token is minted by your backend with a secret key that may `talk`, for one hour, naming
|
|
70
|
+
who the person is in your product and the agents they may reach (`SignalCore-Org` when your key
|
|
71
|
+
acts for an organization you manage). Your page asks your backend for it:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# FastAPI, httpx: GET /signalcore/token for the person signed in to your product.
|
|
75
|
+
import httpx
|
|
76
|
+
|
|
77
|
+
SIGNALCORE = "https://signalcore.ai/v1"
|
|
78
|
+
|
|
79
|
+
@app.get("/signalcore/token")
|
|
80
|
+
async def signalcore_token(user = Depends(current_user)):
|
|
81
|
+
async with httpx.AsyncClient() as http:
|
|
82
|
+
answer = await http.post(
|
|
83
|
+
f"{SIGNALCORE}/tokens",
|
|
84
|
+
headers={"Authorization": f"Bearer {SECRET_KEY}"},
|
|
85
|
+
json={"agents": ["agt_…"], "external_id": user.id, "name": user.name, "email": user.email},
|
|
86
|
+
)
|
|
87
|
+
answer.raise_for_status()
|
|
88
|
+
return answer.json() # {"token": "…", "expires_at": "…"}
|
|
122
89
|
```
|
|
123
90
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`<signalcore-chat>` is `AgentChat` as a web component in a shadow root, for pages that are not
|
|
144
|
-
React. One script, with React and the SDK bundled in (`dist/widget.js`, about 108 kB gzipped):
|
|
145
|
-
|
|
146
|
-
```html
|
|
147
|
-
<script src="https://signalcore.ai/app/widget.js"></script>
|
|
148
|
-
|
|
149
|
-
<!-- An anonymous visitor: a publishable key, bound to the origins it lists. -->
|
|
150
|
-
<signalcore-chat agent="agt_…" publishable-key="pk_live_…"></signalcore-chat>
|
|
151
|
-
|
|
152
|
-
<!-- A person of your product: a token your backend minted for them. -->
|
|
153
|
-
<signalcore-chat agent="agt_…" end-user-token="…" thread="order-7"></signalcore-chat>
|
|
91
|
+
```ts
|
|
92
|
+
// Any TypeScript server with fetch: the same, for the person signed in.
|
|
93
|
+
export async function signalcoreToken(user: { id: string; name: string; email: string }) {
|
|
94
|
+
const answer = await fetch("https://signalcore.ai/v1/tokens", {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${process.env.SIGNALCORE_SECRET_KEY}`,
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
agents: ["agt_…"],
|
|
102
|
+
external_id: user.id,
|
|
103
|
+
name: user.name,
|
|
104
|
+
email: user.email,
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
if (!answer.ok) throw new Error(`SignalCore refused the token: ${answer.status}`);
|
|
108
|
+
return (await answer.json()) as { token: string; expires_at: string };
|
|
109
|
+
}
|
|
154
110
|
```
|
|
155
111
|
|
|
156
|
-
| Attribute | What |
|
|
157
|
-
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
158
|
-
| `agent`, `thread`, `placeholder` | What `AgentChat` takes. `autofocus` focuses the box. |
|
|
159
|
-
| `end-user-token` | The SDK's `endUserToken`. |
|
|
160
|
-
| `publishable-key`, `visitor-id` | The SDK's `publishableKey` and `visitorId`. Without a `visitor-id` the widget keeps one in this browser. |
|
|
161
|
-
| `base-url` | Where the API is (`https://signalcore.ai` by default). |
|
|
162
|
-
| `locale` | `es` (the default) or `en`: the language of its words and dates. See [Words](#words). |
|
|
163
|
-
|
|
164
|
-
The `client` property, set from JavaScript, wins over the attributes: give it any `UiClient` (an SDK
|
|
165
|
-
client with other options, your own proxy). A secret key is not an option here, on purpose. The
|
|
166
|
-
`messages` property, also from JavaScript, changes single words (see [Words](#words)).
|
|
167
|
-
|
|
168
|
-
It draws in a shadow root with the package's styles inside, so the page and the widget do not
|
|
169
|
-
touch. The page themes it from outside:
|
|
170
|
-
|
|
171
|
-
- **Tokens.** `--sc-*` custom properties on the element or above it inherit in
|
|
172
|
-
(`signalcore-chat { --sc-accent: #b4532a; height: 600px }`), and so do the page's font and text
|
|
173
|
-
color. The element is `display: block; height: 32rem` until your CSS says otherwise.
|
|
174
|
-
- **Dark mode.** The widget follows a `data-theme` of `light` or `dark` on itself or the nearest
|
|
175
|
-
ancestor (usually `<html>`), and `prefers-color-scheme` without one.
|
|
176
|
-
- **Parts.** Every slot is a part: `signalcore-chat::part(root)`, `::part(composer)`,
|
|
177
|
-
`::part(message)` (and `::part(message--person)`, `::part(message--agent)`), and the rest of
|
|
178
|
-
`SLOTS`.
|
|
179
|
-
- **Events.** `surface-action` (bubbles, composed) carries what the person picked in `detail`.
|
|
180
|
-
|
|
181
|
-
**`<signalcore-space>`** is [`AgentSpace`](#the-shell-agentspace) in the same script, taking the
|
|
182
|
-
same credentials: `<signalcore-space agent="agt_…" end-user-token="…" windows="flow" mode="text">`.
|
|
183
|
-
`windows` lists the app's windows to open (the first takes the stage), `mode` is `text` or `voice`
|
|
184
|
-
(the element writes the person's choice back and dispatches `mode-change`), and `viewer` is
|
|
185
|
-
`end_user` (the default: a widget serves the people of your product) or `owner`, for a page that
|
|
186
|
-
sets an owner's `client`. The script carries the conversation, the browser, the live stream and the
|
|
187
|
-
pages the agent places, and neither the flow map nor the voice: those windows show what the agent
|
|
188
|
-
said they show, and the voice stays on text. It is `display: block; height: 36rem` by default.
|
|
189
|
-
|
|
190
|
-
If it cannot start (no agent, credentials that make no sense) it shows a quiet line and says why in
|
|
191
|
-
the console. `pnpm build` produces `dist/widget.js` after the library; `import "@signalcore/ui/widget"`
|
|
192
|
-
registers the element from a bundler instead. From JavaScript,
|
|
193
|
-
`defineSignalcoreChat({ tag, createClient })` registers it under another tag, or with your own way
|
|
194
|
-
of making the client.
|
|
195
|
-
|
|
196
|
-
## What it draws
|
|
197
|
-
|
|
198
|
-
**`Surface`** draws a surface of the `signalcore-basic.v1` catalog: cards, key-values, tables,
|
|
199
|
-
charts, callouts, images, links, choice lists, slot pickers, forms, a live browser view and live
|
|
200
|
-
streams. A
|
|
201
|
-
surface is data, never code: it draws only catalog components (and the host's own, see
|
|
202
|
-
[Components](#your-own-components)), resolves their bindings, and shows only `https://` addresses.
|
|
203
|
-
|
|
204
|
-
- What the user does (a pick, a form, a button of your own component) calls
|
|
205
|
-
`onAction({ surface_id, name, data, label })`, the shape the API's `Action` expects. Without
|
|
206
|
-
`onAction` the surface is a record: nothing on it can be pressed.
|
|
207
|
-
- **Every surface carries a text twin** (`fallback_text`). A surface whose payload is damaged, that
|
|
208
|
-
names a component nobody here knows (or one of yours that this page has not registered), or whose
|
|
209
|
-
drawing throws shows its text twin instead, and the rest of your page carries on.
|
|
210
|
-
- The browser view frames a live page in a sandbox, only from an `https://` address.
|
|
211
|
-
|
|
212
|
-
**`RequestCard`** draws what an agent needs from the owner: a question, a choice, an app to
|
|
213
|
-
connect, a file, an approval, a secret, or the controls of its browser. These are the owner's
|
|
214
|
-
decisions about safety, so the platform draws them and nobody else can: an agent cannot fake an
|
|
215
|
-
"Aprobar" button because the catalog has no such component, and **a host cannot replace the card
|
|
216
|
-
either**. It follows levels 1 to 4 of [Styling](#styling) (tokens, presets, `classNames`,
|
|
217
|
-
`unstyled`) and takes `apps` to say how your apps look; the provider's `components` never reach it.
|
|
218
|
-
|
|
219
|
-
Answering goes through the provider's `client` (`requests.resolve`; a secret goes to `vault.put`, a
|
|
220
|
-
file to `documents.addFile`, an app is checked with `connections.refresh`). Without a client,
|
|
221
|
-
`onResolve` is how the answer gets anywhere. Secrets and files never pass through your callbacks.
|
|
222
|
-
|
|
223
|
-
**`useRunStream(runId)`** follows a run through `client.runs.stream` and gives you the events, the
|
|
224
|
-
surfaces and the requests (as cards) it accumulates, and `status` (`connecting`, `streaming`,
|
|
225
|
-
`paused`, `done`, `failed`). When the run pauses on a request it closes the stream and opens it
|
|
226
|
-
again after the last event once the request is answered, wherever that happened. A host that never
|
|
227
|
-
draws the cards passes `{ cards: false }`, and the API is not asked what they say.
|
|
228
|
-
|
|
229
|
-
## Windows of an agent
|
|
230
|
-
|
|
231
|
-
The chats show an agent talking; a window shows the agent itself, as its owner reads it. The web
|
|
232
|
-
app's agent page and map are these two, drawn by the platform from facts and never generated. They
|
|
233
|
-
are **owner windows**: they read the business's overview and the agent's package, so they need an
|
|
234
|
-
owner's credential (`agents:read`), and an end user's token gets a `403`.
|
|
235
|
-
|
|
236
112
|
```tsx
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<div style={{ height: 480 }}>
|
|
243
|
-
<FlowWindow agentId="agt_..." onOpen={showPart} />
|
|
244
|
-
</div>
|
|
245
|
-
</SignalcoreProvider>;
|
|
113
|
+
// The page: a token whenever the client needs one.
|
|
114
|
+
const client = createClient({
|
|
115
|
+
baseUrl: "https://signalcore.ai/v1",
|
|
116
|
+
getToken: async () => (await (await fetch("/signalcore/token")).json()).token,
|
|
117
|
+
});
|
|
246
118
|
```
|
|
247
119
|
|
|
248
|
-
|
|
249
|
-
automation, with what a model wrote about the part over what the platform knows (when it runs,
|
|
250
|
-
its apps, its state, how the last run went). `focusedId`, `attention` and `building` follow the
|
|
251
|
-
builder to the automation it is working on. `AutomationCard`, `ScheduleMarks` and `AppMarks` are
|
|
252
|
-
its parts, exported for a page you lay out yourself.
|
|
253
|
-
- **`FlowWindow`** is the agent as a map of its channels, automations, skills, actions, records and
|
|
254
|
-
apps, with a line for each call, read from its package. A part is picked with a press (`onSelect`,
|
|
255
|
-
`selectedId`) and opened with a second one or Enter (`onOpen`). It is in its own entry because
|
|
256
|
-
`@xyflow/react` and its stylesheet are not small: the core entry imports nothing from it, and a
|
|
257
|
-
test keeps it that way. `agentMap`, `relationsOf` and `kindOf` are exported from it as plain
|
|
258
|
-
functions.
|
|
259
|
-
- **They fetch for themselves** when given `agentId` and the client has `agents.overview` (and
|
|
260
|
-
`agents.get` for the map's files); they say they are loading, or that they could not load. What
|
|
261
|
-
is passed as props (`overview`, `files`) is what is drawn, and nothing is asked. `apps` says how
|
|
262
|
-
your apps look, as in `RequestCard`.
|
|
263
|
-
- They follow levels 1 to 4 of [Styling](#styling); their slots are `automations`, `automation`,
|
|
264
|
-
`schedule`, `app-marks`, `flow`, `flow__node` and `status`.
|
|
265
|
-
|
|
266
|
-
## The shell: `AgentSpace`
|
|
267
|
-
|
|
268
|
-
Everything a person sees of an agent is a window ([ADR 0011](../../docs/adr/0011-windows.md)), and
|
|
269
|
-
`AgentSpace` is where they sit: a **stage** with the one that matters now, a **strip** of the open
|
|
270
|
-
ones and a **dock** to talk from. The conversation is always one of them. The agent adds more as it
|
|
271
|
-
works, placing a page of its own (`show` with a `window`: a generated window, a surface) or opening
|
|
272
|
-
one of the app's (`open_window`: `automations`, `flow`, `voice`), and the person closes and reopens
|
|
273
|
-
them.
|
|
274
|
-
|
|
275
|
-
```tsx
|
|
276
|
-
import { AgentSpace, AutomationsWindow, SignalcoreProvider } from "@signalcore/ui";
|
|
277
|
-
import { FlowWindow } from "@signalcore/ui/flow";
|
|
278
|
-
import { VoiceWindow } from "@signalcore/ui/voice";
|
|
279
|
-
|
|
280
|
-
// Hoisted: a new object each render would draw the shell again for nothing.
|
|
281
|
-
const windows = { automations: AutomationsWindow, flow: FlowWindow, voice: VoiceWindow };
|
|
282
|
-
|
|
283
|
-
<SignalcoreProvider client={client} viewer="owner" windows={windows}>
|
|
284
|
-
<AgentSpace
|
|
285
|
-
agentId="agt_…"
|
|
286
|
-
thread="order-7"
|
|
287
|
-
windows={["automations"]}
|
|
288
|
-
renderVoice={(voice) => <VoiceWindow {...voice} />}
|
|
289
|
-
/>
|
|
290
|
-
</SignalcoreProvider>;
|
|
291
|
-
```
|
|
120
|
+
## AgentChat
|
|
292
121
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
`voice` are yours to register, each from its own import, so a page that never shows the map does
|
|
296
|
-
not load its graph library. Every component takes the same props (`DefaultWindowProps`: the
|
|
297
|
-
agent, the thread, a label, the agent's words for the window, what the host opened it with), so
|
|
298
|
-
the windows of this package plug in as they are. A kind with none shows what the agent said the
|
|
299
|
-
window shows (the text twin), never an error.
|
|
300
|
-
- **`viewer`** says who is looking, because the client does not: `"owner"` (the default) or
|
|
301
|
-
`"end_user"`. An end user's stage never opens the owner's windows (`END_USER_WINDOW_KINDS`:
|
|
302
|
-
`chat` and `voice`), whoever asks, the host or the agent.
|
|
303
|
-
- **A pick on a generated window** goes back to the agent as the next turn, the way a pick in the
|
|
304
|
-
chat does, and the transcript says where a page went ("→ Opened in Sales") instead of drawing it
|
|
305
|
-
twice. A window the person closed stays closed until the agent puts something new in it. The
|
|
306
|
-
stage is rebuilt from the conversation's history when it opens, with no resource of its own.
|
|
307
|
-
- **Voice** is the dock's `renderVoice`: when the person speaks, the dock is the voice and the
|
|
308
|
-
conversation collapses to captions. Without `renderVoice` there is no button for it.
|
|
309
|
-
- **`useWindows`** is the state of the stage without the shell, for a host that lays windows out
|
|
310
|
-
itself: derived from facts (`place`, `restore`, `observe` a run's journal) and from what the
|
|
311
|
-
person does (`focus`, `close`, `reopen`). `WindowFrame`, `SurfaceWindow`, `Strip` and `Dock` are
|
|
312
|
-
the pieces the shell is made of; `renderStrip` and `renderDock` replace two of them.
|
|
313
|
-
- Tab goes strip, stage, dock; Esc closes the window on the stage (never the conversation); under
|
|
314
|
-
720px of its own width the strip becomes tabs. Its slots are `space`, `strip`, `window`, `dock`
|
|
315
|
-
and `captions`, and `<signalcore-space>` is it as a web component (see [The widget](#the-widget)).
|
|
316
|
-
|
|
317
|
-
## Live streams
|
|
318
|
-
|
|
319
|
-
What a run shows as it happens (the browser it drives, a preview its code renders) is a live
|
|
320
|
-
stream, and **`LiveView`** watches one: the pictures it sends, drawn on a canvas as they arrive.
|
|
122
|
+
The conversation and its composer, for a conversation your page already has (one it started with
|
|
123
|
+
`client.conversations.start(agentId)`, or one it kept):
|
|
321
124
|
|
|
322
125
|
```tsx
|
|
323
|
-
<
|
|
324
|
-
<LiveView streamId="liv_..." aspect="16:9" input={false} onStatus={(status) => {}} />
|
|
325
|
-
</SignalcoreProvider>
|
|
126
|
+
<AgentChat client={client} conversationId="cnv_…" name="Lucía" onTool={onTool} />
|
|
326
127
|
```
|
|
327
128
|
|
|
328
|
-
|
|
329
|
-
and the `Browser` component and the hand-over card draw one when they name a `stream_id`, in
|
|
330
|
-
preference to the hosted browser's live URL and to screenshots. `client` needs `live.connect`,
|
|
331
|
-
which the SDK's `Signalcore` has (`client.live`); a host that reaches the API with a cookie
|
|
332
|
-
supplies it with `new Live({ baseUrl })` from `@signalcore/sdk`.
|
|
333
|
-
- **The picture.** The canvas takes the stream's size from its first frame (`aspect` holds its shape
|
|
334
|
-
until then), shows the newest picture and never a queue, and has a role of `img` with a label
|
|
335
|
-
(`label`, "Vista en directo" by default, "Live view" in English). A quiet word says how it is
|
|
336
|
-
("en directo", "reconectando", "terminado", "no disponible"; "live", "reconnecting", "ended",
|
|
337
|
-
"unavailable" in English). It does not animate for people who prefer reduced motion.
|
|
338
|
-
- **Dropped connections** are opened again, after half a second and then twice as long up to ten
|
|
339
|
-
seconds. A stream that ended, or that the server refuses (not signed in, not allowed, no longer
|
|
340
|
-
there), is not. A stream that never answered is given up on after three tries: `onStatus` then
|
|
341
|
-
says `unavailable`, and the browser view goes back to its live URL or its screenshots.
|
|
342
|
-
- **Input.** With `input`, once the stream says it takes input, the pointer (clicks, double clicks,
|
|
343
|
-
drags), the wheel and the keys go to the stream in its own pixels, and the canvas can be reached
|
|
344
|
-
with the keyboard: keys are sent only while it has the focus, Tab leaves it, and keys held when it
|
|
345
|
-
loses the focus are let go. Whether it is the person's turn is for the stream's producer to say; a
|
|
346
|
-
browser session takes input only while the agent has handed the controls over, which is when the
|
|
347
|
-
hand-over card turns `input` on.
|
|
348
|
-
|
|
349
|
-
## Styling
|
|
350
|
-
|
|
351
|
-
Five levels, from nothing to everything. Each one is optional, and each works without the next.
|
|
352
|
-
|
|
353
|
-
**1. Inherit.** Dropped into any page it uses **your font, your text color and your color scheme**
|
|
354
|
-
(`font-family: inherit`, `color: inherit`, tints of `currentColor` for lines and muted text). Dark
|
|
355
|
-
mode follows `prefers-color-scheme`, and an ancestor with `data-theme="dark"` (or `"light"`)
|
|
356
|
-
decides when you set it. This is the whole of the work for many pages.
|
|
357
|
-
|
|
358
|
-
**2. Tokens.** Everything else is a `--sc-*` custom property with a default. Set what you want
|
|
359
|
-
different, in CSS or through the provider, which writes them as inline custom properties on its
|
|
360
|
-
own wrapper element:
|
|
361
|
-
|
|
362
|
-
| Token | What |
|
|
363
|
-
| ----------------------------------------------- | ----------------------------------------------------------- |
|
|
364
|
-
| `--sc-accent`, `--sc-accent-fg` | The owner acting: primary buttons, selection, links |
|
|
365
|
-
| `--sc-bg`, `--sc-surface`, `--sc-sunken` | What it sits on, a card's face, a recessed area |
|
|
366
|
-
| `--sc-line`, `--sc-line-strong` | Borders |
|
|
367
|
-
| `--sc-text`, `--sc-muted`, `--sc-subtle` | Ink (default: your text color) |
|
|
368
|
-
| `--sc-danger`, `--sc-ok`, `--sc-signal` | State, each with a `-soft` tint |
|
|
369
|
-
| `--sc-radius`, `--sc-density` | The corner of a card; how much room things take (1 is ours) |
|
|
370
|
-
| `--sc-font`, `--sc-font-mono`, `--sc-font-size` | Default: your font; the reading size is 14px |
|
|
129
|
+
Its props, words and parts are below ([AgentChat](#agentchat-1)).
|
|
371
130
|
|
|
372
|
-
|
|
373
|
-
<SignalcoreProvider theme={{ accent: "var(--primary)", radius: "var(--radius)", density: 0.9 }} />
|
|
374
|
-
```
|
|
131
|
+
## AgentSpace
|
|
375
132
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
**
|
|
379
|
-
(`--accent-9`, `--color-background`, `--gray-6`…) and **Material 3** (`--md-sys-color-primary`,
|
|
380
|
-
`--md-sys-color-surface`…). So in a shadcn app the surfaces already use your primary, your card and
|
|
381
|
-
your radius, in light and in dark, with no configuration. A page that uses none keeps level 1.
|
|
133
|
+
An agent in your page, whole, for your customers: the conversation and, when the agent has a
|
|
134
|
+
voice (its face, `GET /agents/{id}/face`, says `voice.enabled`), two modes beside the
|
|
135
|
+
composer's paperclip, **text** and **voice**, in one small segmented control.
|
|
382
136
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
137
|
+
- **Text** is the chat (`AgentChat`), as below.
|
|
138
|
+
- **Voice** turns the same panel, at the same size, into the call (`VoiceCall`): the orb, what
|
|
139
|
+
the call is doing (connecting, listening, thinking, speaking), and what is being said, as
|
|
140
|
+
captions under the orb (the person's last words and the agent's answer as it comes, read live
|
|
141
|
+
from the call's own conversation). The modes stay at its foot. `POST /agents/{id}/calls` makes
|
|
142
|
+
the call and a conversation of its own, the person's.
|
|
143
|
+
- **Back to text**: ending the call, or choosing text (which hangs up), comes back to writing:
|
|
144
|
+
once the agent took the call, in the call's conversation, where what was said shows (told
|
|
145
|
+
through `onConversation`, like one the space starts); after a call nobody took, where the
|
|
146
|
+
person was. A call that couldn't begin (no microphone, refused, nobody answering) stays to say
|
|
147
|
+
why, with a button to try again and the modes to go back.
|
|
390
148
|
|
|
391
|
-
The
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`src/presets.ts`; adding a system is adding a table.
|
|
149
|
+
The modes are a radio group (`role="radiogroup"`, each mode a `radio` named by its glyph and its
|
|
150
|
+
word): Tab reaches the mode that is on, the arrows (and Home, End) move to the other and choose
|
|
151
|
+
it, and after a switch the keyboard stays on the modes, drawn anew where the person now is.
|
|
395
152
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
modifier:
|
|
153
|
+
There is no header by default: the space is the conversation, and your page says who the agent
|
|
154
|
+
is. `header` draws the agent's face at the top (its mark in the tint it keeps, its name and
|
|
155
|
+
role), or draw your own from `useAgentFace(client, agentId)`.
|
|
400
156
|
|
|
401
157
|
```tsx
|
|
402
|
-
<
|
|
403
|
-
|
|
158
|
+
<AgentSpace
|
|
159
|
+
client={client}
|
|
160
|
+
agentId="agt_…"
|
|
161
|
+
conversationId={kept} // optional: the person's conversation to go on with
|
|
162
|
+
onConversation={(id) => keep(id)} // hears the one it started
|
|
163
|
+
layout="compact" // or "full", a page's main column
|
|
164
|
+
header // optional: the agent's face at the top
|
|
165
|
+
steps="hidden" // optional: by default as the client speaks (AgentChat's `steps`)
|
|
166
|
+
messages={SPACE_MESSAGES_ES} // Spanish; English by default
|
|
167
|
+
locale={es} // from "@signalcore/ui/locale"
|
|
168
|
+
onTool={onTool}
|
|
169
|
+
onClose={close} // a close button, for a space held in a panel
|
|
404
170
|
/>
|
|
405
171
|
```
|
|
406
172
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
They are classes like any other (`sc-table--compact`), and apply to our components only: a
|
|
422
|
-
primitive you replace draws itself.
|
|
173
|
+
Without `conversationId`, or once the one given is gone for the person (404, 403), it starts one
|
|
174
|
+
and tells `onConversation`; a call the agent took is told the same way, as the conversation it
|
|
175
|
+
goes on in. `onClose` draws a close in the face, or without it at the top's end, over the
|
|
176
|
+
conversation. It takes `AgentChat`'s `classNames`, `components`, `suggestions`, `onError`,
|
|
177
|
+
`steps` and `orb` (the call's), and goes one agent per mount (key it on the agent's id).
|
|
178
|
+
`messages` (`SpaceMessages`, `SPACE_MESSAGES` in English, `SPACE_MESSAGES_ES`): `modes`
|
|
179
|
+
(`{label, text, voice}`: the group's name and each mode's word), `again` (try a call that
|
|
180
|
+
couldn't begin again), `close`, `agent` (its name when the face can't be read), `opening`,
|
|
181
|
+
`failed`, `retry`, and `chat` and `voice`, `AgentChat`'s and `VoiceCall`'s own. Parts:
|
|
182
|
+
`sc-space` (`--compact`, `--full`, `--voice`), `sc-space__face`, `sc-space__mark` (`--sky`,
|
|
183
|
+
`--rose`, `--sand`), `sc-space__who`, `sc-space__name`, `sc-space__role`, `sc-space__close`,
|
|
184
|
+
`sc-space__body`, `sc-space__voice`, `sc-space__foot`, `sc-space__modes`, `sc-space__mode`
|
|
185
|
+
(`--text`, `--voice`; `aria-checked` on the one on), `sc-space__caption` (`--person`, `--agent`),
|
|
186
|
+
`sc-space__status`.
|
|
423
187
|
|
|
424
|
-
|
|
425
|
-
<SignalcoreProvider patterns={{ table: "compact", actions: "footer", card: "raised" }} />
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
| Pattern | Values | What |
|
|
429
|
-
| --------- | ---------------------------------- | ----------------------------------------------------------- |
|
|
430
|
-
| `table` | `comfortable` (default), `compact` | Rows with room, or close together for a lot of records |
|
|
431
|
-
| `actions` | `inline` (default), `footer` | A form's button under its fields, or set apart on a line |
|
|
432
|
-
| `card` | `flat` (default), `raised` | A bordered area, or one that also lifts a hair off the page |
|
|
188
|
+
## The widget
|
|
433
189
|
|
|
434
|
-
|
|
190
|
+
`<signalcore-chat>` for pages without React: one script, React and the stylesheet inside it,
|
|
191
|
+
served by SignalCore beside its API (it follows each release; `@signalcore/ui/widget.js` from
|
|
192
|
+
npm pins a version instead).
|
|
435
193
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
(the default, as the product speaks) and English, and the provider picks one and lets you change
|
|
439
|
-
single words:
|
|
194
|
+
```html
|
|
195
|
+
<script src="https://signalcore.ai/v1/widget.js"></script>
|
|
440
196
|
|
|
441
|
-
|
|
442
|
-
|
|
197
|
+
<signalcore-chat
|
|
198
|
+
base-url="https://signalcore.ai/v1"
|
|
199
|
+
agent="agt_…"
|
|
200
|
+
publishable-key="pk_…"
|
|
201
|
+
></signalcore-chat>
|
|
443
202
|
```
|
|
444
203
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
carry a name or a number are functions (`connectWith: (app) => "Connect " + app`), and the
|
|
450
|
-
groups (`verbs`, what the builder is doing; `liveStatus`; `voiceStates`; and the windows'
|
|
451
|
-
`automations` and `flow`) can change one entry at a time, at any depth
|
|
452
|
-
(`{ flow: { verbs: { calls: "runs" } } }`). Define the object
|
|
453
|
-
outside the render, so the components are not drawn again for nothing.
|
|
454
|
-
- **Another language.** The package has the two. For a third, give every word as `messages` (the
|
|
455
|
-
type makes sure none is missing) over the `locale` whose dates are closest.
|
|
456
|
-
- **The widget** takes the language as an attribute and the words as a property:
|
|
204
|
+
A web channel (SignalCore, the agent's page, Channels, Web) gives the shortest form: one line
|
|
205
|
+
that puts the widget on the page itself, with the agent and the look the business set in its
|
|
206
|
+
panel (or had the builder set), read by the key as it loads, so a change reaches every site
|
|
207
|
+
without pasting again. `data-locale` and `data-theme` say what `locale` and `theme` would.
|
|
457
208
|
|
|
458
209
|
```html
|
|
459
|
-
<
|
|
460
|
-
<script>
|
|
461
|
-
document.querySelector("signalcore-chat").messages = { send: "Post" };
|
|
462
|
-
</script>
|
|
210
|
+
<script src="https://signalcore.ai/v1/widget.js" data-key="pk_…" async></script>
|
|
463
211
|
```
|
|
464
212
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
213
|
+
An element with a publishable key and no `agent` reads its web channel the same way; `contained`
|
|
214
|
+
floats it within its own box instead of at the page's corner, and the `look` property puts a
|
|
215
|
+
look over the channel's (SignalCore's preview).
|
|
216
|
+
|
|
217
|
+
A round button at the page's corner opens the agent's space in a panel (the whole screen on a
|
|
218
|
+
phone, where the space's own close is the way out); `mode="inline"` puts the space in the page's
|
|
219
|
+
flow at the element's size (560px tall unless your CSS says). A visitor's conversation is kept in
|
|
220
|
+
`localStorage` (`signalcore.conversation.<agent>`) for their next visit. In a bundled app,
|
|
221
|
+
`import "@signalcore/ui/widget.js"` registers it the same way.
|
|
222
|
+
|
|
223
|
+
| Attribute | Says |
|
|
224
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| `base-url` | the public API, with `/v1` |
|
|
226
|
+
| `agent` | the agent's id |
|
|
227
|
+
| `publishable-key` | a visitor of your site (`pk_…`) |
|
|
228
|
+
| `token` | instead, one of your people, by the token your backend minted (set it again when it is renewed) |
|
|
229
|
+
| `locale` | `es` or `en`; else the page's `<html lang>`, else the browser's |
|
|
230
|
+
| `theme` | `light` or `dark`; else the page's `color-scheme`, and where the page names none, dark when its text is light (a page made dark by its own classes) |
|
|
231
|
+
| `mode` | `inline`; floating by default |
|
|
232
|
+
| `header` | draws the agent's face over the space (off by default) |
|
|
233
|
+
| `steps` | `shown` or `hidden`; hidden by default (your page speaks for a person) |
|
|
234
|
+
| `stylesheet` | your own CSS for the widget: the CSS itself (anything with a `{`) or its address; adopted inside the shadow root, after ours. Also the element's `stylesheet` property |
|
|
235
|
+
| `unstyled` | our look's defaults drop away (grounds, lines, shadows, corners, every ink but the page's text), the layout stays: for a host that styles everything |
|
|
236
|
+
| `shadow` | `none`: no shadow root; the widget draws in the page (our stylesheet goes first in `<head>`, so your CSS wins), for a host whose global CSS should apply directly. Read once, as the element connects |
|
|
237
|
+
|
|
238
|
+
By default the widget takes your page's type and ink: the font, the base size and the text colour
|
|
239
|
+
the element inherits come through the shadow root and stand in for our defaults (read as it draws,
|
|
240
|
+
and again whenever `<html>` or `<body>` change or the system's scheme does). Its colours are the
|
|
241
|
+
[tokens](#theming), with neutral defaults: the accent is your text's colour unless you give one.
|
|
242
|
+
Everything else is yours to style: the `--sc-*` tokens on the element or any ancestor, the
|
|
243
|
+
[parts](#parts) by `::part(…)`, your own `stylesheet`, `unstyled`, or `shadow="none"`.
|
|
472
244
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
245
|
+
```html
|
|
246
|
+
<style>
|
|
247
|
+
signalcore-chat {
|
|
248
|
+
--sc-accent: #0f766e;
|
|
249
|
+
--sc-accent-fg: #fff;
|
|
250
|
+
--sc-radius: 6px;
|
|
251
|
+
--sc-orb-color: #0f766e;
|
|
252
|
+
}
|
|
253
|
+
signalcore-chat::part(message-person) {
|
|
254
|
+
background: #0f766e;
|
|
255
|
+
color: #fff;
|
|
256
|
+
}
|
|
257
|
+
</style>
|
|
258
|
+
<signalcore-chat base-url="…" agent="agt_…" publishable-key="pk_…" stylesheet="/widget.css">
|
|
259
|
+
</signalcore-chat>
|
|
485
260
|
```
|
|
486
261
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
and with
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
262
|
+
## Your own tools, run in the page
|
|
263
|
+
|
|
264
|
+
A tool of yours declared `client` on the agent (`PUT /v1/agents/{id}/tools/{name}`) runs in the
|
|
265
|
+
person's page: the socket sends `{"type": "tool", "id", "name", "input"}`, the chat calls
|
|
266
|
+
`onTool`, and sends up `{"type": "tool_result", "id", "output"}` with what it resolved to
|
|
267
|
+
(`null` for nothing) or `{"type": "tool_result", "id", "error"}` with the message it rejected
|
|
268
|
+
with (an error is cut to the 2000 characters the API takes). Its step shows as working
|
|
269
|
+
meanwhile, like any step (`messages.steps[name]` words it), and the reply as under way until the
|
|
270
|
+
step is done or failed. Each call runs once; one told again while it waits (the socket opened
|
|
271
|
+
anew: the API tells a person's page again every call still awaited) is answered again with what
|
|
272
|
+
it gave, and the API's `tool` error for an answer no call waits on any more is let pass, never
|
|
273
|
+
shown (noted in the console in development). With no `onTool`, each is refused with
|
|
274
|
+
`messages.noTool` ("This page can't do that"; "Esta página no puede hacer eso" in
|
|
275
|
+
`CHAT_MESSAGES_ES`), so a turn never waits on a page that can't run it; only the person's page
|
|
276
|
+
answers it (the API refuses the business's socket, so our app reading the conversation fails
|
|
277
|
+
nothing).
|
|
495
278
|
|
|
496
279
|
```tsx
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function PropertyCard({ address, price, onAction }: PropertyCardProps) {
|
|
506
|
-
const rate = (fit: boolean) =>
|
|
507
|
-
onAction?.({
|
|
508
|
-
name: "rate",
|
|
509
|
-
data: { address, fit },
|
|
510
|
-
label: `${fit ? "Good fit" : "Not a fit"}: ${address}`,
|
|
511
|
-
});
|
|
512
|
-
return (
|
|
513
|
-
<article>
|
|
514
|
-
<h3>{address}</h3>
|
|
515
|
-
<p>{price} €</p>
|
|
516
|
-
<button disabled={!onAction} onClick={() => rate(true)}>
|
|
517
|
-
Good fit
|
|
518
|
-
</button>
|
|
519
|
-
<button disabled={!onAction} onClick={() => rate(false)}>
|
|
520
|
-
Not a fit
|
|
521
|
-
</button>
|
|
522
|
-
</article>
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
<SignalcoreProvider components={{ PropertyCard }}>
|
|
527
|
-
<AgentChat agentId="agt_…" />
|
|
528
|
-
</SignalcoreProvider>;
|
|
280
|
+
const onTool = async ({ name, input }: ToolCall) => {
|
|
281
|
+
if (name === "open_record") {
|
|
282
|
+
router.push(`/records/${String(input.record)}`);
|
|
283
|
+
return { opened: true };
|
|
284
|
+
}
|
|
285
|
+
throw new Error(`This page has no ${name}`);
|
|
286
|
+
};
|
|
529
287
|
```
|
|
530
288
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
289
|
+
## Make it yours
|
|
290
|
+
|
|
291
|
+
From least to most work:
|
|
292
|
+
|
|
293
|
+
1. **Tokens**: the `--sc-*` custom properties (colours, type, corners, spacing, shadows, the
|
|
294
|
+
orb's colour) on `:root` or any ancestor, the same for the React components and the widget,
|
|
295
|
+
each with a neutral default that follows light and dark ([Theming](#theming)).
|
|
296
|
+
2. **Words**: `messages` on every component (English by default; `CHAT_MESSAGES_ES`,
|
|
297
|
+
`VOICE_CALL_MESSAGES_ES`, `SPACE_MESSAGES_ES`, `KIT_MESSAGES_ES` in Spanish) and `locale` for the
|
|
298
|
+
calendars, days and numbers.
|
|
299
|
+
3. **Parts**: in React, `classNames` adds your classes to every named part, by the part's own
|
|
300
|
+
class (and your stylesheet reaches those classes as it is); in the widget, `::part(…)` from
|
|
301
|
+
your page ([Parts](#parts)), or your own `stylesheet` inside it.
|
|
302
|
+
4. **Bare**: in React, leave our stylesheet out; in the widget, `unstyled` keeps the layout and
|
|
303
|
+
drops our look, and `shadow="none"` lets your global CSS in.
|
|
304
|
+
5. **Pieces**: `components={{ table: YourTable }}` draws a block of what the agent shows with
|
|
305
|
+
your component.
|
|
306
|
+
6. **Headless**: the hooks under the components, to draw everything yourself:
|
|
307
|
+
`createClient`, `useConversation` (the conversation as the person sees it, `onTool` included),
|
|
308
|
+
`useLive` (the socket's frames, raw), `useCall` (a call without its drawing) and
|
|
309
|
+
`useAgentFace` (the agent's face). They are described below.
|
|
310
|
+
|
|
311
|
+
## The client
|
|
312
|
+
|
|
313
|
+
`createClient(options)`: where the API is and who calls it ([Who the page speaks
|
|
314
|
+
for](#who-the-page-speaks-for)), and `fetch`, a host's own when the global one is not the right
|
|
315
|
+
one. Errors are an `ApiError { status, detail, code }`. `client.who` is who the page speaks for,
|
|
316
|
+
from its credentials: `"business"` (our app's cookie) or `"person"` (a token, a publishable key's
|
|
317
|
+
visitor); the chat draws a yes the business decides by it, and shows a person none of the
|
|
318
|
+
agent's steps (`steps`).
|
|
319
|
+
|
|
320
|
+
| Method | Does |
|
|
321
|
+
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
322
|
+
| `agents.face(id)` | what a person may know of the agent (`GET /agents/{id}/face`): `AgentFace {id, name, role, icon, voice: {enabled}}` |
|
|
323
|
+
| `agents.about(id)` | what the agent says about itself (`about`, null until written; `about_at`, null once stale) and its `facts` |
|
|
324
|
+
| `agents.describe(id)` | has the agent write about itself anew (a model call, a few seconds); answers like `about` |
|
|
325
|
+
| `agents.files(id)` | the agent's own files (`StoredFile`: `id, path, media_type, size, searchable, created_at`), by path |
|
|
326
|
+
| `agents.putFile(id, path, blob, searchable)` | stores the blob's bytes under the path (replacing a file of that name), its type as the file's; `searchable` makes it knowledge `search_files` reads (text, Markdown, JSON, YAML, PDF). 50 MB at most, 5 GB per organization |
|
|
327
|
+
| `agents.deleteFile(id, path)` | removes one of the agent's own files |
|
|
328
|
+
| `agents.memories(id)` | what the agent remembered (`MemoryFact`: `id, text, created_at`), newest first |
|
|
329
|
+
| `agents.forget(id, memoryId)` | takes one memory from it |
|
|
330
|
+
| `agents.call(id)` | the owner's call with the agent from our app (`POST /agents/{id}/voice/token`, `/api` only): `CallToken {url, token, room, call_id, conversation_id}`, LiveKit's address and a token for the call's room, and the call's conversation (a test), made now |
|
|
331
|
+
| `files.list()` · `files.put(path, blob, searchable)` · `files.delete(path)` | the organization's files, shared with every agent: the same three as the agent's own |
|
|
332
|
+
| `conversations.get(id)` | one conversation with its messages, who answers it (`handled_by`: `agent`, `person` or `nobody`) and every change of it (`handovers`); on a channel, `channel`, `contact` (`muted`) and each message's `author`, `delivery` and `decision` |
|
|
333
|
+
| `conversations.start(agentId)` | a new conversation with an agent |
|
|
334
|
+
| `conversations.call(agentId)` | a call with the agent for whoever may talk to it, a person's as theirs (`POST /agents/{id}/calls`): a `CallToken`, what `VoiceCall`'s `connect` answers, with the call's own conversation, made now, where what is said lands |
|
|
335
|
+
| `channels.list(agentId?)` | the organization's channels, or one agent's (`Channel`: `id, agent_id, kind, provider, name, external_id, status, detail, settings, created_at`), the disconnected ones too |
|
|
336
|
+
| `liveUrl(conversationId, { fresh? })` | the socket's address, signed in (a promise): `ws:` beside `http:` and `wss:` beside `https:`, with `?token=` (and `&visitor=`) but in our app; `fresh` asks `getToken` for a new token first |
|
|
337
|
+
|
|
338
|
+
## The frames
|
|
339
|
+
|
|
340
|
+
What the socket says, read into `Frame` by `frameOf` (a kind it does not know is let pass):
|
|
341
|
+
|
|
342
|
+
| Down | Means |
|
|
343
|
+
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
344
|
+
| `message {message}` | a message stored: `{id, role, text, action, steps}`; a reply's `steps` carry what it called, what each drew, and `request` while one waits for the person's leave |
|
|
345
|
+
| `text {delta}` | a piece of the reply as it streams |
|
|
346
|
+
| `ui {id, surface, awaits?}` | a surface the agent showed, under the step's id; `awaits`: the turn stops until the person acts on it |
|
|
347
|
+
| `step {id, action?, state, output?}` | an action running, then its state as it moves: `running`, `waiting`, `done`, `failed` |
|
|
348
|
+
| `request {id, action, input, decides}` | an action waits for a yes before it runs: the person's (`decides: "person"`, the turn stopped) or the business's (`"business"`, the turn went on; its people answer from the app) |
|
|
349
|
+
| `tool {id, name, input}` | the agent called one of the host's own tools, to run in the page (`onTool`); its step works meanwhile, and the turn waits for its `tool_result`. Told again on a socket that opens while it waits |
|
|
350
|
+
| `handoff {reason}` | the agent handed the conversation to a person (`escalate`), and why; it stays quiet in it until released |
|
|
351
|
+
| `holder {handled_by, handover}` | who answers the conversation now (`agent`, `person`, `nobody`), after any change of it, and the change: `{holder, reason, by, title, at}` |
|
|
352
|
+
| `decision {message_id, decision}` | what was decided of a person's message, when it is stored or settles (`{kind, reason, automation, title, run_id}`), or `null` once cleared |
|
|
353
|
+
| `done` | the turn is over: the reply is complete, or it stopped to wait |
|
|
354
|
+
| `stopped` | the person stopped the reply |
|
|
355
|
+
| `error {code, detail}` | `code` for the host to word (`pending`: answer the surface first; `business`: that yes is the business's), `detail` for a log |
|
|
356
|
+
| `browser {open, url, title, control, reason, wait, width, height}` | the conversation's browser, whole, whenever it changes (`open: false` once it closed); `control` is who holds it, `reason` and `wait` the agent's hand-over |
|
|
357
|
+
| `browser.frame {jpeg, width, height}` | its screen as it paints (base64 JPEG); never stored, kept out of the state (`watchScreen`) |
|
|
358
|
+
|
|
359
|
+
Up: `say {text}`, `action {surface, name, data, label}` (what the person did on a surface, `label`
|
|
360
|
+
the words shown for it), `stop`, `browser.control {to}` (take the browser, or give it back),
|
|
361
|
+
`browser.input {event}` (the person's pointer, wheel, keys and pasted text, in the page's pixels,
|
|
362
|
+
while they hold it) and `tool_result {id, output}` or `tool_result {id, error}` (what a host's
|
|
363
|
+
tool gave back, `null` for nothing, or why it failed: `toolResultOf(id, settled)` makes either
|
|
364
|
+
from how the call's promise settled).
|
|
365
|
+
|
|
366
|
+
## The hooks
|
|
367
|
+
|
|
368
|
+
- `useLive(client, conversationId, { onFrame, onOpen? })` → `{ open, send(frame) }`. The socket
|
|
369
|
+
itself: frames down to `onFrame`, `send` up, reconnecting on its own (1s, 2s, 4s, 8s, then
|
|
370
|
+
10s); `onOpen(again)` says when it is not the first time. A socket that couldn't open may have
|
|
371
|
+
carried a token that expired: the next try asks for a fresh one (`liveUrl`'s `fresh`), once
|
|
372
|
+
until one opens. The raw layer a host draws its own things over.
|
|
373
|
+
- `useAgentFace(client, agentId)` → `{ face, loading, error }`: what a person may know of the
|
|
374
|
+
agent (`AgentFace`: its name, role, `icon` for `agentGlyph`, `voice.enabled`), read once per
|
|
375
|
+
agent; none while another agent's is read.
|
|
376
|
+
- `useConversation(client, conversationId, { onError?, onHolder?, handover?, onTool? })` → the
|
|
377
|
+
conversation as the person sees it. `onTool(call)` runs the host's tools the agent calls in
|
|
378
|
+
the page and its answer goes up (each call once; one told again is answered again with what it
|
|
379
|
+
gave; the turn stays under way until its step settles); without it each is refused with
|
|
380
|
+
`NO_TOOL`. It answers `{ messages, draft, pending,
|
|
381
|
+
awaiting, open, loading, error, say(text), act(action), stop(), browser,
|
|
382
|
+
watchScreen(listener), control(to), input(event), holder, handovers }`. The history, read each
|
|
383
|
+
time the socket opens and kept live by its frames;
|
|
384
|
+
`pending` from the person's message to `done`, `stopped` or a turn error, `draft` the reply so
|
|
385
|
+
far. What `step`, `ui` and `request` frames say lands on the reply's steps, so a message is
|
|
386
|
+
the one place a step is drawn from, live or read back. `awaiting` is the step the turn waits
|
|
387
|
+
on: a surface to answer, or an action waiting for the person's leave (the step's `request`,
|
|
388
|
+
`decides: "person"`; `act` on its id with `approve` or `decline`); `say` is refused until they
|
|
389
|
+
act. A yes the business gives (`decides: "business"`) waits on its step and holds nothing; the
|
|
390
|
+
person's answer on one, refused `business`, puts it back as the business's. `onError(code)`
|
|
391
|
+
hears the socket's codes and `load` (the
|
|
392
|
+
history could not be read): the host words them, the hook shows no text; `tool` (an answer of
|
|
393
|
+
the page's no call waits on any more) is let pass. `holder` is who
|
|
394
|
+
answers it (null until the history lands) and `handovers` every change of it, read with the
|
|
395
|
+
history and kept live by `holder` frames (each kept once; one heard while a read was on its way
|
|
396
|
+
outlives it); `onHolder(holder, handover)` hears each such frame, for the host's own views of
|
|
397
|
+
it. `handover` is the last change the host knows of (one it made itself, as its request
|
|
398
|
+
answered): taken at once, kept once with the frame that tells it too (by its moment and holder),
|
|
399
|
+
so it shows with the socket down. A `decision` frame lands on its message (`null` clears it). One conversation per
|
|
400
|
+
mount: key the component on the id. On a channel (v3/CHANNELS.md) the reply runs on its own
|
|
401
|
+
time: what the person writes there (`author: contact`) and what a person of the business
|
|
402
|
+
writes (`operator`) are kept without touching the turn, which opens on the reply's first words
|
|
403
|
+
or step and closes on `done`; and while something of the business's is on its way (`queued`,
|
|
404
|
+
or `sent` less than a minute ago) the history is read again every 3 s, since where it got to
|
|
405
|
+
comes in no frame.
|
|
406
|
+
|
|
407
|
+
## AgentChat
|
|
408
|
+
|
|
409
|
+
`<AgentChat client conversationId name messages? classNames? components? onError? actions? suggestions? compose? look? steps? write? handovers? onHolder? handover? onTool? />`:
|
|
410
|
+
the conversation, the reply streaming, and the composer (Enter sends, Shift+Enter breaks the
|
|
411
|
+
line; while a reply is on its way the send button is a stop button, Esc stops too, and one quiet
|
|
412
|
+
line over the composer says who is thinking, for how many seconds, and how to stop). The reply
|
|
413
|
+
streams at a reading pace (`reveal.ts`: a few characters a frame, faster the further behind, never
|
|
414
|
+
more than about a second behind the model), and goes on from there once it is stored. A reply
|
|
415
|
+
draws its words as Markdown, then the steps it took as a compact list on a thin line (the
|
|
416
|
+
action's glyph once done, a turning ring while it runs, a ring while it waits, a warning when it
|
|
417
|
+
failed; the action's name; its output), then what each step drew, through `Surface`; a surface takes input while the
|
|
418
|
+
turn waits on it, or while nothing runs and the agent showed it since the person last spoke. A
|
|
419
|
+
step waiting for a yes whoever the page speaks for gives (its `request`, live or read back)
|
|
420
|
+
draws a card headed by `approval`, with the sentence, the action's input and two buttons; on a
|
|
421
|
+
person's page (`client.who`), one the business decides (`decides: "business"`) is a quiet line
|
|
422
|
+
instead, `confirming` ("Waiting for the business to confirm"), never buttons. On a call, a person's message
|
|
423
|
+
that says what they heard of the agent's last words (`heard`, when it wasn't what was stored)
|
|
424
|
+
draws it: with no reply before it, the greeting the agent picked up with (never stored) as the
|
|
425
|
+
agent's words, where it was said; after a reply they cut short, a quiet line under it quoting the
|
|
426
|
+
last of what they heard (`cut(heard)`, `""` when nothing of it was). Where the agent handed the conversation to a person (an
|
|
427
|
+
`escalate` step done, live or read back), one quiet line across the log says so, after the last
|
|
428
|
+
reply of that turn. With `handovers` (a host of the business's) the log draws every change of
|
|
429
|
+
who answers it instead, read and live, each a quiet line where it fell (the agent's own after the
|
|
430
|
+
last reply of its turn), with why and when (`handover(holder, by, name)`, `by` a `HandoverBy`:
|
|
431
|
+
`owner`, `agent`, `platform` or an `automation` by its title; the reason follows it); and under a
|
|
432
|
+
person's message the agent let pass this time (its `decision` a `skip`), a quiet line says so,
|
|
433
|
+
and why (`skipped`). `onHolder(holder, handover)` hears each change live, for the host's own
|
|
434
|
+
views of who answers; `handover` (the host's last change, as its request answered) is drawn at
|
|
435
|
+
once, whatever the socket says. The host picks the conversation;
|
|
436
|
+
the chat has no list and starts none. `name` is the agent's, for the words that address it;
|
|
437
|
+
`messages` are those words, English by default (`placeholder(name, fresh)`: `fresh` while nothing
|
|
438
|
+
is said, `connecting`, `awaiting`, `thinking(name)`, `typing` (the line while it works, where steps
|
|
439
|
+
are hidden), `working` and `writing` (the builder's line),
|
|
440
|
+
`elapsed(seconds)`, `escape`, `cut(heard)`, `empty(name)`: the title of a conversation
|
|
441
|
+
with nothing said, `intro(name)`: a line under it (none when empty), `send`, `stop`, `log`,
|
|
442
|
+
`steps` by action name, `failed` (beside a failed step, in the builder's look), `approval` (the
|
|
443
|
+
head of the approval card), `request(name, action)`, `allow`, `decline`, `confirming` (a person's line for a yes the
|
|
444
|
+
business gives), `handedOff`: the handoff's line, `kit`: the words
|
|
445
|
+
of the surfaces' controls); `locale` is the surfaces' language; `components` is how a block type
|
|
446
|
+
draws (below);
|
|
447
|
+
`actions` is a slot at the composer's left for the host's own controls; `suggestions` are a few
|
|
448
|
+
things the person might say first, offered as quoted lines while nothing has been said (a click says
|
|
449
|
+
one). `compose` (`ChatCompose { text, send, key }`) hands the chat a request from elsewhere on the
|
|
450
|
+
page, such as a button beside it, taken once each time `key` changes (not the one the chat mounts
|
|
451
|
+
with, so a chat mounted again never says it twice; another conversation does not take it either):
|
|
452
|
+
with `send` it is said, once the socket is open (while a reply is on its way or the turn waits, it
|
|
453
|
+
goes into the composer instead); without, it goes into the composer, focused with the caret at its
|
|
454
|
+
end, for the person to finish.
|
|
455
|
+
|
|
456
|
+
On a channel the person's messages are the contact's, at the right as anywhere; the business's
|
|
457
|
+
side stays at the left: the agent's words with a quiet mark under them for where they got to
|
|
458
|
+
(`delivery`: a clock while queued, a check once sent, two once delivered, two in the accent once
|
|
459
|
+
read, the danger tone with the provider's reason once failed), and what a person of the business
|
|
460
|
+
wrote apart from them, in a bubble of a breath of the accent with who wrote it (`business`)
|
|
461
|
+
under it. The surfaces the agent showed went to the person there, so they only read here (an
|
|
462
|
+
approval still takes the owner's answer), and the reply can't be stopped from here. `write(text,
|
|
463
|
+
files)` takes what is written instead of the socket, for a person of the business answering on
|
|
464
|
+
the channel (`POST /conversations/{id}/operator`): the composer says `write`, takes words and
|
|
465
|
+
files whatever the turn is doing, and empties once the promise resolves; a rejection leaves them
|
|
466
|
+
for the host to say why. The words for it: `write`, `business`, `delivery` by state,
|
|
467
|
+
`undelivered(error)`.
|
|
468
|
+
|
|
469
|
+
`steps` (`ChatSteps`) is whether the agent's steps show. By default it follows who the page
|
|
470
|
+
speaks for (`client.who`): our app's cookie (`business`) sees them, as the work it is; a person
|
|
471
|
+
or a visitor (`person`) sees none of them (no file searches, memories, tool calls or their raw
|
|
472
|
+
outputs, which may be in English), only what the agent says and shows (its words, its surfaces,
|
|
473
|
+
files, an approval of theirs, the business's line `confirming`) and, while the turn runs, one
|
|
474
|
+
quiet line, `typing` ("Typing…", "Escribiendo…"), without the seconds or the key that stops it;
|
|
475
|
+
nor how long the reply took. `steps="shown"` or `"hidden"` says it either way.
|
|
476
|
+
|
|
477
|
+
`onTool(call)` runs the host's own tools the agent calls in the page ([Your own tools, run in
|
|
478
|
+
the page](#your-own-tools-run-in-the-page)); without it each is refused with `noTool`, a word the
|
|
479
|
+
agent reads and the person never sees.
|
|
480
|
+
|
|
481
|
+
`look` is `agent` by default. `builder` is v2's builder chat (`sc-chat--builder`): on the page's
|
|
482
|
+
ground (`--sc-bg`), the line over the composer says what it is doing (the step running by its
|
|
483
|
+
name, `writing` while its words come, `working` otherwise) under the brand's light sweeping
|
|
484
|
+
violet into orange, each block settles in as it comes, a step that returned something opens on it
|
|
485
|
+
(`sc-chat__output`) with `failed` beside one that failed, the send is the brand's gradient and
|
|
486
|
+
the stop the ink, the composer takes the focus, and Esc stops the turn from anywhere on the page.
|
|
487
|
+
`console` adds `sc-chat--console`: the same as a console beside the work, the person's words as
|
|
488
|
+
`❯` prompt lines and the composer as a prompt, in the mono face.
|
|
489
|
+
|
|
490
|
+
## BrowserView
|
|
491
|
+
|
|
492
|
+
`<BrowserView live name messages? classNames? />`: the browser the agent drives, live, while the
|
|
493
|
+
conversation has one (`live` is `useConversation`'s; `AgentChat` docks it over its composer). Small
|
|
494
|
+
while the agent works (its screen, who holds it, the address read only, the page's title), with
|
|
495
|
+
`take` and an enlarge toggle; taken by the person, or handed over by the agent, it opens large and
|
|
496
|
+
the screen takes their pointer, wheel, keys and paste. A hand-over shows its reason and `done`,
|
|
497
|
+
which answers the turn's wait and gives the browser back; `giveBack` returns one they took. A
|
|
498
|
+
hand-over whose browser closed meanwhile still shows `closed` and `done`, so the person can go on.
|
|
499
|
+
Pictures come through `watchScreen` and are painted on a canvas, the latest only, at the page's
|
|
500
|
+
size (the owner's own Chrome keeps the size of their window: the browser's `where` is `mine`, and
|
|
501
|
+
who holds it says so). `messages` (`BROWSER_MESSAGES`, English): `agent(name)`,
|
|
502
|
+
`agentMine(name)`, `person`, `take`, `giveBack`, `handedOver(name)`, `done`, `closed`, `expand`,
|
|
503
|
+
`shrink`, `blank`, `screen(where)`.
|
|
504
|
+
|
|
505
|
+
## VoiceCall
|
|
506
|
+
|
|
507
|
+
`<VoiceCall connect name tint? orb? orbSize? endedBy? messages? classNames? onAnswer? onEnd? after? captions? />`: a call with
|
|
508
|
+
an agent, the one a customer has on the phone, through LiveKit (`livekit-client`). The call starts
|
|
509
|
+
when it mounts and ends when it unmounts (key it on the attempt for another). It opens the
|
|
510
|
+
microphone first (no call is made for someone who can't be heard), then asks `connect()` where to
|
|
511
|
+
join (`{url, token}`: our API's `client.conversations.call(agentId)` answers both, with the
|
|
512
|
+
call's conversation; `client.agents.call(id)` for the owner's test), joins the room and publishes the microphone; the agent's voice plays as it comes,
|
|
513
|
+
and the call is on once the agent joins (its time counts from then).
|
|
514
|
+
|
|
515
|
+
What it draws, centred: the `Orb`, large (`orbSize`, 280 px; `orb`: `"wave"`, the default, or
|
|
516
|
+
`"fluid-dots"`), named by what the call is doing (`orbOf(look)`): it rests while the line is
|
|
517
|
+
found, moves with whoever speaks (the agent's voice while it speaks, the person's microphone
|
|
518
|
+
while it listens, each read off `meter` on every frame it draws, so the call renders for neither),
|
|
519
|
+
calms while the agent thinks, and fades and comes to rest once over. The shader paints its own
|
|
520
|
+
colours, or the host's (`--sc-orb-color`); the agent's tint (`tint`: `agentTint(id)`) is the
|
|
521
|
+
chrome's, worn by the still orb where there is no WebGL. Under it what the call is doing in a word (`connecting`;
|
|
522
|
+
`listening`, `thinking`, `speaking`: what the agent says it is doing, LiveKit's `lk.agent.state`,
|
|
523
|
+
or its voice for an agent that says nothing; `reconnecting`; `ended`), then who is being called,
|
|
524
|
+
the time, or how it ended and how long it lasted; under that, while the call is on, `captions`:
|
|
525
|
+
what is being said, as the host draws it (`AgentSpace` draws the call's conversation there); and
|
|
526
|
+
the two controls a phone has, mute (filled with the ink while the microphone is off) and hang up
|
|
527
|
+
(the one red thing), each with its word. Once over, `after` stands in their place: the host's
|
|
528
|
+
offers (call again, go back to writing).
|
|
529
|
+
|
|
530
|
+
It ends when the person hangs up; when the room closes under them (the agent's goodbye, or its
|
|
531
|
+
longest time, which the room doesn't tell apart: `endedBy`, the API's word as `endedByReason`
|
|
532
|
+
reads it, does); when the line drops (the network, or the agent leaving without closing the
|
|
533
|
+
room); or, before it begins, when the microphone can't be used (refused, missing, held by
|
|
534
|
+
another application, a browser that can't call), when `connect` fails, and when no agent joins
|
|
535
|
+
within 20 s. Each says why in words, and what to do. `onAnswer()` hears once that the agent took
|
|
536
|
+
the call; `onEnd(answered, problem)` hears once that it is over, whichever way (`answered`: the
|
|
537
|
+
agent took it; `problem`: why it never began, a `CallProblem`, null for an end). Where the
|
|
538
|
+
browser holds the agent's voice until a press, a button lets it play.
|
|
539
|
+
|
|
540
|
+
Space mutes and unmutes from anywhere that isn't a field or a control of the page's own. Escape
|
|
541
|
+
never hangs up: it is the key that closes things, pressed by reflex, and a call ended by accident
|
|
542
|
+
can't be taken back, so ending one is the red button's alone; a host that holds the call in a
|
|
543
|
+
panel keeps the panel open on Escape while the call is on (the root carries `sc-call--on`). Under
|
|
544
|
+
reduced motion the orb draws one still frame per state, and the words say the rest.
|
|
545
|
+
|
|
546
|
+
`messages` (`VOICE_CALL_MESSAGES`, English): `call(name)` (its name for screen readers), `states`
|
|
547
|
+
by look, `calling(name)`, `ended` by who ended it (`person`, `agent(name)`, `limit`, `dropped`),
|
|
548
|
+
`problems` by why it never began (`denied`, `nomic`, `inuse`, `unsupported`, `failed`,
|
|
549
|
+
`unanswered`), each `{title(name), help}`, `mute`, `hangUp`, `keys`, `muted`, `hear(name)`.
|
|
550
|
+
Parts: `sc-call` (`--connecting`, `--listening`, `--thinking`, `--speaking`, `--reconnecting`,
|
|
551
|
+
`--ended`, `--problem`; `--sky`, `--rose`, `--sand`; `--on` while ringing or talking; `--muted`),
|
|
552
|
+
`sc-call__stage`, `sc-call__orb` (on the orb; its own parts are the kit's `sc-orb…`, which
|
|
553
|
+
`classNames` takes too), `sc-call__state`, `sc-call__line`, `sc-call__captions`,
|
|
554
|
+
`sc-call__sound`, `sc-call__controls`, `sc-call__control`, `sc-call__mute`, `sc-call__hangup`,
|
|
555
|
+
`sc-call__hint`, `sc-call__after`.
|
|
556
|
+
|
|
557
|
+
`useCall(connect, { sink })` is the call without its drawing, for a host that draws its own:
|
|
558
|
+
`{ state, meter, mute(muted?), hangUp(), hear() }`, the agent's voice playing from `sink` (an
|
|
559
|
+
element of the host's). `state` is a `CallState` (`stage`, `answeredAt`, `agent`, `muted`,
|
|
560
|
+
`end: {by, seconds}`, `problem`…), `lookOf(state)` its word, `orbOf(look)` the orb's state and
|
|
561
|
+
whose loudness it follows (`"agent"`, `"person"` or none), `durationOf(seconds)` the time as a
|
|
562
|
+
phone shows it ("0:07", "12:34", "1:02:03"), and `meter.agent()` and `meter.person()` each side's
|
|
563
|
+
loudness now, from 0 to 1, to read every frame (an `Orb`'s `level` takes either as it is).
|
|
564
|
+
|
|
565
|
+
## Orb
|
|
566
|
+
|
|
567
|
+
`<Orb state level size? style? label className? />`: the shape a voice conversation with an
|
|
568
|
+
agent takes, v2's: `state` is `idle` (at rest), `listening`, `thinking` or `speaking`; `level`
|
|
569
|
+
how loud the microphone (listening) or the voice (speaking) is, 0 to 1, a number or a reader
|
|
570
|
+
called on each frame drawn (a meter: the orb follows it with no render); `size` the diameter
|
|
571
|
+
(200 px); `label` its name for screen readers. Two styles (`ORB_STYLES`, `OrbStyle`): `wave`,
|
|
572
|
+
the default, the iOS voice waveform, and `fluid-dots`, six dots that merge and scatter. Both are
|
|
573
|
+
the fragment shaders of siri-wave (MIT), kept verbatim but for the signal they are given (the
|
|
574
|
+
level; the wave also the state's energy: 0 at rest, 0.5 thinking, 1 listening and speaking) and
|
|
575
|
+
the host's colour, on one WebGL canvas. The shaders paint their own spectrum unless
|
|
576
|
+
`--sc-orb-color` names a colour: then their light is painted in it, as bright as it was, so the
|
|
577
|
+
orb is the host's (read off the canvas with each state, so a theme changed shows by the next).
|
|
578
|
+
|
|
579
|
+
Each style is a module of its own, loaded when an orb of it is first drawn: a host that never
|
|
580
|
+
calls loads none. The orb draws only while something moves: it eases the level and the energy
|
|
581
|
+
toward where they go, its clock paced by the energy (slower at rest), asks for no frame while the
|
|
582
|
+
tab is hidden, and stops three seconds after it comes to rest. Under `prefers-reduced-motion`
|
|
583
|
+
there is no loop: one frame per state, at a fixed moment. The shaders paint light on a
|
|
584
|
+
transparent canvas (what is near black is cut out) blended by exclusion with the surface under
|
|
585
|
+
it: as painted on a dark surface, turned over on a light one, ink on paper (so it needs a
|
|
586
|
+
surface painted under it, and fading it is fading the canvas: `opacity` on an ancestor
|
|
587
|
+
isolates it). Where WebGL is
|
|
588
|
+
missing, the shader doesn't compile or the context is lost, a still disc in a ring stands in, its
|
|
589
|
+
outline saying the state (thin at rest, solid listening, dashed thinking, double speaking) in
|
|
590
|
+
`--sc-orb-color`, else the signal (the agent's tint in a call). Parts (the kit's): `sc-orb` (`--idle`,
|
|
591
|
+
`--listening`, `--thinking`, `--speaking`; `--still` under reduced motion and for the disc;
|
|
592
|
+
`--loading` while its style loads), `sc-orb__ring`, `sc-orb__core`.
|
|
593
|
+
|
|
594
|
+
## The kit
|
|
595
|
+
|
|
596
|
+
The primitives everything the package draws is made of, each one BEM family in `styles.css`
|
|
597
|
+
(tokens only), so a host styles each once and every place it appears follows:
|
|
598
|
+
|
|
599
|
+
- `Card` (`sc-panel`; `sc-card` is the Ficha's), `List` and `Row` (`sc-list`, `sc-row`: a mark, a
|
|
600
|
+
title clamped to two lines, a quieter line, a detail at the right, a `trailing` slot for the
|
|
601
|
+
host's controls such as a `Switch` and a ⋯ `Button`; rows grouped in one hairline box, the
|
|
602
|
+
row pressable with `onPress`, `pressed` for the one picked, `quiet` for one paused), `Mark`
|
|
603
|
+
(`sc-mark`: what kind of thing a row is, bare, with nothing drawn behind it: a glyph in the
|
|
604
|
+
muted ink, a logo as it comes, an initial, or a photo, round for a person; it keeps its square
|
|
605
|
+
so rows line up), `MarkStack` (`sc-stack`: a few marks side by side and "+N"), `FileMark` (a
|
|
606
|
+
file's mark by its family, `familyOf(name, type)`: PDF, Word, Excel, PowerPoint, picture, video,
|
|
607
|
+
sound, text, archive; the common formats in their own tint), `Tag` (`sc-tag`: a short word on
|
|
608
|
+
how something stands, in its tone, no pill; a property every row has is said by the heading
|
|
609
|
+
the rows sit under, not by a word on each), `Stat`, `Note`, `Button`
|
|
610
|
+
(`primary`, `neutral`, `danger`, `ghost`; `sm`; a glyph alone; a link with `href`), `Switch`.
|
|
611
|
+
- The form: `Field` (label, a quiet word when optional, help, error, all wired to the control by
|
|
612
|
+
id) and its controls, each controlled (`value`, `onChange`) and named by `labelledBy` or
|
|
613
|
+
`label`: `TextInput` (text, email, phone, url, with their icon and keyboard), `TextArea`
|
|
614
|
+
(grows), `NumberInput` (− and +, its unit), `SecretInput` (show and hide), `Checkbox`,
|
|
615
|
+
`DateField` (a calendar in a popover, react-day-picker; only the `dates` offered, between
|
|
616
|
+
`earliest` and `latest`), `TimeField` (the `times` as chips, else every half hour to search,
|
|
617
|
+
or one typed; `endOfDay`, an end of a stretch, offers and takes the day's end, "24:00", too), `DateTimeField`, `Choices` (few options as cards, or one bar when short),
|
|
618
|
+
`Chips` (several of few), `Combobox` (the ARIA combobox, one or several, searchable),
|
|
619
|
+
`RangeField` (a slider with its value over the thumb), `RatingField` (stars).
|
|
620
|
+
|
|
621
|
+
`<KitProvider classNames? messages? locale?>` gives the primitives under it the host's classes
|
|
622
|
+
(`KitPart`), words (`KIT_MESSAGES`, English; `automation` holds an automation's and its runs',
|
|
623
|
+
`AUTOMATION_MESSAGES`) and language: `locale` is one of `@signalcore/ui/locale`
|
|
624
|
+
(`import { es } from "@signalcore/ui/locale"`), which the calendar reads and whose `code`
|
|
625
|
+
formats days, times, numbers and lists. Popovers render in a portal at the page's end carrying
|
|
626
|
+
`sc`, so the tokens set on `:root` reach them; inside a host's dialog (`role="dialog"`), in the
|
|
627
|
+
dialog, which a modal one needs to let the pointer and the focus reach them; inside an element
|
|
628
|
+
marked `data-sc-portal`, in it (a shadow root's, where the page's end is outside its styles: the
|
|
629
|
+
widget's).
|
|
630
|
+
|
|
631
|
+
## Automation and RunView
|
|
632
|
+
|
|
633
|
+
An agent's automation and one of its runs, as its owner reads them (v3/AUTOMATIONS.md), built
|
|
634
|
+
from the kit and drawing only what they are given: the API's objects (`AutomationData`,
|
|
635
|
+
`AutomationView`, `Run`, `RunRecord`, `RunStep`, `Trigger`, `AutomationStep`, `Use`, `Board`),
|
|
636
|
+
the kit's words (`messages.automation`) and its locale's `code`. A host fetches, switches and
|
|
637
|
+
routes; an agent shows one in a conversation as the `automation` block.
|
|
638
|
+
|
|
639
|
+
`<Automation automation runs? onSwitch? switching? onOpenRun? openRun? apps? now? />`: one card.
|
|
640
|
+
The head: what starts it as a 40px mark (a clock, a webhook, the app whose event it waits for),
|
|
641
|
+
the title, when it runs in words with its next run ("Cada día laborable a las 9:00, próxima
|
|
642
|
+
mañana"), and its state in a word (`active` in the success tone, `paused` quiet, `failing` in
|
|
643
|
+
danger) with the host's switch when it gives `onSwitch` (held still while `switching`). Then the
|
|
644
|
+
owner's sentence (`summary`), set apart; its steps, numbered, each with what it uses (an app's
|
|
645
|
+
logo and name, "IA", "Agente", "Mensaje" for a message to a contact on a channel…); the table it
|
|
646
|
+
moves (`board`), a `Stat` per value, four at most and "+N" after; and, given `runs`, its runs edge
|
|
647
|
+
to edge, each with what started it as its mark (named by it), when, its line (a test's as what it would have done), why it
|
|
648
|
+
failed in danger, a check once it went well; `onOpenRun` makes them pressable, `openRun` lights
|
|
649
|
+
the one open. Nothing in it runs an automation.
|
|
650
|
+
|
|
651
|
+
`<RunView run automation? onOpenConversation? apps? now? />`: one run. Its head (what started
|
|
652
|
+
it as its mark, when; at its end how it stands and, under it, how long it took); its line; why it failed, in a `Note`; then what it
|
|
653
|
+
did, step by step in the code's order on a rail, each call in words ("Buscó en Gmail",
|
|
654
|
+
"Escribió un texto con IA", "Añadió una fila a Citas", "Abrió una conversación", "Avisó en la
|
|
655
|
+
bandeja", "Envió un WhatsApp a +34…" by the channel the record says, its brand as the mark when
|
|
656
|
+
the host draws brands; a test's writes as "Habría…"), the same call in a row once with "×N", a
|
|
657
|
+
failed one in danger with its error; and, with `onOpenConversation`, the conversation it was
|
|
658
|
+
asked in and those it opened. `automation` names its apps and its table and numbers its steps.
|
|
659
|
+
|
|
660
|
+
`apps` (`AppDirectory`, as `AgentAbout` takes it) draws apps with the host's brands; without it
|
|
661
|
+
an app shows the logo the API gives, or its initial. The pure helpers: `whenOf(trigger, words,
|
|
662
|
+
code)` and `scheduleOf(cron, words, code)` (a trigger, a cron line, as a sentence; `timed` when
|
|
663
|
+
it names the one time of day it runs), `lineOf(automation, now, words, code)` (the head's line),
|
|
664
|
+
`momentOf`, `dayRefOf`, `timeOf`, `clockOf`, `tookOf`; `verbOf(tool)` (what a tool does, from its
|
|
665
|
+
name), `callWords(step, words.calls, names)`, `groupsOf(steps, words.calls, names)` (a record's
|
|
666
|
+
lines by step) and `namingOf(automation)`; and the cron reader under them, `parseCron`, `fires`,
|
|
667
|
+
`occurrences` and `offsetOf`.
|
|
668
|
+
|
|
669
|
+
## Surface
|
|
670
|
+
|
|
671
|
+
`<Surface surface onAction active? answered? components? classNames? messages? locale? />` draws
|
|
672
|
+
what the agent showed: a tree of blocks, one to one with the API's models in `blocks.ts`, each
|
|
673
|
+
mapped onto the kit (a `card` is a `Card`, a `list` a `List` of `Row`s, a `stat` a `Stat`, a
|
|
674
|
+
`button` a `Button`, a form's fields the kit's controls by `kind`: a select with up to 5
|
|
675
|
+
options as cards, more as a combobox; a multiselect with up to 8 as chips, more as a combobox).
|
|
676
|
+
`text` is Markdown: paragraphs, bold, italic, code, links, bullet lists. A block or a field kind
|
|
677
|
+
it does not know leaves the surface to its `text` twin. `onAction` receives `{name, data,
|
|
678
|
+
label}`: a button sends its action with `{}`, a choice `{option: id}`, a list `{item: id}`, a
|
|
679
|
+
form its values by field name (words, a number, true or false, "YYYY-MM-DD", "HH:MM",
|
|
680
|
+
"YYYY-MM-DDTHH:MM", option ids; what is left empty as null) under, when it asks one thing, that
|
|
681
|
+
answer in words (the day, the slot, the options picked), else its submit's label. A form checks
|
|
682
|
+
itself on submit (required, bounds, an email's, a phone's, an address's shape, the days and
|
|
683
|
+
times offered), shows what is wrong under each field in the kit's words and focuses the first.
|
|
684
|
+
`active` false closes the inputs; `answered` (what the person already did on it) draws its
|
|
685
|
+
answer: the option, row or button picked, the form's values with "Sent" where the submit was.
|
|
686
|
+
`components={{ table: TheirTable }}` draws a block type with the host's own component, given
|
|
687
|
+
the block's fields, `active` and `onAction` (a container's `children` come drawn). The pure
|
|
688
|
+
helpers `pressed`, `picked`, `chosen`, `filled`, `labelOf` and `problemOf` are what the blocks
|
|
689
|
+
send and check. An `automation` block draws `Automation` from the `view` the API filled in, read
|
|
690
|
+
only (no switch, its runs not pressable: the agent's page is where it is switched and opened);
|
|
691
|
+
one whose `view` is null says quietly that it is no longer there. A `file` block (`FileBlock`)
|
|
692
|
+
is a file the agent put in front of the person, compact: its kind's mark, its title over its name,
|
|
693
|
+
its size; pressed, it opens whole in the host's viewer (`files.open`, the conversation's
|
|
694
|
+
`FileHost`), where `FileView` draws it whatever its kind. A picture, a video or a sound plays where
|
|
695
|
+
it is instead (`part="media"`, `sc-surface__media`), in the browser's own player, streamed from the
|
|
696
|
+
address the host gives (`files.play`; the conversation's asks `client.conversations.play`, a link
|
|
697
|
+
the API signs for an hour that needs no credential and answers `Range`); a picture pressed opens
|
|
698
|
+
in the viewer, a player whose address ran out asks for a new one once, and a host that can't play
|
|
699
|
+
it keeps the row. Until the API has filled in `file` it is only named, quietly.
|
|
700
|
+
|
|
701
|
+
## AgentAbout
|
|
702
|
+
|
|
703
|
+
`<AgentAbout client agentId name messages? classNames? controls? apps? locale? view? onViewChange? onOpenFile? onOpenConversation? />`: Qué puede, made to sit
|
|
704
|
+
beside the conversation (it fits a page too). From the top: who it is about and, in its own words,
|
|
705
|
+
a line or two of what it takes care of; **what it does on its own**, a row per automation, its
|
|
706
|
+
mark what starts it (a glyph for a time or a call, the app's logo for its event), what it does then
|
|
707
|
+
and when (`Cada día a las 8:00`), a paused one quieter; what it asks the person's leave for; then **what it works with**,
|
|
708
|
+
six topics (`TOPICS`): apps (with their marks), tools (what it can do while it talks), knowledge
|
|
709
|
+
(its files and the tables it reads or writes), learning (what it remembered), channels (where
|
|
710
|
+
people write to it, and its devices), keys. Each topic is a row with its glyph, its name over a
|
|
711
|
+
preview of what it holds and its count, quieter when it holds nothing; a row opens the topic in
|
|
712
|
+
the same place, with the way back above it (the host's control on the title's line), a line saying
|
|
713
|
+
what it is, and its rows: an app with its tools by name; every tool, its own code's and the ones
|
|
714
|
+
every agent has under a heading each when it has both, the ones that are off quieter, saying so;
|
|
715
|
+
what it knows by whose it is, its own under its name and the organization's under theirs (said
|
|
716
|
+
once, by the heading), each file by its kind's mark, its name on one line, its folder under it
|
|
717
|
+
(and that it is only kept, when `search_files` does not read it), its size at the right, then
|
|
718
|
+
the tables with what it may do in them; what it learned with when it was kept; keys, its own and
|
|
719
|
+
the organization's, with where they may go. Its channels are read whole when the topic opens (`channels.list(agentId)`, again after
|
|
720
|
+
each `tools.refresh()`): WhatsApp, Instagram and SMS in that order, the disconnected last and
|
|
721
|
+
quieter, each by its brand's mark (`apps.logo` with the kind as its provider) and name, its number
|
|
722
|
+
or handle, the provider's word on it (`channelDetail`), and how it stands on a dot and a word
|
|
723
|
+
(`channelStates`: amber while pending, the danger tone, with the reason under the row, on an
|
|
724
|
+
error); a server without the list shows the facts' rows.
|
|
725
|
+
|
|
726
|
+
The words are the model's (`describe`), written from the agent's folder, its automations and its
|
|
727
|
+
apps: the summary, one line per automation tied to it by id (`does: [{automation, icon, text,
|
|
728
|
+
when}]`; `automationsOf(about, facts)` pairs them, a new automation showing its title and kind
|
|
729
|
+
until the words catch up) and what it asks leave for. Nobody asks for them: when they are
|
|
730
|
+
missing or the agent changed since (`about_at` null, which a change to its folder, its grants or
|
|
731
|
+
its automations does), the component has the agent write them again by itself, keeping the old
|
|
732
|
+
ones in view with a quiet line; when they could not be read or written, a line in the warning
|
|
733
|
+
tone and a button to try again.
|
|
734
|
+
|
|
735
|
+
The component only reads. A host that lets its people change things draws its own controls
|
|
736
|
+
through `controls` (`AboutControls`), each given `tools: { refresh() }`, which reads the facts
|
|
737
|
+
again and has the agent rewrite its words when the change made them stale: `newAutomation(tools)`
|
|
738
|
+
over what it does on its own, `automation(fact, tools)` at a card's right, `topic[topic](tools)`
|
|
739
|
+
over an open topic, and `app`, `tool`, `file`, `table`, `memory`, `key` and `channel` (the API's
|
|
740
|
+
`Channel`) at a row's right
|
|
741
|
+
(answer `null` for a row that gets none, such as a shared file). Call `tools.refresh()` once a
|
|
742
|
+
change is through. `onOpenFile(file)` opens a file pressed in what it knows.
|
|
743
|
+
|
|
744
|
+
It goes one level in and back, in place, like a phone's settings: a topic or an automation from
|
|
745
|
+
its home, a run from its automation. Each view opened from another has a bar that stays at the
|
|
746
|
+
top as it scrolls: the way back, named for where it goes, and at its end the host's controls (an
|
|
747
|
+
automation's are `controls.automation`, the same as on its row). An automation shows as
|
|
748
|
+
`Automation`, flat on the panel, with its runs; a run as `RunView`; both read with `client`
|
|
749
|
+
(`automations.get`, `runs.get`) and again every 3 s while a run is under way (`usePolled`). The
|
|
750
|
+
rows of what it does on its own also say how each stands when it needs a look: a run under way,
|
|
751
|
+
or its last one failed (`automations.list`). A move in slides from the side it lies on, a move
|
|
752
|
+
back from the other, and each view comes back scrolled where it was left. Where it is lives in
|
|
753
|
+
`view` (`AboutView`: `{kind: "home"}`, `{kind: "topic", topic}`, `{kind: "automation", name}`,
|
|
754
|
+
`{kind: "run", name, run}`), the panel's own unless the host keeps it (in its address, say) and
|
|
755
|
+
hears each move through `onViewChange`; `aboutBack(view)` is a step back, for a host's Escape.
|
|
756
|
+
`onOpenConversation(id)` opens a conversation a run opened or was asked in.
|
|
757
|
+
|
|
758
|
+
`messages` are the words, English by default (`loading`, `writing(name)`, `updating`, `refresh`:
|
|
759
|
+
the try-again button, `asks`, `failed`, `does`, `noAutomations`, `paused`, `kinds`: what an
|
|
760
|
+
automation waits for, `topics`, `empty` and `hints` by topic, `theirs(name)` and `organization`:
|
|
761
|
+
over what is the agent's own and the organization's, `builtIn`: over the tools every agent has,
|
|
762
|
+
`tables`: over the tables, `kept`: under a file nothing reads at a search, `off`, `can`: what it
|
|
763
|
+
may do in a table, `title(name)`, `facts`: over the topics, `actions`: a shipped action's name in
|
|
764
|
+
the owner's words (one of its own code, and any other nobody named, by `actionWords(name)`),
|
|
765
|
+
`size(bytes)`, `ago(when)`, `runGone`: in place of a run no longer there, `channelNames` by kind,
|
|
766
|
+
`channelStates` by status, `channelDetail(detail)`: the provider's word in the host's, `kit`: what
|
|
767
|
+
the kit's pieces in it say, an automation and a run). The pure helpers
|
|
768
|
+
`aboutState(about, aboutAt, loading)`, `automationsOf(about, facts)`, `TOPICS`, `namesOf(topic, facts, label)`, `countOf(topic, facts)`,
|
|
769
|
+
`toolName(action)` (`Gmail.SendEmail` is "Send email"), `actionWords(name)` (`ver_hoja` is "Ver
|
|
770
|
+
hoja", `gmail__GMAIL_SEND_EMAIL` "Send email (Gmail)"), `familyOf(name, type)`,
|
|
771
|
+
`formatSize(bytes, locale?)` and `toneOf(status)` are what it draws from; `FileMark` is the mark
|
|
772
|
+
it gives a file. The package draws its icons with `@phosphor-icons/react`.
|
|
773
|
+
|
|
774
|
+
## FileView
|
|
775
|
+
|
|
776
|
+
`<FileView blob name preview? messages? classNames? />`: one file to see and to save, whatever its
|
|
777
|
+
kind: a bar with its kind, its size (a PDF's pages) and the button that saves it under its name,
|
|
778
|
+
over the file as it reads. `preview` is what the API makes of it (`client.files.preview(at)`,
|
|
779
|
+
`FilePreview` by `kind`; `at` is `FileAt`: the organization's `{path}`, an agent's own
|
|
780
|
+
`{agentId, path}`, a conversation's `{conversationId, fileId}`): a spreadsheet as a grid, one
|
|
781
|
+
sheet at a time, its columns kept in view and its sheets as tabs at its foot (how many of its rows
|
|
782
|
+
are shown, when not all); a Word file as a document at a reading measure, its HTML kept again here
|
|
783
|
+
to the API's allow-list (`cleanDocument`); a deck's slides one under another, each a card with its
|
|
784
|
+
notes; Markdown drawn, plain words, a source in the mono face wrapped and never run; a PDF in the
|
|
785
|
+
browser's own viewer; a picture whole, a video and a sound in the browser's players; `unknown`
|
|
786
|
+
says it can't be shown, and saves it. Without `preview` it reads what the browser can of the bytes
|
|
787
|
+
alone (words, a CSV, a PDF, a picture). It fills the height it is given and scrolls inside.
|
|
788
|
+
`messages` (`FILE_MESSAGES`): `download`, `noPreview`, `noPreviewHint`, `truncated`, `file`,
|
|
789
|
+
`size(bytes)`, `pages(count)`, `sheets`, `rows(shown, total)`, `emptySheet`, `slide(number)`,
|
|
790
|
+
`notes`. Parts: `sc-file`, `sc-file__bar`, `sc-file__meta`, `sc-file__body`, `sc-file__text`,
|
|
791
|
+
`sc-file__plain`, `sc-file__code`, `sc-file__sheet`, `sc-file__table`, `sc-file__tabs`,
|
|
792
|
+
`sc-file__tab` (`aria-selected` on the one open), `sc-file__document`, `sc-file__slides`,
|
|
793
|
+
`sc-file__slide`, `sc-file__notes`, `sc-file__frame`, `sc-file__image`, `sc-file__media`
|
|
794
|
+
(`data-kind`), `sc-file__none`, `sc-file__note`.
|
|
795
|
+
|
|
796
|
+
## AgentCard
|
|
797
|
+
|
|
798
|
+
`<AgentCard id name role fields renderValue? classNames? />`: the Ficha as a page about the
|
|
799
|
+
agent, one sheet centred at 600px. At the top the agent's mark (the glyph of its work alone,
|
|
800
|
+
bare, in the tint `agentTint(id)` keeps for it everywhere) beside its name, its role under it;
|
|
801
|
+
then its short fields as a quiet list of properties, each label beside its value; under a
|
|
802
|
+
hairline, each `block` field (a long text) under a small heading at a reading measure. `name`, `role` and each of `fields` is a `CardField {key, label, value, placeholder?,
|
|
803
|
+
block?}`; an empty value draws its placeholder, muted. The card draws text; a host that edits in
|
|
804
|
+
place gives `renderValue(field)`, which draws its own control in the text's place (the band's two
|
|
805
|
+
included) and may answer `undefined` to leave a field to the card. `agentTint(id)` is the pure
|
|
806
|
+
helper under the mark: `sky`, `rose` or `sand` from the id, so a rename changes nothing.
|
|
807
|
+
|
|
808
|
+
## Theming
|
|
809
|
+
|
|
810
|
+
One vocabulary for every component, in React and in the widget: the `--sc-*` tokens, set on
|
|
811
|
+
`:root`, on `<signalcore-chat>` or on any ancestor. Every visual decision of ours reads one of
|
|
812
|
+
them; their defaults are neutral greys that follow the page's `color-scheme` (`light-dark()`),
|
|
813
|
+
and in the widget the page's own type and text colour stand in for ours. There are no themes or
|
|
814
|
+
presets: a host says what it is, once. Unset, a token keeps the default in the right column.
|
|
815
|
+
|
|
816
|
+
| Token | What it decides | Default |
|
|
817
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
818
|
+
| `--sc-font` | the face | the page's in the widget; `ui-sans-serif, system-ui` |
|
|
819
|
+
| `--sc-font-size` | the base size; every other size is relative to it | the page's in the widget; `14px` |
|
|
820
|
+
| `--sc-font-size-sm` | a notch down: a step, a small button, a kit's details | base − 1px |
|
|
821
|
+
| `--sc-font-size-xs` | quiet lines: the working line, a meta, a hint, the modes | base − 2px |
|
|
822
|
+
| `--sc-font-size-lg` | a title: the empty chat's | base + 2px |
|
|
823
|
+
| `--sc-font-weight` | the regular weight | the page's (inherited); 400 where a part resets it |
|
|
824
|
+
| `--sc-font-weight-medium` | names, buttons, the modes | `500` |
|
|
825
|
+
| `--sc-font-weight-bold` | titles, the call's state | `600` |
|
|
826
|
+
| `--sc-font-mono` | code, the console | `ui-monospace, "SF Mono", Menlo` |
|
|
827
|
+
| `--sc-line-height` | the lines' height | `1.5` |
|
|
828
|
+
| `--sc-fg` | the ink | the page's text in the widget; near black, near white |
|
|
829
|
+
| `--sc-muted` | quieter ink: roles, hints, placeholders | grey |
|
|
830
|
+
| `--sc-subtle` | quieter still: a meta, the builder's line | lighter grey |
|
|
831
|
+
| `--sc-bg` | the page's ground (the builder's), what sits on the main action | off-white, near black |
|
|
832
|
+
| `--sc-surface` | the panel, the chat, cards, the composer, the call | white, `#121212` |
|
|
833
|
+
| `--sc-sunken` | a notch off the surface: code, a step's output | a breath off it |
|
|
834
|
+
| `--sc-line` | hairlines and borders | light grey, white at 12% |
|
|
835
|
+
| `--sc-accent` | the launcher, a step running, a picked option, the caret, focus | the ink |
|
|
836
|
+
| `--sc-accent-fg` | what sits on the accent (the launcher's glyph) | the surface |
|
|
837
|
+
| `--sc-signal` | the agent at work: the breathing dot, the still orb | the accent |
|
|
838
|
+
| `--sc-primary`, `--sc-on-primary` | the main action (send, a primary button, mute on) and its glyph | the ink, `--sc-bg` |
|
|
839
|
+
| `--sc-bubble`, `--sc-bubble-fg` | the person's message, and its text | the ink at 5% over the surface, the ink |
|
|
840
|
+
| `--sc-read` | the two checks of a message the person read | WhatsApp's blue, `#53bdeb` |
|
|
841
|
+
| `--sc-success`, `--sc-warning`, `--sc-danger` | the tones a note, a step, a field's error take | green, amber, red |
|
|
842
|
+
| `--sc-danger-fg` | what sits on the danger tone filled (hanging up) | white |
|
|
843
|
+
| `--sc-sky`, `--sc-sand`, `--sc-rose` | the agent's tints as inks (its mark in the face, the still orb in a call); the wash under each derives from it | blue, sand, rose |
|
|
844
|
+
| `--sc-brand-from`, `--sc-brand-to`, `--sc-on-brand` | the brand's gradient: the builder's sweeping light and its send (our app's) | violet, orange, white |
|
|
845
|
+
| `--sc-focus` | a field's focus ring | the accent at 40% |
|
|
846
|
+
| `--sc-orb-color` | the orb: its shader's light, and the still orb's ink | the shaders' own spectrum; the still orb in the signal |
|
|
847
|
+
| `--sc-radius` | a box's corners (cards, the composer, the panel; inner corners derive from it) | `12px` |
|
|
848
|
+
| `--sc-radius-full` | what is round: pills, round buttons, the launcher, the modes | `9999px` |
|
|
849
|
+
| `--sc-space` | the spacing unit: every gap and padding of the chat, the space and the call is a multiple of it | `4px` |
|
|
850
|
+
| `--sc-shadow` | what lifts a card off the page | a hair |
|
|
851
|
+
| `--sc-shadow-raised` | what stands over the surface: the composer, the mode on | soft |
|
|
852
|
+
| `--sc-shadow-overlay` | what floats over the page: a popover, the widget's panel and launcher | deep |
|
|
853
|
+
| `--sc-panel-width`, `--sc-panel-height` | the widget's floating panel | `400px`, `640px` (within the screen) |
|
|
854
|
+
| `--sc-launcher-size`, `--sc-launcher-offset` | the widget's launcher, and how far from the page's corner it and the panel sit | `56px`, `20px` |
|
|
855
|
+
|
|
856
|
+
In React, the same tokens on any ancestor, your classes on the parts (`classNames`) and your own
|
|
857
|
+
drawing of a block (`components`):
|
|
534
858
|
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
"
|
|
538
|
-
"
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
859
|
+
```tsx
|
|
860
|
+
<div
|
|
861
|
+
className="support"
|
|
862
|
+
style={{ "--sc-accent": "#0f766e", "--sc-font": "Inter" } as CSSProperties}
|
|
863
|
+
>
|
|
864
|
+
<AgentSpace
|
|
865
|
+
client={client}
|
|
866
|
+
agentId="agt_…"
|
|
867
|
+
classNames={{ "sc-chat__composer": "support-composer", "sc-space__mode": "support-mode" }}
|
|
868
|
+
components={{ table: SupportTable }}
|
|
869
|
+
/>
|
|
870
|
+
</div>
|
|
542
871
|
```
|
|
543
872
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
`
|
|
566
|
-
|
|
567
|
-
`
|
|
873
|
+
### Parts
|
|
874
|
+
|
|
875
|
+
In the widget, the page styles what is inside its shadow root by `::part(…)`
|
|
876
|
+
(`signalcore-chat::part(send) { … }`). An element may carry several names; since `::part()`
|
|
877
|
+
takes no attribute selector, a state is a name of its own (`mode-selected`). The widget's own
|
|
878
|
+
`stylesheet` reaches everything inside by class as well.
|
|
879
|
+
|
|
880
|
+
| Part | What it is |
|
|
881
|
+
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
882
|
+
| `launcher`, `panel` | the round button at the corner, and the panel it opens (or the inline one) |
|
|
883
|
+
| `space`, `header`, `close` | the agent's space, its face (with `header`), its close |
|
|
884
|
+
| `modes`, `mode`, `mode-text`, `mode-voice`, `mode-selected` | the two modes beside the paperclip (and at the call's foot), each, the one on |
|
|
885
|
+
| `chat`, `transcript`, `empty`, `suggestion` | the chat, its scrolling log, a conversation's start, a thing to say first |
|
|
886
|
+
| `message`, `message-person`, `message-agent`, `message-business` | a message, by who said it (the business's: a person of it, on a channel) |
|
|
887
|
+
| `text` | the agent's words (Markdown), in a reply or a surface |
|
|
888
|
+
| `files` | the files sent with a message, or waiting over the composer |
|
|
889
|
+
| `surface` | a screen the agent shows |
|
|
890
|
+
| `approval`, `confirming` | a yes the person gives, and the line for one the business gives |
|
|
891
|
+
| `status` | the line while the agent works ("Typing…") |
|
|
892
|
+
| `composer`, `input`, `attach`, `actions`, `send`, `stop` | where the person writes: its field, the paperclip, the modes' slot, send, stop |
|
|
893
|
+
| `voice`, `call`, `orb`, `call-state`, `call-line`, `captions` | the voice panel, the call in it, the orb, what the call is doing, its time, what is said |
|
|
894
|
+
| `call-controls`, `mute`, `hang-up`, `call-after` | the call's controls, and what is offered once it is over |
|
|
895
|
+
| `card`, `stack`, `divider`, `fields`, `table`, `list`, `row`, `stat`, `image`, `choice`, `option`, `form` | the blocks of what the agent shows |
|
|
896
|
+
| `note`, `note-info`, `note-success`, `note-warning`, `note-danger` | a note, by its tone |
|
|
897
|
+
| `field`, `control` | a form's field, and the box of its control |
|
|
898
|
+
| `button`, `button-primary`, `button-neutral`, `button-danger`, `button-ghost` | a button, by its tone (with its own name too: `close`, `mute`, `hang-up`) |
|
|
899
|
+
|
|
900
|
+
In React the same elements carry the same `part` names (harmless outside a shadow root), and
|
|
901
|
+
everything is reachable by its class: `classNames` adds the host's own classes to named parts,
|
|
902
|
+
which are also the BEM classes they carry. The chat: `sc-chat` (`--builder`, `--console`), `sc-chat__log`, `sc-chat__message`,
|
|
903
|
+
`sc-chat__message--person`, `sc-chat__message--agent`, `sc-chat__message--action`,
|
|
904
|
+
`sc-chat__business` and `sc-chat__message--business` (what a person of the business wrote on a
|
|
905
|
+
channel), `sc-chat__meta` (who wrote it, where it got to), `sc-chat__delivery` (`data-delivery`),
|
|
906
|
+
`sc-chat__steps`, `sc-chat__step` (`--running`, `--waiting`, `--done`, `--failed`),
|
|
907
|
+
`sc-chat__mark` (the step's glyph or state), `sc-chat__output` (what a step returned, in the
|
|
908
|
+
builder's look), `sc-chat__text` (the agent's words),
|
|
909
|
+
`sc-chat__request`, `sc-chat__confirming` (a person's line for a yes the business gives),
|
|
910
|
+
`sc-chat__handoff` (the line where a person took over), `sc-chat__handover`
|
|
911
|
+
(where who answers changed, its `time`), `sc-chat__decision` (a message let pass),
|
|
912
|
+
`sc-chat__message--heard` (the greeting heard on a call), `sc-chat__cut` (where a reply on a call
|
|
913
|
+
was cut short), `sc-chat__reply`,
|
|
914
|
+
`sc-chat__status`, `sc-chat__empty`,
|
|
915
|
+
`sc-chat__suggestion`, `sc-chat__composer`, `sc-chat__input`, `sc-chat__actions`,
|
|
916
|
+
`sc-chat__send`, `sc-chat__stop`. A surface: `sc-surface`, `sc-surface__card`,
|
|
917
|
+
`sc-surface__stack` (`--row`), `sc-surface__text`, `sc-surface__fields`, `sc-surface__table`,
|
|
918
|
+
`sc-surface__note` (`--info`, `--success`, `--warning`, `--danger`), `sc-surface__button`
|
|
919
|
+
(`--primary`, `--neutral`, `--danger`), `sc-surface__choice`, `sc-surface__option`,
|
|
920
|
+
`sc-surface__form`, `sc-surface__field`, `sc-surface__submit`. Qué puede: `sc-about`,
|
|
921
|
+
`sc-about__pane`, `sc-about__intro`, `sc-about__heading`, `sc-about__name` (the mark and the name, `data-tint`), `sc-about__lead`, `sc-about__updated`
|
|
922
|
+
(the words that could not be read, and the button to try again), `sc-about__updating`,
|
|
923
|
+
`sc-about__section`, `sc-about__label`, `sc-about__asks`, `sc-about__menu`, `sc-about__bar`
|
|
924
|
+
(`sc-about__bar-end`: the host's controls), `sc-about__back`, `sc-about__automation`,
|
|
925
|
+
`sc-about__run`, `sc-about__group`, `sc-about__title`, `sc-about__count`, `sc-about__tools` (the
|
|
926
|
+
host's control on a topic's title line), `sc-about__hint`, `sc-about__subtitle` (over a part of
|
|
927
|
+
a topic: whose, or what kind), `sc-about__file` (a file's row: its name and folder on a line
|
|
928
|
+
each), `sc-about__memory`, `sc-about__detail` (a channel's provider's word),
|
|
929
|
+
`sc-about__dot` (`data-tone`), `sc-about__state` (a channel's dot and word, `data-tone`),
|
|
930
|
+
`sc-about__empty`, `sc-about__writing`. The card: `sc-card` (its tint: `--sky`, `--rose`,
|
|
931
|
+
`--sand`), `sc-card__head`, `sc-card__mark` (the agent's mark, bare), `sc-card__who`,
|
|
932
|
+
`sc-card__name`, `sc-card__role`, `sc-card__facts`, `sc-card__fact`, `sc-card__body`,
|
|
933
|
+
`sc-card__row`, `sc-card__label`, `sc-card__value`, `sc-card__empty`. An
|
|
934
|
+
automation: `sc-automation` (`--gone`: the block's quiet line for one not there),
|
|
935
|
+
`sc-automation__head`, `sc-automation__heading`, `sc-automation__title`, `sc-automation__when`,
|
|
936
|
+
`sc-automation__end` (the state's word and the switch), `sc-automation__dot`,
|
|
937
|
+
`sc-automation__summary`, `sc-automation__section`, `sc-automation__label`,
|
|
938
|
+
`sc-automation__steps`, `sc-automation__step`, `sc-automation__number`, `sc-automation__name`,
|
|
939
|
+
`sc-automation__uses`, `sc-automation__use`, `sc-automation__board`, `sc-automation__more`,
|
|
940
|
+
`sc-automation__runs`, `sc-automation__run--open`, `sc-automation__status` (a run's check),
|
|
941
|
+
`sc-automation__empty`. A run: `sc-run`, `sc-run__head`, `sc-run__by` (what started it, its mark
|
|
942
|
+
named by it), `sc-run__heading`, `sc-run__title`, `sc-run__meta` (when), `sc-run__end` (how it
|
|
943
|
+
stands, `sc-run__took` under it), `sc-run__summary`, `sc-run__section`, `sc-run__label`, `sc-run__steps`,
|
|
944
|
+
`sc-run__step`, `sc-run__number`, `sc-run__name`, `sc-run__calls`, `sc-run__call` (`--would`,
|
|
945
|
+
`--failed`), `sc-run__count`, `sc-run__error`, `sc-run__live` (the dot of one still going),
|
|
946
|
+
`sc-run__links`, `sc-run__empty`. The stylesheet is plain,
|
|
947
|
+
unlayered CSS; a host whose utilities must win imports it into a layer under them
|
|
948
|
+
(`@import "@signalcore/ui/styles.css" layer(components);`).
|
|
949
|
+
|
|
950
|
+
Tests: `pnpm test` here, or `pnpm -r test` from `v3/`.
|