@yadsh/dsh-qa-surface 0.13.0 → 0.14.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 +279 -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 +64 -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 +66 -6
- 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 +133 -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 +7228 -4002
- 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/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 +3 -1
- 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/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(() => {
|
|
@@ -372,6 +492,10 @@ export function QaSurface(props) {
|
|
|
372
492
|
void controller?.deleteChat(sessionId);
|
|
373
493
|
}, [controller]);
|
|
374
494
|
const handleSend = useCallback((text, attachments, pick) => controller?.send(text, attachments, pick) ?? Promise.resolve(false), [controller]);
|
|
495
|
+
// Closing the queue notice only uncovers the composer: the question the stand
|
|
496
|
+
// had no room for is still the text in there, and asking it again is a
|
|
497
|
+
// keystroke the visitor takes when a place frees up.
|
|
498
|
+
const dismissRequestQueue = useCallback(() => controller?.dismissRequestQueueNotice(), [controller]);
|
|
375
499
|
// Cheap refresh on every palette opening: the skill registry has no browser
|
|
376
500
|
// change event, and the controller skips the round-trip while its answer is
|
|
377
501
|
// still fresh.
|
|
@@ -379,6 +503,9 @@ export function QaSurface(props) {
|
|
|
379
503
|
void controller?.refreshSlashCatalog();
|
|
380
504
|
}, [controller]);
|
|
381
505
|
const handleStop = useCallback(() => controller?.stop() ?? Promise.resolve(), [controller]);
|
|
506
|
+
const handleQueueEdit = useCallback((id, text) => controller?.queueAction(id, "edit", text) ?? Promise.resolve(null), [controller]);
|
|
507
|
+
const handleQueueSendNow = useCallback((id) => controller?.queueAction(id, "steer") ?? Promise.resolve(null), [controller]);
|
|
508
|
+
const handleQueueRemove = useCallback((id) => controller?.queueAction(id, "remove") ?? Promise.resolve(null), [controller]);
|
|
382
509
|
const handleAnswerApproval = useCallback((requestId, decision) => controller?.answerApproval(requestId, decision) ?? Promise.resolve(), [controller]);
|
|
383
510
|
const handleAnswerQuestion = useCallback((requestId, answers) => controller?.answerQuestion(requestId, answers) ?? Promise.resolve(), [controller]);
|
|
384
511
|
const handleCancelQuestion = useCallback((requestId) => controller?.cancelQuestion(requestId) ?? Promise.resolve(), [controller]);
|
|
@@ -540,15 +667,122 @@ export function QaSurface(props) {
|
|
|
540
667
|
onSave: (input) => accounts.updateStarters(input),
|
|
541
668
|
}
|
|
542
669
|
: undefined;
|
|
670
|
+
// One object serves both halves of the same decision: the form that edits
|
|
671
|
+
// it and the notices that obey it. A reader cannot be looking at one and
|
|
672
|
+
// waiting on the other, and a memo keeps the identity stable for the
|
|
673
|
+
// watcher that reads the channels out of it.
|
|
674
|
+
const notifications = {
|
|
675
|
+
notifications: accountsSnapshot.user.notifications,
|
|
676
|
+
switches: config.notifications,
|
|
677
|
+
onSave: (input) => accounts.updateNotifications(input),
|
|
678
|
+
};
|
|
543
679
|
const skills = config.accounts.skills.enabled && boundSkillApi !== undefined
|
|
544
680
|
? boundSkillApi
|
|
545
681
|
: undefined;
|
|
546
|
-
return {
|
|
682
|
+
return {
|
|
683
|
+
profile,
|
|
684
|
+
password,
|
|
685
|
+
starters,
|
|
686
|
+
notifications,
|
|
687
|
+
skills,
|
|
688
|
+
integrationTokens,
|
|
689
|
+
};
|
|
547
690
|
}, [accounts, accountsSnapshot, config, boundSkillApi, integrationTokens]);
|
|
548
691
|
const busyTurn = state.phase === "running" ? (railItems.at(-1)?.turn ?? null) : null;
|
|
549
692
|
// Projected even while the sidebar is hidden: the account settings report
|
|
550
693
|
// the same chat count, and one projection cannot disagree with itself.
|
|
551
694
|
const chatRows = useMemo(() => buildChatRows(controller?.chatIds() ?? [], listState.byId, activeSessionId, (id) => ownerNames?.get(id)), [controller, listState, activeSessionId, ownerNames, state.chatsRevision]);
|
|
695
|
+
// A turn that ends in one of this browser's own chats gets a notice: the
|
|
696
|
+
// sidebar dot going out is otherwise the only sign, and it is easy to miss
|
|
697
|
+
// from behind another window. The account's own list, not the sidebar's, is
|
|
698
|
+
// what bounds it: an admin's shared history is a wider read than ownership,
|
|
699
|
+
// and another account's turn is not this reader's news.
|
|
700
|
+
const turnNotices = useQaTurnNotifications({
|
|
701
|
+
chats: chatRows,
|
|
702
|
+
ownChatIds: accountsSnapshot.stage === "authed" ? accountsSnapshot.ownIds : undefined,
|
|
703
|
+
notifications: config.notifications,
|
|
704
|
+
storage: window.localStorage,
|
|
705
|
+
storageKey: `${stateKey}:notifications`,
|
|
706
|
+
// Signed in, the account's own channels decide and the settings form is
|
|
707
|
+
// what writes them; anonymously, the hook falls back to this browser.
|
|
708
|
+
account: settingsDialog?.notifications,
|
|
709
|
+
paused: state.phase === "reconnecting",
|
|
710
|
+
activeSessionId,
|
|
711
|
+
onSwitch: handleSwitch,
|
|
712
|
+
});
|
|
713
|
+
/**
|
|
714
|
+
* A line leaving the stack takes the control the reader was working out of the
|
|
715
|
+
* page, and a page with nothing focused answers Tab with the browser's own
|
|
716
|
+
* order — which leaves the QA interface. Most of the ways a line goes leave
|
|
717
|
+
* the stack standing and its count of lines where it was: waving one of the
|
|
718
|
+
* three off, a fourth turn pushing the oldest out of a full stack, the opt-in
|
|
719
|
+
* going away once the browser has answered the permission question. So the
|
|
720
|
+
* loss is read where it happens — the control the ring held is out of the page
|
|
721
|
+
* and nothing of the ring holds the focus — and the reader is handed to the
|
|
722
|
+
* line that took the lost one's place, on the same control of it: a cross
|
|
723
|
+
* given back as a cross. The anchor goes only with the focus it hands back: a
|
|
724
|
+
* page takes the focus out of a removed control on its own moment, not in step
|
|
725
|
+
* with this render. A dialog that holds the keyboard is not the ring's
|
|
726
|
+
* business, and is left to put the focus wherever it thinks the reader
|
|
727
|
+
* belongs.
|
|
728
|
+
*
|
|
729
|
+
* A page the reader is not looking at keeps its keyboard where it is: while
|
|
730
|
+
* `document.hasFocus()` is false nothing here is moved. The hand-off is owed
|
|
731
|
+
* rather than skipped — the anchor stands until the window comes back, and the
|
|
732
|
+
* `focus` event of this page is what pays it — because the reader who returns
|
|
733
|
+
* to a stack that dropped a line under them is exactly the reader this place
|
|
734
|
+
* was remembered for, while a control of a page no one is looking at has no
|
|
735
|
+
* claim on the keyboard of the page they are working in.
|
|
736
|
+
*
|
|
737
|
+
* Opening a chat from a line is the one path where a line goes and the chat
|
|
738
|
+
* changes in the same moment. The keyboard stays on the stack — on a
|
|
739
|
+
* neighbouring line, or back inside `<main>` once none is left — rather than
|
|
740
|
+
* following the switch: the composer the switch remounts is disabled while the
|
|
741
|
+
* chat is still being bound, so it can take no focus, and the surface moves
|
|
742
|
+
* the keyboard on a switch no further than it does anywhere else.
|
|
743
|
+
*/
|
|
744
|
+
const restoreRingFocus = useCallback(() => {
|
|
745
|
+
const anchor = ringAnchor.current;
|
|
746
|
+
if (anchor === null || anchor.element.isConnected)
|
|
747
|
+
return;
|
|
748
|
+
if (dialogOwnsKeyboard()) {
|
|
749
|
+
ringAnchor.current = null;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (!document.hasFocus())
|
|
753
|
+
return;
|
|
754
|
+
const ring = focusRing(ringRoots());
|
|
755
|
+
const active = document.activeElement;
|
|
756
|
+
if (active !== null && ring.includes(active))
|
|
757
|
+
return;
|
|
758
|
+
ringAnchor.current = null;
|
|
759
|
+
// The line itself first, for a control that left while its line stayed:
|
|
760
|
+
// then whichever neighbour is still on screen, the one after the reader
|
|
761
|
+
// taking the place the stack still lists in that order.
|
|
762
|
+
const candidates = [];
|
|
763
|
+
for (const line of [anchor.line, anchor.after, anchor.before]) {
|
|
764
|
+
if (line === null || !line.isConnected)
|
|
765
|
+
continue;
|
|
766
|
+
const controls = focusable(line);
|
|
767
|
+
candidates.push(controls[Math.min(anchor.step, controls.length - 1)]);
|
|
768
|
+
}
|
|
769
|
+
// The stack has no line left to stand on: back into the interface, onto the
|
|
770
|
+
// control the ring reaches last, and past the first candidate of it that
|
|
771
|
+
// refuses the keyboard.
|
|
772
|
+
candidates.push(...[...ring].reverse(), surface.current ?? undefined);
|
|
773
|
+
focusFirst(candidates);
|
|
774
|
+
}, [dialogOwnsKeyboard, ringRoots]);
|
|
775
|
+
useEffect(() => {
|
|
776
|
+
restoreRingFocus();
|
|
777
|
+
});
|
|
778
|
+
useEffect(() => {
|
|
779
|
+
// The hand-off a blurred page owed, read again at the moment the reader
|
|
780
|
+
// comes back: whoever holds the keyboard then keeps it, and a control of the
|
|
781
|
+
// ring they reached on their own is not taken away from them.
|
|
782
|
+
const onWindowFocus = () => restoreRingFocus();
|
|
783
|
+
window.addEventListener("focus", onWindowFocus);
|
|
784
|
+
return () => window.removeEventListener("focus", onWindowFocus);
|
|
785
|
+
}, [restoreRingFocus]);
|
|
552
786
|
// The audit provider is optional: `auditSnapshot.api` is null until the
|
|
553
787
|
// audit plugin's client bundle is loaded, and the badge is absent until
|
|
554
788
|
// then. Chat ids rather than rows feed the poll, so a re-render does not
|
|
@@ -594,7 +828,7 @@ export function QaSurface(props) {
|
|
|
594
828
|
const welcomeNotice = (_jsx(QaWelcomeNotice, { storage: window.localStorage, storageKey: `${stateKey}:welcome-notice` }, stateKey));
|
|
595
829
|
if (config.accounts.enabled && accounts !== undefined) {
|
|
596
830
|
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 })] }));
|
|
831
|
+
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
832
|
}
|
|
599
833
|
if (accountsStage === "gate") {
|
|
600
834
|
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 +884,11 @@ export function QaSurface(props) {
|
|
|
650
884
|
} }));
|
|
651
885
|
}
|
|
652
886
|
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" })] }) }));
|
|
887
|
+
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
888
|
}
|
|
655
|
-
return (_jsxs(_Fragment, { children: [welcomeNotice,
|
|
889
|
+
return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaTurnNotice, { items: turnNotices.items, onOpen: turnNotices.onOpen, onDismiss: turnNotices.onDismiss, rootRef: noticeStack, onKeyDown: trapNoticeKeys, ...(turnNotices.onEnableDesktop === undefined
|
|
890
|
+
? {}
|
|
891
|
+
: { onEnableDesktop: turnNotices.onEnableDesktop }) }), settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
|
|
656
892
|
? "profile"
|
|
657
893
|
: "general"), email: accountsSnapshot.stage === "authed"
|
|
658
894
|
? accountsSnapshot.user.email
|
|
@@ -664,15 +900,15 @@ export function QaSurface(props) {
|
|
|
664
900
|
? {}
|
|
665
901
|
: { password: settingsDialog.password }), ...(settingsDialog.starters === undefined
|
|
666
902
|
? {}
|
|
667
|
-
: { starters: settingsDialog.starters }), ...(settingsDialog.integrationTokens === undefined
|
|
903
|
+
: { starters: settingsDialog.starters }), notifications: settingsDialog.notifications, ...(settingsDialog.integrationTokens === undefined
|
|
668
904
|
? {}
|
|
669
905
|
: { integrationTokens: settingsDialog.integrationTokens }), ...(settingsDialog.skills === undefined
|
|
670
906
|
? {}
|
|
671
|
-
: { skills: settingsDialog.skills }) })), _jsxs("main", { className: QA_SURFACE_CLASS, "data-phase": state.phase, "aria-label": config.branding.title, tabIndex: -1, onKeyDown:
|
|
907
|
+
: { 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
908
|
sessionRole?.name ??
|
|
673
909
|
sessionRole?.id ??
|
|
674
910
|
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 &&
|
|
911
|
+
"", ...(previewing ? { onExit: exitPreview } : {}), disabled: state.phase === "creating" })) : 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 &&
|
|
676
912
|
accounts !== undefined &&
|
|
677
913
|
accountsStage === "authed"
|
|
678
914
|
? {
|
|
@@ -683,7 +919,7 @@ export function QaSurface(props) {
|
|
|
683
919
|
? {}
|
|
684
920
|
: { settings: settingsEntry }),
|
|
685
921
|
}
|
|
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 ||
|
|
922
|
+
: 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
923
|
selectedSubrole === null ||
|
|
688
924
|
previewing ? undefined : (_jsx(QaRoleSelector, { roles: access.subroles, selected: selectedSubrole, conversationStarted: !empty, disabled: config.session.policy === "fixed" ||
|
|
689
925
|
state.phase === "creating" ||
|
|
@@ -694,7 +930,7 @@ export function QaSurface(props) {
|
|
|
694
930
|
setSelectedSubrole(id);
|
|
695
931
|
setSessionRole(access.subroles.find((role) => role.id === id));
|
|
696
932
|
void controller?.selectSubrole(id, false);
|
|
697
|
-
} })), administration: accountsSnapshot.stage === "authed" &&
|
|
933
|
+
} })), themeSwitcher: _jsx(QaThemeSwitcher, { preference: theme.preference, onSelect: theme.select }), administration: accountsSnapshot.stage === "authed" &&
|
|
698
934
|
accountsSnapshot.user.role === "admin"
|
|
699
935
|
? {
|
|
700
936
|
onOpen: () => window.history.pushState(null, "", `${config.route.path === "/" ? "" : config.route.path}/admin`),
|
|
@@ -702,13 +938,13 @@ export function QaSurface(props) {
|
|
|
702
938
|
: 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 }),
|
|
703
939
|
// The sidebar carries this entry next to the account name, so
|
|
704
940
|
// 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: {
|
|
941
|
+
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
942
|
"--dsh-qa-content-width": `${config.ui.minContentWidth}px`,
|
|
707
|
-
}, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", onScroll: (event) => {
|
|
943
|
+
}, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", "data-testid": "qa-surface-transcript", onScroll: (event) => {
|
|
708
944
|
const element = event.currentTarget;
|
|
709
945
|
nearBottom.current = isNearBottom(element);
|
|
710
946
|
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) => {
|
|
947
|
+
}, 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
948
|
const group = message.role === "user"
|
|
713
949
|
? view.groupByPromptId.get(message.id)
|
|
714
950
|
: undefined;
|
|
@@ -727,8 +963,8 @@ export function QaSurface(props) {
|
|
|
727
963
|
...offsets,
|
|
728
964
|
[group.groupId]: step,
|
|
729
965
|
})) })) : 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 }),
|
|
966
|
+
})), 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
967
|
? ["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] })] }));
|
|
968
|
+
: 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
969
|
}
|
|
734
970
|
//# sourceMappingURL=QaSurface.js.map
|