@yadsh/dsh-qa-surface 0.13.0 → 0.15.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 +148 -22
- package/compatibility.json +7 -3
- package/lib/access/capability-catalog.js +22 -6
- package/lib/access/capability-catalog.js.map +1 -1
- package/lib/access/model.js +36 -2
- package/lib/access/model.js.map +1 -1
- package/lib/access/service.js +19 -4
- package/lib/access/service.js.map +1 -1
- package/lib/account-remotes.js +4 -0
- package/lib/account-remotes.js.map +1 -1
- package/lib/accounts/database.js +49 -9
- package/lib/accounts/database.js.map +1 -1
- package/lib/accounts/errors.js.map +1 -1
- package/lib/accounts/file.js.map +1 -1
- package/lib/accounts/store.js +53 -8
- package/lib/accounts/store.js.map +1 -1
- package/lib/admin/permissions.js +6 -0
- package/lib/admin/permissions.js.map +1 -1
- package/lib/admin/quality-store.js +95 -19
- package/lib/admin/quality-store.js.map +1 -1
- package/lib/admin/service.js +178 -0
- package/lib/admin/service.js.map +1 -1
- package/lib/cli.js +24 -4
- package/lib/cli.js.map +1 -1
- package/lib/client/QaAccountsController.js +52 -5
- package/lib/client/QaAccountsController.js.map +1 -1
- package/lib/client/QaConfigController.js.map +1 -1
- package/lib/client/QaSessionController.js +626 -63
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +293 -43
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaSurfaceGuard.js +2 -2
- package/lib/client/QaSurfaceGuard.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +4 -2
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/admin/QaAdmin.js +59 -34
- package/lib/client/admin/QaAdmin.js.map +1 -1
- package/lib/client/admin/pages/Conversations.js +12 -12
- package/lib/client/admin/pages/Conversations.js.map +1 -1
- package/lib/client/admin/pages/ExpertMemory.js +210 -0
- package/lib/client/admin/pages/ExpertMemory.js.map +1 -0
- package/lib/client/admin/pages/Overview.js +4 -4
- package/lib/client/admin/pages/Overview.js.map +1 -1
- package/lib/client/admin/pages/Quality.js +5 -5
- package/lib/client/admin/pages/Quality.js.map +1 -1
- package/lib/client/admin/pages/Review.js +5 -5
- package/lib/client/admin/pages/Review.js.map +1 -1
- package/lib/client/admin/pages/SkillFiles.js +3 -3
- package/lib/client/admin/pages/SkillFiles.js.map +1 -1
- package/lib/client/admin/pages/Users.js +12 -12
- package/lib/client/admin/pages/Users.js.map +1 -1
- package/lib/client/admin/routes.js +4 -0
- package/lib/client/admin/routes.js.map +1 -1
- package/lib/client/admin/shared.js +15 -6
- package/lib/client/admin/shared.js.map +1 -1
- package/lib/client/admin/styles.js +3 -0
- package/lib/client/admin/styles.js.map +1 -1
- package/lib/client/attestation.js +11 -2
- package/lib/client/attestation.js.map +1 -1
- package/lib/client/audit/QaAuditDialog.js +2 -2
- package/lib/client/audit/QaAuditDialog.js.map +1 -1
- package/lib/client/audit/QaAuditRowBadge.js +1 -1
- package/lib/client/audit/QaAuditRowBadge.js.map +1 -1
- package/lib/client/components/Markdown.js +7 -2
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaAgentsDrawer.js +7 -5
- package/lib/client/components/QaAgentsDrawer.js.map +1 -1
- package/lib/client/components/QaApproval.js +2 -2
- package/lib/client/components/QaApproval.js.map +1 -1
- package/lib/client/components/QaAuthGate.js +6 -6
- package/lib/client/components/QaAuthGate.js.map +1 -1
- package/lib/client/components/QaChangelog.js +77 -0
- package/lib/client/components/QaChangelog.js.map +1 -1
- package/lib/client/components/QaComposer.js +9 -6
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaFileAttachment.js +1 -1
- package/lib/client/components/QaFileAttachment.js.map +1 -1
- package/lib/client/components/QaFilesPanel.js +7 -7
- package/lib/client/components/QaFilesPanel.js.map +1 -1
- package/lib/client/components/QaHeader.js +4 -4
- package/lib/client/components/QaHeader.js.map +1 -1
- package/lib/client/components/QaMessage.js +12 -12
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaModal.js +11 -2
- package/lib/client/components/QaModal.js.map +1 -1
- package/lib/client/components/QaQuestions.js +4 -4
- package/lib/client/components/QaQuestions.js.map +1 -1
- package/lib/client/components/QaQueueDock.js +137 -0
- package/lib/client/components/QaQueueDock.js.map +1 -0
- package/lib/client/components/QaRequestQueue.js +22 -0
- package/lib/client/components/QaRequestQueue.js.map +1 -0
- package/lib/client/components/QaRightRail.js +2 -2
- package/lib/client/components/QaRightRail.js.map +1 -1
- package/lib/client/components/QaSidebar.js +98 -7
- package/lib/client/components/QaSidebar.js.map +1 -1
- package/lib/client/components/QaSourcesPanel.js +10 -8
- package/lib/client/components/QaSourcesPanel.js.map +1 -1
- package/lib/client/components/QaThemeSwitcher.js +19 -0
- package/lib/client/components/QaThemeSwitcher.js.map +1 -0
- package/lib/client/components/QaTurnNotice.js +40 -0
- package/lib/client/components/QaTurnNotice.js.map +1 -0
- package/lib/client/components/QaTurnRail.js +3 -3
- package/lib/client/components/QaTurnRail.js.map +1 -1
- package/lib/client/components/QaWelcomeNotice.js +2 -6
- package/lib/client/components/QaWelcomeNotice.js.map +1 -1
- package/lib/client/components/QaWidthHandle.js +2 -2
- package/lib/client/components/QaWidthHandle.js.map +1 -1
- package/lib/client/components/QaWorkGroup.js +5 -5
- package/lib/client/components/QaWorkGroup.js.map +1 -1
- package/lib/client/components/QaWorkspaceBrowser.js +10 -10
- package/lib/client/components/QaWorkspaceBrowser.js.map +1 -1
- package/lib/client/components/VariantSwitcher.js +1 -1
- package/lib/client/components/VariantSwitcher.js.map +1 -1
- package/lib/client/failure-copy.js +26 -8
- package/lib/client/failure-copy.js.map +1 -1
- package/lib/client/focus-ring.js +193 -0
- package/lib/client/focus-ring.js.map +1 -0
- package/lib/client/index.js +40 -20
- package/lib/client/index.js.map +1 -1
- package/lib/client/lineage.js +3 -16
- package/lib/client/lineage.js.map +1 -1
- package/lib/client/markdown/CodeBlock.js +2 -2
- package/lib/client/markdown/CodeBlock.js.map +1 -1
- package/lib/client/markdown/blocks.js +118 -31
- package/lib/client/markdown/blocks.js.map +1 -1
- package/lib/client/markdown/math.js +1 -1
- package/lib/client/markdown/math.js.map +1 -1
- package/lib/client/markdown/render.js +32 -16
- package/lib/client/markdown/render.js.map +1 -1
- package/lib/client/markdown/source-chip.js +3 -3
- package/lib/client/markdown/source-chip.js.map +1 -1
- package/lib/client/notifications/notification-dispatcher.js +90 -0
- package/lib/client/notifications/notification-dispatcher.js.map +1 -0
- package/lib/client/notifications/preferences.js +61 -0
- package/lib/client/notifications/preferences.js.map +1 -0
- package/lib/client/notifications/turn-completion-source.js +82 -0
- package/lib/client/notifications/turn-completion-source.js.map +1 -0
- package/lib/client/notifications/use-turn-notifications.js +108 -0
- package/lib/client/notifications/use-turn-notifications.js.map +1 -0
- package/lib/client/panels/PanelHost.js +5 -5
- package/lib/client/panels/PanelHost.js.map +1 -1
- package/lib/client/panels/PanelLauncher.js +2 -2
- package/lib/client/panels/PanelLauncher.js.map +1 -1
- package/lib/client/project-session-state.js +156 -4
- package/lib/client/project-session-state.js.map +1 -1
- package/lib/client/role/RoleSelector.js +9 -4
- package/lib/client/role/RoleSelector.js.map +1 -1
- package/lib/client/settings/card.js +58 -17
- package/lib/client/settings/card.js.map +1 -1
- package/lib/client/settings/fields.js +11 -10
- package/lib/client/settings/fields.js.map +1 -1
- package/lib/client/settings/format.js +0 -9
- package/lib/client/settings/format.js.map +1 -1
- package/lib/client/settings/sections/access.js +5 -5
- package/lib/client/settings/sections/access.js.map +1 -1
- package/lib/client/settings/sections/accounts.js +10 -10
- package/lib/client/settings/sections/accounts.js.map +1 -1
- package/lib/client/settings/sections/attachments.js +6 -6
- package/lib/client/settings/sections/attachments.js.map +1 -1
- package/lib/client/settings/sections/branding.js +7 -7
- package/lib/client/settings/sections/branding.js.map +1 -1
- package/lib/client/settings/sections/common.js +4 -2
- package/lib/client/settings/sections/common.js.map +1 -1
- package/lib/client/settings/sections/docs.js +4 -3
- package/lib/client/settings/sections/docs.js.map +1 -1
- package/lib/client/settings/sections/embedding.js +2 -2
- package/lib/client/settings/sections/embedding.js.map +1 -1
- package/lib/client/settings/sections/interface.js +12 -12
- package/lib/client/settings/sections/interface.js.map +1 -1
- package/lib/client/settings/sections/lockdown.js +9 -9
- package/lib/client/settings/sections/lockdown.js.map +1 -1
- package/lib/client/settings/sections/notes.js +12 -12
- package/lib/client/settings/sections/notes.js.map +1 -1
- package/lib/client/settings/sections/session.js +15 -12
- package/lib/client/settings/sections/session.js.map +1 -1
- package/lib/client/settings/sections/slash.js +9 -9
- package/lib/client/settings/sections/slash.js.map +1 -1
- package/lib/client/settings/sections/sources.js +26 -26
- package/lib/client/settings/sections/sources.js.map +1 -1
- package/lib/client/settings/sections/status.js +2 -2
- package/lib/client/settings/sections/status.js.map +1 -1
- package/lib/client/settings/styles.js +15 -9
- package/lib/client/settings/styles.js.map +1 -1
- package/lib/client/slash/SlashPalette.js +4 -4
- package/lib/client/slash/SlashPalette.js.map +1 -1
- package/lib/client/styles.js +145 -83
- package/lib/client/styles.js.map +1 -1
- package/lib/client/theme-preference.js +162 -0
- package/lib/client/theme-preference.js.map +1 -0
- package/lib/client/types.js +8 -0
- package/lib/client/types.js.map +1 -1
- package/lib/client/use-session-ui-state.js +7 -5
- package/lib/client/use-session-ui-state.js.map +1 -1
- package/lib/client/user-settings/GeneralSettingsPage.js +6 -6
- package/lib/client/user-settings/GeneralSettingsPage.js.map +1 -1
- package/lib/client/user-settings/IntegrationTokensPage.js +6 -6
- package/lib/client/user-settings/IntegrationTokensPage.js.map +1 -1
- package/lib/client/user-settings/NotificationSettingsPage.js +107 -0
- package/lib/client/user-settings/NotificationSettingsPage.js.map +1 -0
- package/lib/client/user-settings/PasswordSettingsPage.js +1 -1
- package/lib/client/user-settings/PasswordSettingsPage.js.map +1 -1
- package/lib/client/user-settings/ProfileSettingsPage.js +2 -2
- package/lib/client/user-settings/ProfileSettingsPage.js.map +1 -1
- package/lib/client/user-settings/SkillCatalog.js +3 -3
- package/lib/client/user-settings/SkillCatalog.js.map +1 -1
- package/lib/client/user-settings/SkillEditor.js +26 -8
- package/lib/client/user-settings/SkillEditor.js.map +1 -1
- package/lib/client/user-settings/SkillToolPicker.js +3 -3
- package/lib/client/user-settings/SkillToolPicker.js.map +1 -1
- package/lib/client/user-settings/SkillsSettingsPage.js +1 -1
- package/lib/client/user-settings/SkillsSettingsPage.js.map +1 -1
- package/lib/client/user-settings/StartersSettingsPage.js +6 -6
- package/lib/client/user-settings/StartersSettingsPage.js.map +1 -1
- package/lib/client/user-settings/UserSettingsDialog.js +7 -2
- package/lib/client/user-settings/UserSettingsDialog.js.map +1 -1
- package/lib/client/user-settings/copy.js +2 -0
- package/lib/client/user-settings/copy.js.map +1 -1
- package/lib/client/user-settings/draft.js +4 -1
- package/lib/client/user-settings/draft.js.map +1 -1
- package/lib/client/user-settings/fields.js +5 -5
- package/lib/client/user-settings/fields.js.map +1 -1
- package/lib/client.js +7307 -4010
- package/lib/client.js.map +1 -1
- package/lib/config-resolvers/defaults.js +7 -0
- package/lib/config-resolvers/defaults.js.map +1 -1
- package/lib/config-resolvers/lockdown.js +7 -0
- package/lib/config-resolvers/lockdown.js.map +1 -1
- package/lib/config-resolvers/notifications.js +15 -0
- package/lib/config-resolvers/notifications.js.map +1 -0
- package/lib/config-resolvers/session.js +11 -1
- package/lib/config-resolvers/session.js.map +1 -1
- package/lib/config.js +67 -18
- package/lib/config.js.map +1 -1
- package/lib/enforcement/tool-grants.js +6 -6
- package/lib/enforcement/tool-grants.js.map +1 -1
- package/lib/index.js +145 -30
- package/lib/index.js.map +1 -1
- package/lib/integration/answer.js +74 -8
- package/lib/integration/answer.js.map +1 -1
- package/lib/integration/expert-memory.js +39 -0
- package/lib/integration/expert-memory.js.map +1 -0
- package/lib/integration/host-runner.js +26 -14
- package/lib/integration/host-runner.js.map +1 -1
- package/lib/notifications.js +66 -0
- package/lib/notifications.js.map +1 -0
- package/lib/personal-skills/errors.js.map +1 -1
- package/lib/personal-skills/service.js +81 -7
- package/lib/personal-skills/service.js.map +1 -1
- package/lib/prompt-notes.js +2 -10
- package/lib/prompt-notes.js.map +1 -1
- package/lib/provenance/host-store.js +8 -5
- package/lib/provenance/host-store.js.map +1 -1
- package/lib/qa-tools/docs-tools.js +1 -1
- package/lib/qa-tools/docs-tools.js.map +1 -1
- package/lib/qa-tools/lifecycle.js +6 -3
- package/lib/qa-tools/lifecycle.js.map +1 -1
- package/lib/request-queue.js +32 -0
- package/lib/request-queue.js.map +1 -0
- package/lib/resolve-config.js +2 -0
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +7 -2
- package/lib/secure-session.js.map +1 -1
- package/lib/shared/settings.js +19 -2
- package/lib/shared/settings.js.map +1 -1
- package/lib/turn-failure-log.js +67 -0
- package/lib/turn-failure-log.js.map +1 -0
- package/lib/typert.host.js +1398 -651
- package/lib/typert.remote-client.d.ts +15 -1
- package/lib/typert.remote-client.js +1315 -645
- package/lib/types/access/capability-catalog.d.ts +20 -4
- package/lib/types/access/model.d.ts +23 -0
- package/lib/types/access/service.d.ts +2 -3
- package/lib/types/account-remotes.d.ts +8 -1
- package/lib/types/accounts/database.d.ts +13 -3
- package/lib/types/accounts/errors.d.ts +9 -1
- package/lib/types/accounts/file.d.ts +12 -0
- package/lib/types/accounts/store.d.ts +30 -2
- package/lib/types/admin/quality-store.d.ts +33 -0
- package/lib/types/admin/service.d.ts +60 -1
- package/lib/types/client/QaAccountsController.d.ts +25 -2
- package/lib/types/client/QaConfigController.d.ts +2 -2
- package/lib/types/client/QaSessionController.d.ts +199 -6
- package/lib/types/client/QaSurface.d.ts +6 -1
- package/lib/types/client/admin/pages/ExpertMemory.d.ts +19 -0
- package/lib/types/client/admin/routes.d.ts +3 -1
- package/lib/types/client/admin/shared.d.ts +4 -0
- package/lib/types/client/components/Markdown.d.ts +8 -1
- package/lib/types/client/components/QaHeader.d.ts +18 -4
- package/lib/types/client/components/QaModal.d.ts +7 -0
- package/lib/types/client/components/QaQueueDock.d.ts +27 -0
- package/lib/types/client/components/QaRequestQueue.d.ts +20 -0
- package/lib/types/client/components/QaSidebar.d.ts +14 -0
- package/lib/types/client/components/QaThemeSwitcher.d.ts +14 -0
- package/lib/types/client/components/QaTurnNotice.d.ts +55 -0
- package/lib/types/client/focus-ring.d.ts +65 -0
- package/lib/types/client/lineage.d.ts +3 -13
- package/lib/types/client/markdown/blocks.d.ts +30 -2
- package/lib/types/client/markdown/render.d.ts +4 -1
- package/lib/types/client/notifications/notification-dispatcher.d.ts +68 -0
- package/lib/types/client/notifications/preferences.d.ts +44 -0
- package/lib/types/client/notifications/turn-completion-source.d.ts +114 -0
- package/lib/types/client/notifications/use-turn-notifications.d.ts +51 -0
- package/lib/types/client/project-session-state.d.ts +35 -1
- package/lib/types/client/role/RoleSelector.d.ts +7 -1
- package/lib/types/client/settings/card.d.ts +36 -5
- package/lib/types/client/settings/fields.d.ts +24 -0
- package/lib/types/client/settings/format.d.ts +0 -5
- package/lib/types/client/settings/sections/common.d.ts +3 -1
- package/lib/types/client/settings/sections/session.d.ts +2 -1
- package/lib/types/client/settings/styles.d.ts +9 -4
- package/lib/types/client/settings-extensions/user-session.d.ts +1 -1
- package/lib/types/client/theme-preference.d.ts +69 -0
- package/lib/types/client/types.d.ts +25 -1
- package/lib/types/client/use-session-ui-state.d.ts +6 -4
- package/lib/types/client/user-settings/GeneralSettingsPage.d.ts +5 -5
- package/lib/types/client/user-settings/NotificationSettingsPage.d.ts +40 -0
- package/lib/types/client/user-settings/UserSettingsDialog.d.ts +12 -2
- package/lib/types/client/user-settings/draft.d.ts +4 -1
- package/lib/types/client/user-settings/fields.d.ts +7 -0
- package/lib/types/config-resolvers/notifications.d.ts +10 -0
- package/lib/types/config-resolvers/session.d.ts +6 -0
- package/lib/types/config.d.ts +20 -1
- package/lib/types/index.d.ts +53 -5
- package/lib/types/integration/answer.d.ts +27 -0
- package/lib/types/integration/expert-memory.d.ts +49 -0
- package/lib/types/notifications.d.ts +32 -0
- package/lib/types/personal-skills/errors.d.ts +1 -1
- package/lib/types/personal-skills/service.d.ts +6 -0
- package/lib/types/prompt-notes.d.ts +17 -4
- package/lib/types/request-queue.d.ts +32 -0
- package/lib/types/shared/settings.d.ts +19 -2
- package/lib/types/turn-failure-log.d.ts +18 -0
- package/lib/types/types.d.ts +180 -4
- package/package.json +64 -63
- package/scripts/attach-workspace-sessions.mjs +24 -5
- package/scripts/repair-session-events.mjs +25 -5
- package/lib/client/markdown/MermaidBlock.js +0 -80
- package/lib/client/markdown/MermaidBlock.js.map +0 -1
- package/lib/types/client/markdown/MermaidBlock.d.ts +0 -6
package/lib/client/QaSurface.js
CHANGED
|
@@ -8,21 +8,29 @@ import { QA_SESSION_IDLE_STATE } from "./types.js";
|
|
|
8
8
|
import { QaAuthGate } from "./components/QaAuthGate.js";
|
|
9
9
|
import { QaApproval } from "./components/QaApproval.js";
|
|
10
10
|
import { QaQuestions } from "./components/QaQuestions.js";
|
|
11
|
+
import { QaRequestQueue } from "./components/QaRequestQueue.js";
|
|
11
12
|
import { QaComposer } from "./components/QaComposer.js";
|
|
13
|
+
import { QaQueueDock } from "./components/QaQueueDock.js";
|
|
12
14
|
import { QaHeader, QaSubagentBanner } from "./components/QaHeader.js";
|
|
15
|
+
import { QaThemeSwitcher } from "./components/QaThemeSwitcher.js";
|
|
16
|
+
import { useQaThemePreference } from "./theme-preference.js";
|
|
13
17
|
import { QaMessage } from "./components/QaMessage.js";
|
|
14
18
|
import { buildChatRows, QaSidebar } from "./components/QaSidebar.js";
|
|
15
19
|
import { QaAgentsDrawer, collectSubagents, } from "./components/QaAgentsDrawer.js";
|
|
16
20
|
import { collectChatFiles, countChatAttachments } from "./chat-files.js";
|
|
17
21
|
import { QaFilesPanel } from "./components/QaFilesPanel.js";
|
|
22
|
+
import { isQaModalOpen } from "./components/QaModal.js";
|
|
18
23
|
import { QaRightRail } from "./components/QaRightRail.js";
|
|
19
24
|
import { QaSourcesPanel } from "./components/QaSourcesPanel.js";
|
|
25
|
+
import { QA_TURN_NOTICE_LINE_SELECTOR, QA_TURN_NOTICE_OFFER_SELECTOR, QaTurnNotice, } from "./components/QaTurnNotice.js";
|
|
26
|
+
import { useQaTurnNotifications } from "./notifications/use-turn-notifications.js";
|
|
20
27
|
import { QA_TURN_FOLLOW_PX, QaTurnRail, computeActiveTurn, scrollToTranscriptAnchor, } from "./components/QaTurnRail.js";
|
|
21
28
|
import { QaWidthHandle, useQaContentWidth, } from "./components/QaWidthHandle.js";
|
|
22
29
|
import { VariantSwitcher } from "./components/VariantSwitcher.js";
|
|
23
30
|
import { QaWelcomeNotice } from "./components/QaWelcomeNotice.js";
|
|
24
31
|
import { statusText, titleFromMessages } from "./components/surface-utils.js";
|
|
25
32
|
import { useThinkingPhrase } from "./components/thinking-phrases.js";
|
|
33
|
+
import { focusFirst, focusRing, focusable, isInert, trapKeys, } from "./focus-ring.js";
|
|
26
34
|
import { QaUserSettingsDialog, } from "./user-settings/UserSettingsDialog.js";
|
|
27
35
|
import { useTranscriptView } from "./use-transcript-view.js";
|
|
28
36
|
import { useSessionUiState } from "./use-session-ui-state.js";
|
|
@@ -51,37 +59,42 @@ const QA_SURFACE_CLASS = qaKioskMode()
|
|
|
51
59
|
const NO_QUESTIONS = Object.freeze([]);
|
|
52
60
|
const ACCOUNTS_CHECKING_SNAPSHOT = { stage: "checking" };
|
|
53
61
|
const noopAccountsSnapshot = () => ACCOUNTS_CHECKING_SNAPSHOT;
|
|
54
|
-
function focusable(root) {
|
|
55
|
-
return [
|
|
56
|
-
...root.querySelectorAll("button:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex='-1'])"),
|
|
57
|
-
].filter((element) => !element.hidden);
|
|
58
|
-
}
|
|
59
|
-
function trapKeys(event) {
|
|
60
|
-
event.stopPropagation();
|
|
61
|
-
if (event.key !== "Tab")
|
|
62
|
-
return;
|
|
63
|
-
const items = focusable(event.currentTarget);
|
|
64
|
-
if (items.length === 0) {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
event.currentTarget.focus();
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const first = items[0];
|
|
70
|
-
const last = items.at(-1);
|
|
71
|
-
if (event.shiftKey && document.activeElement === first) {
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
last?.focus();
|
|
74
|
-
}
|
|
75
|
-
else if (!event.shiftKey && document.activeElement === last) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
first?.focus();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
62
|
/** Same follow threshold the rail uses: this close to the floor is "at bottom". */
|
|
81
63
|
function isNearBottom(element) {
|
|
82
64
|
return (element.scrollHeight - element.scrollTop - element.clientHeight <
|
|
83
65
|
QA_TURN_FOLLOW_PX);
|
|
84
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The stack line the reader's control stands on, or null when the ring has no
|
|
69
|
+
* place to keep for it. The desktop opt-in is not a line: it is what the stack
|
|
70
|
+
* ends with, so losing it is losing the last control of the last line, and that
|
|
71
|
+
* line is taken as its place. The opt-in is read by its own block rather than as
|
|
72
|
+
* whatever the stack holds that is not a line — the next block the stack grows
|
|
73
|
+
* (a heading, a «скрыть все») is neither, and handing the reader to a line they
|
|
74
|
+
* never stood on is worse than handing them nowhere.
|
|
75
|
+
*/
|
|
76
|
+
function noticeLineOf(target, stack) {
|
|
77
|
+
const line = target.closest(QA_TURN_NOTICE_LINE_SELECTOR);
|
|
78
|
+
if (line !== null)
|
|
79
|
+
return line;
|
|
80
|
+
if (target.closest(QA_TURN_NOTICE_OFFER_SELECTOR) === null) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const lines = stack.querySelectorAll(QA_TURN_NOTICE_LINE_SELECTOR);
|
|
84
|
+
return lines.item(lines.length - 1);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The line the stack lists on this side of `line`, or null when there is none:
|
|
88
|
+
* the opt-in block shares the stack with the lines but is not one of them, so it
|
|
89
|
+
* is not a place a reader can be handed back to.
|
|
90
|
+
*/
|
|
91
|
+
function adjacentNoticeLine(line, side) {
|
|
92
|
+
const sibling = side === "previous" ? line.previousElementSibling : line.nextElementSibling;
|
|
93
|
+
return sibling instanceof HTMLElement &&
|
|
94
|
+
sibling.matches(QA_TURN_NOTICE_LINE_SELECTOR)
|
|
95
|
+
? sibling
|
|
96
|
+
: null;
|
|
97
|
+
}
|
|
85
98
|
export function QaSurface(props) {
|
|
86
99
|
const route = useSyncExternalStore(props.route.subscribe, props.route.getSnapshot, props.route.getSnapshot);
|
|
87
100
|
const configState = useSyncExternalStore(props.config.subscribe, props.config.getSnapshot, props.config.getSnapshot);
|
|
@@ -106,10 +119,113 @@ export function QaSurface(props) {
|
|
|
106
119
|
const transcript = useRef(null);
|
|
107
120
|
const chat = useRef(null);
|
|
108
121
|
const nearBottom = useRef(true);
|
|
122
|
+
const surface = useRef(null);
|
|
123
|
+
/** The notice stack: after `<main>` in the page, inside the ring with it. */
|
|
124
|
+
const noticeStack = useRef(null);
|
|
125
|
+
/**
|
|
126
|
+
* Whether a dialog, rather than this ring, owns the keyboard: the onboarding
|
|
127
|
+
* gate holds the page inert, and a `QaModal` stands over it with the Escape of
|
|
128
|
+
* its own. Neither paints the notice stack away — a dialog is a neighbour of
|
|
129
|
+
* it, not an ancestor — so the ring has to step back of its own accord, and
|
|
130
|
+
* step back to the subtree it counted before the stack was ever part of it.
|
|
131
|
+
*/
|
|
132
|
+
const dialogOwnsKeyboard = useCallback(() => {
|
|
133
|
+
const root = surface.current;
|
|
134
|
+
return isQaModalOpen() || (root !== null && isInert(root));
|
|
135
|
+
}, []);
|
|
136
|
+
/** The roots the Tab ring is drawn around, in the order the ring walks them. */
|
|
137
|
+
const ringRoots = useCallback(() => dialogOwnsKeyboard()
|
|
138
|
+
? [surface.current]
|
|
139
|
+
: [surface.current, noticeStack.current], [dialogOwnsKeyboard]);
|
|
140
|
+
/**
|
|
141
|
+
* Where the keyboard last stood in the notice stack: the control, the line it
|
|
142
|
+
* stood on, the lines the stack listed beside it, and the control's step
|
|
143
|
+
* within the line. The stack alone is remembered: a control of `<main>` that
|
|
144
|
+
* leaves the page with its row — a queue dock entry, a transcript action, a
|
|
145
|
+
* rebuilt chat — is the surface's own business, and this ring hands nothing
|
|
146
|
+
* back there. Read as focus moves, so it names where the reader stands rather
|
|
147
|
+
* than where they once stood: a focus landing anywhere else lets the place go.
|
|
148
|
+
*
|
|
149
|
+
* The place is held as lines, not as a number counted along the ring, because
|
|
150
|
+
* a counted place is not a stable coordinate here. A finished turn is put in
|
|
151
|
+
* front of the lines the reader already sees (`use-turn-notifications.ts`), so
|
|
152
|
+
* a step counted from the front of the stack slides by as many controls as the
|
|
153
|
+
* batch brought, and `<main>`, which the ring counts first, grows and shrinks
|
|
154
|
+
* for reasons of its own. The line the reader stood on and its neighbours keep
|
|
155
|
+
* the one claim the restore has to make — hand the keyboard to the line that
|
|
156
|
+
* took their place — whatever else arrived or left in the same moment.
|
|
157
|
+
*/
|
|
158
|
+
const ringAnchor = useRef(null);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const onFocusIn = (event) => {
|
|
161
|
+
const target = event.target;
|
|
162
|
+
const stack = noticeStack.current;
|
|
163
|
+
if (!(target instanceof HTMLElement) ||
|
|
164
|
+
stack === null ||
|
|
165
|
+
!stack.contains(target)) {
|
|
166
|
+
ringAnchor.current = null;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const line = noticeLineOf(target, stack);
|
|
170
|
+
if (line === null) {
|
|
171
|
+
ringAnchor.current = null;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const controls = focusable(line);
|
|
175
|
+
const step = controls.indexOf(target);
|
|
176
|
+
ringAnchor.current = {
|
|
177
|
+
element: target,
|
|
178
|
+
line,
|
|
179
|
+
// A control outside every line is the desktop opt-in, and the place it
|
|
180
|
+
// stands for is the front of the line the stack keeps last. Not its end:
|
|
181
|
+
// the last control is the cross, which answers the Enter the reader gave
|
|
182
|
+
// the offer by waving a notification they never aimed at.
|
|
183
|
+
step: step < 0 ? 0 : step,
|
|
184
|
+
before: adjacentNoticeLine(line, "previous"),
|
|
185
|
+
after: adjacentNoticeLine(line, "next"),
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
document.addEventListener("focusin", onFocusIn);
|
|
189
|
+
return () => document.removeEventListener("focusin", onFocusIn);
|
|
190
|
+
}, []);
|
|
191
|
+
/**
|
|
192
|
+
* Keys typed in the surface: Tab stays in the ring, and nothing else of the
|
|
193
|
+
* key reaches the harness the overlay is built on.
|
|
194
|
+
*/
|
|
195
|
+
const trapSurfaceKeys = useCallback((event) => {
|
|
196
|
+
trapKeys(event, ringRoots());
|
|
197
|
+
}, [ringRoots]);
|
|
198
|
+
/**
|
|
199
|
+
* Keys typed in the notice stack, where no ancestor `<main>` can hear them.
|
|
200
|
+
*
|
|
201
|
+
* Only the Tab this ring answers is taken; every other key is left to bubble
|
|
202
|
+
* as the browser would carry it, so a dialog that listens on the window —
|
|
203
|
+
* `QaModal`, and the settings dialog built on it — still hears the Escape the
|
|
204
|
+
* reader gives it. A dialog is also why that Tab is left alone: the stack is
|
|
205
|
+
* the dialog's neighbour rather than its content, so a ring closed around both
|
|
206
|
+
* would take the reader out of the dialog they are working in and put them
|
|
207
|
+
* back on a page held under the scrim.
|
|
208
|
+
*/
|
|
209
|
+
const trapNoticeKeys = useCallback((event) => {
|
|
210
|
+
if (event.key !== "Tab" || dialogOwnsKeyboard())
|
|
211
|
+
return;
|
|
212
|
+
trapKeys(event, ringRoots());
|
|
213
|
+
}, [dialogOwnsKeyboard, ringRoots]);
|
|
109
214
|
/** Turn marks of the visible transcript, kept in a ref for stable callbacks. */
|
|
110
215
|
const railItemsRef = useRef([]);
|
|
111
216
|
const activeTurnFrame = useRef(null);
|
|
112
217
|
const stateKey = qaStorageNamespace(config);
|
|
218
|
+
// The palette belongs to the surface, so it is only painted while the
|
|
219
|
+
// surface is what the visitor is looking at: outside its own route the
|
|
220
|
+
// overlay is mounted and invisible, and flipping the Host document's
|
|
221
|
+
// attributes from there would be the QA stand restyling the harness. What the
|
|
222
|
+
// surface borrowed for the visit it hands back on the way out, for the same
|
|
223
|
+
// reason — see `useQaThemePreference`.
|
|
224
|
+
const theme = useQaThemePreference({
|
|
225
|
+
active: route.active,
|
|
226
|
+
storage: window.localStorage,
|
|
227
|
+
storageKey: `${stateKey}:theme`,
|
|
228
|
+
});
|
|
113
229
|
const isAdmin = accountsSnapshot.stage === "authed" &&
|
|
114
230
|
accountsSnapshot.user.role === "admin";
|
|
115
231
|
// The preview belongs to the history entry that asked for it, so the
|
|
@@ -214,6 +330,9 @@ export function QaSurface(props) {
|
|
|
214
330
|
? {}
|
|
215
331
|
: { questionApi: props.questionApi }),
|
|
216
332
|
...(props.slashApi === undefined ? {} : { slashApi: props.slashApi }),
|
|
333
|
+
...(props.queueStatus === undefined
|
|
334
|
+
? {}
|
|
335
|
+
: { queueStatus: props.queueStatus }),
|
|
217
336
|
config,
|
|
218
337
|
initialSubrole: selectedSubrole,
|
|
219
338
|
adminPreview: previewing,
|
|
@@ -240,6 +359,7 @@ export function QaSurface(props) {
|
|
|
240
359
|
props.fileUpload,
|
|
241
360
|
props.approvalApi,
|
|
242
361
|
props.questionApi,
|
|
362
|
+
props.queueStatus,
|
|
243
363
|
props.secureSession,
|
|
244
364
|
props.sourceApi,
|
|
245
365
|
props.sessions,
|
|
@@ -327,9 +447,9 @@ export function QaSurface(props) {
|
|
|
327
447
|
]);
|
|
328
448
|
const listState = useSyncExternalStore(props.sessions.list.subscribe, props.sessions.list.getSnapshot, props.sessions.list.getSnapshot);
|
|
329
449
|
// Draft text, attachments, variant offsets and drawers are chat-local; the
|
|
330
|
-
// hook clears them whenever the
|
|
331
|
-
//
|
|
332
|
-
const ui = useSessionUiState(state.
|
|
450
|
+
// hook clears them whenever the chat changes. The rail controller takes the
|
|
451
|
+
// drawer slice; the surface keeps the composer and marks state.
|
|
452
|
+
const ui = useSessionUiState(state.chatKey);
|
|
333
453
|
const rail = useRightRail(ui);
|
|
334
454
|
const { activeTurn, setActiveTurn, variantOffsets, setVariantOffsets, agentsOpen, setAgentsOpen, pendingAttachments, setPendingAttachments, } = ui;
|
|
335
455
|
useEffect(() => {
|
|
@@ -361,6 +481,15 @@ export function QaSurface(props) {
|
|
|
361
481
|
const handleNewChat = useCallback(() => {
|
|
362
482
|
void controller?.startDraft();
|
|
363
483
|
}, [controller]);
|
|
484
|
+
// The phone layout's history drawer. Its state is the surface's, not the
|
|
485
|
+
// sidebar's, because the control that opens it stands in the header: the narrow
|
|
486
|
+
// sheet switches the whole sidebar subtree off, and an opener inside it is no
|
|
487
|
+
// opener at all. The sidebar keeps its own collapsed rail, which is a
|
|
488
|
+
// wide-layout choice and a different thing — a phone that opens the drawer gets
|
|
489
|
+
// the list whatever that rail remembers.
|
|
490
|
+
const [historyOpen, setHistoryOpen] = useState(false);
|
|
491
|
+
const toggleHistory = useCallback(() => setHistoryOpen((open) => !open), []);
|
|
492
|
+
const closeHistory = useCallback(() => setHistoryOpen(false), []);
|
|
364
493
|
// The audit dialog's target, not its state: opening it is a view choice, and
|
|
365
494
|
// closing it must not disturb the chat underneath.
|
|
366
495
|
const [auditTarget, setAuditTarget] = useState(null);
|
|
@@ -372,6 +501,10 @@ export function QaSurface(props) {
|
|
|
372
501
|
void controller?.deleteChat(sessionId);
|
|
373
502
|
}, [controller]);
|
|
374
503
|
const handleSend = useCallback((text, attachments, pick) => controller?.send(text, attachments, pick) ?? Promise.resolve(false), [controller]);
|
|
504
|
+
// Closing the queue notice only uncovers the composer: the question the stand
|
|
505
|
+
// had no room for is still the text in there, and asking it again is a
|
|
506
|
+
// keystroke the visitor takes when a place frees up.
|
|
507
|
+
const dismissRequestQueue = useCallback(() => controller?.dismissRequestQueueNotice(), [controller]);
|
|
375
508
|
// Cheap refresh on every palette opening: the skill registry has no browser
|
|
376
509
|
// change event, and the controller skips the round-trip while its answer is
|
|
377
510
|
// still fresh.
|
|
@@ -379,6 +512,9 @@ export function QaSurface(props) {
|
|
|
379
512
|
void controller?.refreshSlashCatalog();
|
|
380
513
|
}, [controller]);
|
|
381
514
|
const handleStop = useCallback(() => controller?.stop() ?? Promise.resolve(), [controller]);
|
|
515
|
+
const handleQueueEdit = useCallback((id, text) => controller?.queueAction(id, "edit", text) ?? Promise.resolve(null), [controller]);
|
|
516
|
+
const handleQueueSendNow = useCallback((id) => controller?.queueAction(id, "steer") ?? Promise.resolve(null), [controller]);
|
|
517
|
+
const handleQueueRemove = useCallback((id) => controller?.queueAction(id, "remove") ?? Promise.resolve(null), [controller]);
|
|
382
518
|
const handleAnswerApproval = useCallback((requestId, decision) => controller?.answerApproval(requestId, decision) ?? Promise.resolve(), [controller]);
|
|
383
519
|
const handleAnswerQuestion = useCallback((requestId, answers) => controller?.answerQuestion(requestId, answers) ?? Promise.resolve(), [controller]);
|
|
384
520
|
const handleCancelQuestion = useCallback((requestId) => controller?.cancelQuestion(requestId) ?? Promise.resolve(), [controller]);
|
|
@@ -540,15 +676,122 @@ export function QaSurface(props) {
|
|
|
540
676
|
onSave: (input) => accounts.updateStarters(input),
|
|
541
677
|
}
|
|
542
678
|
: undefined;
|
|
679
|
+
// One object serves both halves of the same decision: the form that edits
|
|
680
|
+
// it and the notices that obey it. A reader cannot be looking at one and
|
|
681
|
+
// waiting on the other, and a memo keeps the identity stable for the
|
|
682
|
+
// watcher that reads the channels out of it.
|
|
683
|
+
const notifications = {
|
|
684
|
+
notifications: accountsSnapshot.user.notifications,
|
|
685
|
+
switches: config.notifications,
|
|
686
|
+
onSave: (input) => accounts.updateNotifications(input),
|
|
687
|
+
};
|
|
543
688
|
const skills = config.accounts.skills.enabled && boundSkillApi !== undefined
|
|
544
689
|
? boundSkillApi
|
|
545
690
|
: undefined;
|
|
546
|
-
return {
|
|
691
|
+
return {
|
|
692
|
+
profile,
|
|
693
|
+
password,
|
|
694
|
+
starters,
|
|
695
|
+
notifications,
|
|
696
|
+
skills,
|
|
697
|
+
integrationTokens,
|
|
698
|
+
};
|
|
547
699
|
}, [accounts, accountsSnapshot, config, boundSkillApi, integrationTokens]);
|
|
548
700
|
const busyTurn = state.phase === "running" ? (railItems.at(-1)?.turn ?? null) : null;
|
|
549
701
|
// Projected even while the sidebar is hidden: the account settings report
|
|
550
702
|
// the same chat count, and one projection cannot disagree with itself.
|
|
551
703
|
const chatRows = useMemo(() => buildChatRows(controller?.chatIds() ?? [], listState.byId, activeSessionId, (id) => ownerNames?.get(id)), [controller, listState, activeSessionId, ownerNames, state.chatsRevision]);
|
|
704
|
+
// A turn that ends in one of this browser's own chats gets a notice: the
|
|
705
|
+
// sidebar dot going out is otherwise the only sign, and it is easy to miss
|
|
706
|
+
// from behind another window. The account's own list, not the sidebar's, is
|
|
707
|
+
// what bounds it: an admin's shared history is a wider read than ownership,
|
|
708
|
+
// and another account's turn is not this reader's news.
|
|
709
|
+
const turnNotices = useQaTurnNotifications({
|
|
710
|
+
chats: chatRows,
|
|
711
|
+
ownChatIds: accountsSnapshot.stage === "authed" ? accountsSnapshot.ownIds : undefined,
|
|
712
|
+
notifications: config.notifications,
|
|
713
|
+
storage: window.localStorage,
|
|
714
|
+
storageKey: `${stateKey}:notifications`,
|
|
715
|
+
// Signed in, the account's own channels decide and the settings form is
|
|
716
|
+
// what writes them; anonymously, the hook falls back to this browser.
|
|
717
|
+
account: settingsDialog?.notifications,
|
|
718
|
+
paused: state.phase === "reconnecting",
|
|
719
|
+
activeSessionId,
|
|
720
|
+
onSwitch: handleSwitch,
|
|
721
|
+
});
|
|
722
|
+
/**
|
|
723
|
+
* A line leaving the stack takes the control the reader was working out of the
|
|
724
|
+
* page, and a page with nothing focused answers Tab with the browser's own
|
|
725
|
+
* order — which leaves the QA interface. Most of the ways a line goes leave
|
|
726
|
+
* the stack standing and its count of lines where it was: waving one of the
|
|
727
|
+
* three off, a fourth turn pushing the oldest out of a full stack, the opt-in
|
|
728
|
+
* going away once the browser has answered the permission question. So the
|
|
729
|
+
* loss is read where it happens — the control the ring held is out of the page
|
|
730
|
+
* and nothing of the ring holds the focus — and the reader is handed to the
|
|
731
|
+
* line that took the lost one's place, on the same control of it: a cross
|
|
732
|
+
* given back as a cross. The anchor goes only with the focus it hands back: a
|
|
733
|
+
* page takes the focus out of a removed control on its own moment, not in step
|
|
734
|
+
* with this render. A dialog that holds the keyboard is not the ring's
|
|
735
|
+
* business, and is left to put the focus wherever it thinks the reader
|
|
736
|
+
* belongs.
|
|
737
|
+
*
|
|
738
|
+
* A page the reader is not looking at keeps its keyboard where it is: while
|
|
739
|
+
* `document.hasFocus()` is false nothing here is moved. The hand-off is owed
|
|
740
|
+
* rather than skipped — the anchor stands until the window comes back, and the
|
|
741
|
+
* `focus` event of this page is what pays it — because the reader who returns
|
|
742
|
+
* to a stack that dropped a line under them is exactly the reader this place
|
|
743
|
+
* was remembered for, while a control of a page no one is looking at has no
|
|
744
|
+
* claim on the keyboard of the page they are working in.
|
|
745
|
+
*
|
|
746
|
+
* Opening a chat from a line is the one path where a line goes and the chat
|
|
747
|
+
* changes in the same moment. The keyboard stays on the stack — on a
|
|
748
|
+
* neighbouring line, or back inside `<main>` once none is left — rather than
|
|
749
|
+
* following the switch: the composer the switch remounts is disabled while the
|
|
750
|
+
* chat is still being bound, so it can take no focus, and the surface moves
|
|
751
|
+
* the keyboard on a switch no further than it does anywhere else.
|
|
752
|
+
*/
|
|
753
|
+
const restoreRingFocus = useCallback(() => {
|
|
754
|
+
const anchor = ringAnchor.current;
|
|
755
|
+
if (anchor === null || anchor.element.isConnected)
|
|
756
|
+
return;
|
|
757
|
+
if (dialogOwnsKeyboard()) {
|
|
758
|
+
ringAnchor.current = null;
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (!document.hasFocus())
|
|
762
|
+
return;
|
|
763
|
+
const ring = focusRing(ringRoots());
|
|
764
|
+
const active = document.activeElement;
|
|
765
|
+
if (active !== null && ring.includes(active))
|
|
766
|
+
return;
|
|
767
|
+
ringAnchor.current = null;
|
|
768
|
+
// The line itself first, for a control that left while its line stayed:
|
|
769
|
+
// then whichever neighbour is still on screen, the one after the reader
|
|
770
|
+
// taking the place the stack still lists in that order.
|
|
771
|
+
const candidates = [];
|
|
772
|
+
for (const line of [anchor.line, anchor.after, anchor.before]) {
|
|
773
|
+
if (line === null || !line.isConnected)
|
|
774
|
+
continue;
|
|
775
|
+
const controls = focusable(line);
|
|
776
|
+
candidates.push(controls[Math.min(anchor.step, controls.length - 1)]);
|
|
777
|
+
}
|
|
778
|
+
// The stack has no line left to stand on: back into the interface, onto the
|
|
779
|
+
// control the ring reaches last, and past the first candidate of it that
|
|
780
|
+
// refuses the keyboard.
|
|
781
|
+
candidates.push(...[...ring].reverse(), surface.current ?? undefined);
|
|
782
|
+
focusFirst(candidates);
|
|
783
|
+
}, [dialogOwnsKeyboard, ringRoots]);
|
|
784
|
+
useEffect(() => {
|
|
785
|
+
restoreRingFocus();
|
|
786
|
+
});
|
|
787
|
+
useEffect(() => {
|
|
788
|
+
// The hand-off a blurred page owed, read again at the moment the reader
|
|
789
|
+
// comes back: whoever holds the keyboard then keeps it, and a control of the
|
|
790
|
+
// ring they reached on their own is not taken away from them.
|
|
791
|
+
const onWindowFocus = () => restoreRingFocus();
|
|
792
|
+
window.addEventListener("focus", onWindowFocus);
|
|
793
|
+
return () => window.removeEventListener("focus", onWindowFocus);
|
|
794
|
+
}, [restoreRingFocus]);
|
|
552
795
|
// The audit provider is optional: `auditSnapshot.api` is null until the
|
|
553
796
|
// audit plugin's client bundle is loaded, and the badge is absent until
|
|
554
797
|
// then. Chat ids rather than rows feed the poll, so a re-render does not
|
|
@@ -594,7 +837,7 @@ export function QaSurface(props) {
|
|
|
594
837
|
const welcomeNotice = (_jsx(QaWelcomeNotice, { storage: window.localStorage, storageKey: `${stateKey}:welcome-notice` }, stateKey));
|
|
595
838
|
if (config.accounts.enabled && accounts !== undefined) {
|
|
596
839
|
if (accountsStage === "checking") {
|
|
597
|
-
return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx("main", { className: QA_SURFACE_CLASS, "aria-busy": "true", "aria-label": config.branding.title, tabIndex: -1 })] }));
|
|
840
|
+
return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx("main", { className: QA_SURFACE_CLASS, "data-testid": "qa-surface-loading", "aria-busy": "true", "aria-label": config.branding.title, tabIndex: -1 })] }));
|
|
598
841
|
}
|
|
599
842
|
if (accountsStage === "gate") {
|
|
600
843
|
return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaAuthGate, { accounts: accounts, snapshot: accountsSnapshot, title: config.branding.title, logoUrl: config.branding.logoUrl, allowRegistration: config.accounts.allowRegistration })] }));
|
|
@@ -650,9 +893,11 @@ export function QaSurface(props) {
|
|
|
650
893
|
} }));
|
|
651
894
|
}
|
|
652
895
|
if (adminRoute) {
|
|
653
|
-
return (_jsx("main", { className: "dsh-qa-admin", "aria-label": "\u0410\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 QA", children: _jsxs("div", { className: "dsh-qa-admin__loading", children: [_jsx("p", { children: "\u042D\u0442\u043E\u0442 \u0440\u0430\u0437\u0434\u0435\u043B \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0443." }), _jsx("button", { type: "button", onClick: () => window.history.pushState(null, "", config.route.path), children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u0447\u0430\u0442" })] }) }));
|
|
896
|
+
return (_jsx("main", { className: "dsh-qa-admin", "data-testid": "qa-surface-admin-denied", "aria-label": "\u0410\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 QA", children: _jsxs("div", { className: "dsh-qa-admin__loading", children: [_jsx("p", { children: "\u042D\u0442\u043E\u0442 \u0440\u0430\u0437\u0434\u0435\u043B \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0443." }), _jsx("button", { type: "button", "data-testid": "qa-surface-admin-return", onClick: () => window.history.pushState(null, "", config.route.path), children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u0447\u0430\u0442" })] }) }));
|
|
654
897
|
}
|
|
655
|
-
return (_jsxs(_Fragment, { children: [welcomeNotice,
|
|
898
|
+
return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaTurnNotice, { items: turnNotices.items, onOpen: turnNotices.onOpen, onDismiss: turnNotices.onDismiss, rootRef: noticeStack, onKeyDown: trapNoticeKeys, ...(turnNotices.onEnableDesktop === undefined
|
|
899
|
+
? {}
|
|
900
|
+
: { onEnableDesktop: turnNotices.onEnableDesktop }) }), settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
|
|
656
901
|
? "profile"
|
|
657
902
|
: "general"), email: accountsSnapshot.stage === "authed"
|
|
658
903
|
? accountsSnapshot.user.email
|
|
@@ -664,15 +909,15 @@ export function QaSurface(props) {
|
|
|
664
909
|
? {}
|
|
665
910
|
: { password: settingsDialog.password }), ...(settingsDialog.starters === undefined
|
|
666
911
|
? {}
|
|
667
|
-
: { starters: settingsDialog.starters }), ...(settingsDialog.integrationTokens === undefined
|
|
912
|
+
: { starters: settingsDialog.starters }), notifications: settingsDialog.notifications, ...(settingsDialog.integrationTokens === undefined
|
|
668
913
|
? {}
|
|
669
914
|
: { integrationTokens: settingsDialog.integrationTokens }), ...(settingsDialog.skills === undefined
|
|
670
915
|
? {}
|
|
671
|
-
: { skills: settingsDialog.skills }) })), _jsxs("main", { className: QA_SURFACE_CLASS, "data-phase": state.phase, "aria-label": config.branding.title, tabIndex: -1, onKeyDown:
|
|
916
|
+
: { skills: settingsDialog.skills }) })), _jsxs("main", { ref: surface, className: QA_SURFACE_CLASS, "data-testid": "qa-surface-root", "data-phase": state.phase, "aria-label": config.branding.title, tabIndex: -1, onKeyDown: trapSurfaceKeys, children: [previewing || sessionPreview ? (_jsx(QaAdminPreviewBanner, { role: preview?.name ??
|
|
672
917
|
sessionRole?.name ??
|
|
673
918
|
sessionRole?.id ??
|
|
674
919
|
selectedSubrole ??
|
|
675
|
-
"", ...(previewing ? { onExit: exitPreview } : {}) })) : null, showSidebar ? (_jsx(QaSidebar, { rows: chatRows, groupByOwner: ownerNames !== undefined, title: config.branding.title, logoUrl: config.branding.logoUrl, stateKey: stateKey, showNewChat: allowNewChat, busy: state.phase === "creating", onSwitch: handleSwitch, onNewChat: handleNewChat, onDelete: handleDelete, audits: chatAudits, ...(auditSnapshot.api === null ? {} : { onAudit: openAudit }), account: config.accounts.enabled &&
|
|
920
|
+
"", ...(previewing ? { onExit: exitPreview } : {}), disabled: state.phase === "creating" })) : null, showSidebar && historyOpen ? (_jsx("div", { className: "dsh-qa-sidebar__scrim", "data-testid": "qa-surface-sidebar-scrim", "aria-hidden": "true", onClick: closeHistory })) : null, showSidebar ? (_jsx(QaSidebar, { rows: chatRows, groupByOwner: ownerNames !== undefined, title: config.branding.title, logoUrl: config.branding.logoUrl, stateKey: stateKey, showNewChat: allowNewChat, busy: state.phase === "creating", drawerOpen: historyOpen, onDrawerClose: closeHistory, onSwitch: handleSwitch, onNewChat: handleNewChat, onDelete: handleDelete, audits: chatAudits, ...(auditSnapshot.api === null ? {} : { onAudit: openAudit }), account: config.accounts.enabled &&
|
|
676
921
|
accounts !== undefined &&
|
|
677
922
|
accountsStage === "authed"
|
|
678
923
|
? {
|
|
@@ -683,7 +928,7 @@ export function QaSurface(props) {
|
|
|
683
928
|
? {}
|
|
684
929
|
: { settings: settingsEntry }),
|
|
685
930
|
}
|
|
686
|
-
: undefined })) : null, _jsx(QaAuditDialog, { open: auditTarget !== null, sessionId: auditTarget, title: "\u0410\u0443\u0434\u0438\u0442 \u0447\u0430\u0442\u0430", api: auditSnapshot.api, summary: auditTarget === null ? undefined : chatAudits.get(auditTarget), onClose: closeAudit }), _jsxs("div", { className: "dsh-qa-body", children: [state.viewingSubagent !== null && !config.ui.showHeader ? (_jsx(QaSubagentBanner, { onClose: handleCloseSubagent })) : null, config.ui.showHeader ? (_jsx(QaHeader, { logoUrl: config.branding.logoUrl, title: conversationTitle, viewingSubagent: state.viewingSubagent !== null, onCloseSubagent: handleCloseSubagent, roleSelector: access === undefined ||
|
|
931
|
+
: undefined })) : null, _jsx(QaAuditDialog, { open: auditTarget !== null, sessionId: auditTarget, title: "\u0410\u0443\u0434\u0438\u0442 \u0447\u0430\u0442\u0430", api: auditSnapshot.api, summary: auditTarget === null ? undefined : chatAudits.get(auditTarget), onClose: closeAudit }), _jsxs("div", { className: "dsh-qa-body", "data-testid": "qa-surface-body", children: [state.viewingSubagent !== null && !config.ui.showHeader ? (_jsx(QaSubagentBanner, { onClose: handleCloseSubagent })) : null, config.ui.showHeader ? (_jsx(QaHeader, { logoUrl: config.branding.logoUrl, title: conversationTitle, viewingSubagent: state.viewingSubagent !== null, onCloseSubagent: handleCloseSubagent, roleSelector: access === undefined ||
|
|
687
932
|
selectedSubrole === null ||
|
|
688
933
|
previewing ? undefined : (_jsx(QaRoleSelector, { roles: access.subroles, selected: selectedSubrole, conversationStarted: !empty, disabled: config.session.policy === "fixed" ||
|
|
689
934
|
state.phase === "creating" ||
|
|
@@ -694,21 +939,26 @@ export function QaSurface(props) {
|
|
|
694
939
|
setSelectedSubrole(id);
|
|
695
940
|
setSessionRole(access.subroles.find((role) => role.id === id));
|
|
696
941
|
void controller?.selectSubrole(id, false);
|
|
697
|
-
} })), administration: accountsSnapshot.stage === "authed" &&
|
|
942
|
+
} })), themeSwitcher: _jsx(QaThemeSwitcher, { preference: theme.preference, onSelect: theme.select }), administration: accountsSnapshot.stage === "authed" &&
|
|
698
943
|
accountsSnapshot.user.role === "admin"
|
|
699
944
|
? {
|
|
700
945
|
onOpen: () => window.history.pushState(null, "", `${config.route.path === "/" ? "" : config.route.path}/admin`),
|
|
701
946
|
}
|
|
702
947
|
: undefined, agentCount: agentRows.length, agentsOpen: agentsOpen, onToggleAgents: rail.toggleAgents, sourcesVisible: config.sources.enabled && config.sources.display.sidebar, sourcesCount: state.sources.length, sourcesComplete: state.sourcesComplete, sourcesOpen: rail.railOpen && rail.railTab === "sources", onOpenSources: () => rail.openTab("sources"), fileCount: attachmentCount, filesEnabled: filesEnabled, filesOpen: rail.railOpen && rail.railTab === "files", onOpenFiles: () => rail.openTab("files"), panelLauncher: _jsx(QaPanelLauncher, { panels: props.panels }),
|
|
948
|
+
// The narrow layout switches the sidebar off, so the history is
|
|
949
|
+
// opened from here — and only where there is a sidebar to open.
|
|
950
|
+
history: showSidebar
|
|
951
|
+
? { open: historyOpen, onToggle: toggleHistory }
|
|
952
|
+
: undefined,
|
|
703
953
|
// The sidebar carries this entry next to the account name, so
|
|
704
954
|
// the header takes it over exactly when there is no sidebar.
|
|
705
|
-
settings: showSidebar ? undefined : settingsEntry, showReset: showResetButton, resetDisabled: controller === undefined || state.phase === "creating", onReset: handleNewChat })) : null, _jsxs("div", { className: "dsh-qa-workspace", children: [_jsxs("div", { ref: chat, className: "dsh-qa-chat", style: {
|
|
955
|
+
settings: showSidebar ? undefined : settingsEntry, showReset: showResetButton, resetDisabled: controller === undefined || state.phase === "creating", onReset: handleNewChat })) : null, _jsxs("div", { className: "dsh-qa-workspace", "data-testid": "qa-surface-workspace-split", children: [_jsxs("div", { ref: chat, className: "dsh-qa-chat", "data-testid": "qa-surface-chat", style: {
|
|
706
956
|
"--dsh-qa-content-width": `${config.ui.minContentWidth}px`,
|
|
707
|
-
}, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", onScroll: (event) => {
|
|
957
|
+
}, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", "data-testid": "qa-surface-transcript", onScroll: (event) => {
|
|
708
958
|
const element = event.currentTarget;
|
|
709
959
|
nearBottom.current = isNearBottom(element);
|
|
710
960
|
scheduleActiveTurnSync();
|
|
711
|
-
}, children: [empty ? null : (_jsx(QaTurnRail, { items: railItems, activeTurn: activeTurn, busyTurn: busyTurn, scrollerRef: transcript, onNavigate: handleTurnNavigate })), _jsxs("div", { className: "dsh-qa-transcript__inner", children: [empty ? (_jsxs("section", { className: "dsh-qa-welcome", "aria-labelledby": "dsh-qa-welcome-title", children: [_jsx("h2", { id: "dsh-qa-welcome-title", children: config.branding.welcomeMessage }), config.branding.subtitle === "" ? null : (_jsx("p", { children: config.branding.subtitle }))] })) : (visibleMessages.map((message, index) => {
|
|
961
|
+
}, children: [empty ? null : (_jsx(QaTurnRail, { items: railItems, activeTurn: activeTurn, busyTurn: busyTurn, scrollerRef: transcript, onNavigate: handleTurnNavigate })), _jsxs("div", { className: "dsh-qa-transcript__inner", children: [empty ? (_jsxs("section", { className: "dsh-qa-welcome", "data-testid": "qa-surface-transcript-welcome", "aria-labelledby": "dsh-qa-welcome-title", children: [_jsx("h2", { id: "dsh-qa-welcome-title", children: config.branding.welcomeMessage }), config.branding.subtitle === "" ? null : (_jsx("p", { children: config.branding.subtitle }))] })) : (visibleMessages.map((message, index) => {
|
|
712
962
|
const group = message.role === "user"
|
|
713
963
|
? view.groupByPromptId.get(message.id)
|
|
714
964
|
: undefined;
|
|
@@ -727,8 +977,8 @@ export function QaSurface(props) {
|
|
|
727
977
|
...offsets,
|
|
728
978
|
[group.groupId]: step,
|
|
729
979
|
})) })) : null] }, message.id));
|
|
730
|
-
})), state.pendingMessage === null ? null : (_jsx("div", { className: "dsh-qa-message-slot", children: _jsx(QaMessage, { message: state.pendingMessage, renderMarkdown: false, showTimestamp: false, thinkingPhrases: config.thinkingPhrases }) })), state.compatibilityReadOnly === true ? (_jsx("div", { className: "dsh-qa-compatibility", role: "status", children: "\u042D\u0442\u043E\u0442 \u0447\u0430\u0442 \u0441\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u0438 \u0434\u0440\u0443\u0433\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0435\u043D\u0434\u0430 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F. \u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430; \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u043C\u0438, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442." })) : null, state.error === null ? null : (_jsxs("div", { className: "dsh-qa-error", role: "alert", children: [_jsx("span", { children: state.error }), state.phase === "error" ? (_jsx("button", { type: "button", onClick: () => void controller?.ensureSession(), children: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C" })) : null] }))] })] }), _jsx("footer", { className: "dsh-qa-footer", children: _jsxs("div", { className: "dsh-qa-footer__inner", children: [_jsx(QaApproval, { approvals: state.approvals, onAnswer: handleAnswerApproval }), _jsx(QaQuestions, { questions: state.questions, onAnswer: handleAnswerQuestion, onCancel: handleCancelQuestion, onStop: handleStop, canStop: state.canStop }),
|
|
980
|
+
})), state.pendingMessage === null ? null : (_jsx("div", { className: "dsh-qa-message-slot", children: _jsx(QaMessage, { message: state.pendingMessage, renderMarkdown: false, showTimestamp: false, thinkingPhrases: config.thinkingPhrases }) })), state.compatibilityReadOnly === true ? (_jsx("div", { className: "dsh-qa-compatibility", "data-testid": "qa-surface-compatibility", role: "status", children: "\u042D\u0442\u043E\u0442 \u0447\u0430\u0442 \u0441\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u0438 \u0434\u0440\u0443\u0433\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0435\u043D\u0434\u0430 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F. \u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430; \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u043C\u0438, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442." })) : null, state.error === null ? null : (_jsxs("div", { className: "dsh-qa-error", "data-testid": "qa-surface-error", role: "alert", children: [_jsx("span", { children: state.error }), state.phase === "error" ? (_jsx("button", { type: "button", "data-testid": "qa-surface-error-retry", onClick: () => void controller?.ensureSession(), children: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C" })) : null] }))] })] }), _jsx("footer", { className: "dsh-qa-footer", "data-testid": "qa-surface-footer", children: _jsxs("div", { className: "dsh-qa-footer__inner", children: [_jsx(QaApproval, { approvals: state.approvals, onAnswer: handleAnswerApproval }), _jsx(QaQuestions, { questions: state.questions, onAnswer: handleAnswerQuestion, onCancel: handleCancelQuestion, onStop: handleStop, canStop: state.canStop }), _jsxs("div", { className: "dsh-qa-composer-slot", "data-testid": "qa-surface-composer-slot", hidden: state.questions.length > 0, children: [_jsx(QaQueueDock, { rows: state.queue, running: state.phase === "running", canEdit: state.canEditQueue, onEdit: handleQueueEdit, onSendNow: handleQueueSendNow, onRemove: handleQueueRemove }), _jsx(QaComposer, { placeholder: config.branding.placeholder, quickQuestions: empty ? quickQuestions : NO_QUESTIONS, canSend: state.canSend, canStop: state.canStop, running: state.phase === "running", showStop: config.ui.showStop, status: status, attachments: pendingAttachments, limits: limits, slash: state.slash, slashPolicy: config.slashCommands.palette, onAttachmentsChange: setPendingAttachments, onSend: handleSend, onSlashOpen: handleSlashOpen, onStop: handleStop }, state.chatKey)] }), config.branding.disclaimer === "" ? null : (_jsxs("p", { className: "dsh-qa-footer__disclaimer", children: [_jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "5.75" }), _jsx("path", { d: "M8 7.25v3.5m0-5.25v.5" })] }), config.branding.disclaimer] }))] }) }), !empty
|
|
731
981
|
? ["left", "right"].map((side) => (_jsx(QaWidthHandle, { side: side, ...widthHandlers }, side)))
|
|
732
|
-
: null] }), _jsx(QaPanelHost, { panels: props.panels, sessionId: state.sessionId, qaToken: accounts?.token() ?? "", renderSlot: props.renderSlot })] })] }), agentsOpen && agentRows.length > 0 ? (_jsx(QaAgentsDrawer, { agents: agentRows, activeId: state.viewingSubagent?.id ?? null, onView: (id, title) => void controller?.viewSubagent(id, title), onClose: () => setAgentsOpen(false) })) : null, rail.railOpen ? (_jsx(QaRightRail, { tabs: railTabs, activeTab: rail.railTab, onTabSelect: rail.selectTab, onClose: rail.close })) : null] })] }));
|
|
982
|
+
: null] }), _jsx(QaPanelHost, { panels: props.panels, sessionId: state.sessionId, qaToken: accounts?.token() ?? "", renderSlot: props.renderSlot })] })] }), agentsOpen && agentRows.length > 0 ? (_jsx(QaAgentsDrawer, { agents: agentRows, activeId: state.viewingSubagent?.id ?? null, onView: (id, title) => void controller?.viewSubagent(id, title), onClose: () => setAgentsOpen(false) })) : null, rail.railOpen ? (_jsx(QaRightRail, { tabs: railTabs, activeTab: rail.railTab, onTabSelect: rail.selectTab, onClose: rail.close })) : null, _jsx(QaRequestQueue, { status: state.requestQueue, onClose: dismissRequestQueue })] })] }));
|
|
733
983
|
}
|
|
734
984
|
//# sourceMappingURL=QaSurface.js.map
|