@arbidocs/blocks 0.5.113 → 0.5.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedCallDock-VCJ6Q7WE.cjs → ConnectedCallDock-WER7R222.cjs} +27 -27
- package/dist/{ConnectedCallDock-VCJ6Q7WE.cjs.map → ConnectedCallDock-WER7R222.cjs.map} +1 -1
- package/dist/{ConnectedCallRoom-BUI3ILJD.cjs → ConnectedCallRoom-T7OBQTYP.cjs} +19 -19
- package/dist/{ConnectedCallRoom-BUI3ILJD.cjs.map → ConnectedCallRoom-T7OBQTYP.cjs.map} +1 -1
- package/dist/{ConnectedMailInviteCard-P65MQM6J.cjs → ConnectedMailInviteCard-EINB3THA.cjs} +5 -5
- package/dist/{ConnectedMailInviteCard-P65MQM6J.cjs.map → ConnectedMailInviteCard-EINB3THA.cjs.map} +1 -1
- package/dist/{ConnectedNativeDocxComparison-JO2H4QIP.cjs → ConnectedNativeDocxComparison-YXMYJWXY.cjs} +24 -24
- package/dist/{ConnectedNativeDocxComparison-JO2H4QIP.cjs.map → ConnectedNativeDocxComparison-YXMYJWXY.cjs.map} +1 -1
- package/dist/{ConnectedProductionSetDialog-BSPM4NQC.cjs → ConnectedProductionSetDialog-LHB4VQWC.cjs} +9 -9
- package/dist/{ConnectedProductionSetDialog-BSPM4NQC.cjs.map → ConnectedProductionSetDialog-LHB4VQWC.cjs.map} +1 -1
- package/dist/{ConnectedStreamedWordArtifact-DMQEJFCA.cjs → ConnectedStreamedWordArtifact-2RTCHVPI.cjs} +24 -24
- package/dist/{ConnectedStreamedWordArtifact-DMQEJFCA.cjs.map → ConnectedStreamedWordArtifact-2RTCHVPI.cjs.map} +1 -1
- package/dist/{DocumentReviewCard-UBE2DVAC.cjs → DocumentReviewCard-ZSBZBGFK.cjs} +23 -23
- package/dist/{DocumentReviewCard-UBE2DVAC.cjs.map → DocumentReviewCard-ZSBZBGFK.cjs.map} +1 -1
- package/dist/{FilesViewerDialog-EBYRA3ZZ.cjs → FilesViewerDialog-I5QIHQZT.cjs} +55 -55
- package/dist/{FilesViewerDialog-EBYRA3ZZ.cjs.map → FilesViewerDialog-I5QIHQZT.cjs.map} +1 -1
- package/dist/{NativeDocxEditor-EJEIJDOC.cjs → NativeDocxEditor-H6TAEVGY.cjs} +6 -6
- package/dist/{NativeDocxEditor-EJEIJDOC.cjs.map → NativeDocxEditor-H6TAEVGY.cjs.map} +1 -1
- package/dist/{WorkspaceTableView-BO2OJO7A.cjs → WorkspaceTableView-J3C6VH4T.cjs} +9 -9
- package/dist/{WorkspaceTableView-BO2OJO7A.cjs.map → WorkspaceTableView-J3C6VH4T.cjs.map} +1 -1
- package/dist/agents/index.cjs +298 -298
- package/dist/{ai-EMNMGDAN.cjs → ai-IM4A23YT.cjs} +27 -27
- package/dist/ai-IM4A23YT.cjs.map +1 -0
- package/dist/calls/index.cjs +149 -149
- package/dist/chat/DraftingDocChip.d.ts +3 -1
- package/dist/chat/DraftingDocChip.js +7 -4
- package/dist/chat/DraftingDocChip.js.map +2 -2
- package/dist/chat/index.cjs +65 -65
- package/dist/{chunk-AJVZYRAJ.cjs → chunk-24EDG4PC.cjs} +68 -68
- package/dist/{chunk-AJVZYRAJ.cjs.map → chunk-24EDG4PC.cjs.map} +1 -1
- package/dist/{chunk-GTTDJMWX.cjs → chunk-325GVZE6.cjs} +6 -6
- package/dist/{chunk-GTTDJMWX.cjs.map → chunk-325GVZE6.cjs.map} +1 -1
- package/dist/{chunk-HM5L4SBC.cjs → chunk-3A7QWUAW.cjs} +3 -3
- package/dist/{chunk-HM5L4SBC.cjs.map → chunk-3A7QWUAW.cjs.map} +1 -1
- package/dist/{chunk-ET3NVY27.cjs → chunk-42TURFHF.cjs} +5 -5
- package/dist/{chunk-ET3NVY27.cjs.map → chunk-42TURFHF.cjs.map} +1 -1
- package/dist/{chunk-J2RXQVVF.cjs → chunk-4EK7TAP7.cjs} +5 -5
- package/dist/{chunk-J2RXQVVF.cjs.map → chunk-4EK7TAP7.cjs.map} +1 -1
- package/dist/{chunk-BHTEKYWJ.cjs → chunk-557MJ4IH.cjs} +23 -23
- package/dist/{chunk-BHTEKYWJ.cjs.map → chunk-557MJ4IH.cjs.map} +1 -1
- package/dist/{chunk-Y45VRNSZ.cjs → chunk-57NHXEU2.cjs} +4 -4
- package/dist/{chunk-Y45VRNSZ.cjs.map → chunk-57NHXEU2.cjs.map} +1 -1
- package/dist/{chunk-MUA5PJGX.cjs → chunk-5SKHK3BG.cjs} +4 -4
- package/dist/{chunk-MUA5PJGX.cjs.map → chunk-5SKHK3BG.cjs.map} +1 -1
- package/dist/{chunk-AFXDLDPU.cjs → chunk-5UYFLFNR.cjs} +4 -4
- package/dist/{chunk-AFXDLDPU.cjs.map → chunk-5UYFLFNR.cjs.map} +1 -1
- package/dist/{chunk-OTG5T5Z5.cjs → chunk-6UTKM5AB.cjs} +5 -5
- package/dist/{chunk-OTG5T5Z5.cjs.map → chunk-6UTKM5AB.cjs.map} +1 -1
- package/dist/{chunk-H5OLHP2U.cjs → chunk-7OPOPYV6.cjs} +3 -3
- package/dist/{chunk-H5OLHP2U.cjs.map → chunk-7OPOPYV6.cjs.map} +1 -1
- package/dist/{chunk-MQANCZOD.cjs → chunk-A3T3IC3K.cjs} +78 -78
- package/dist/{chunk-MQANCZOD.cjs.map → chunk-A3T3IC3K.cjs.map} +1 -1
- package/dist/{chunk-5VCID67B.cjs → chunk-B3LZITVF.cjs} +12 -12
- package/dist/{chunk-5VCID67B.cjs.map → chunk-B3LZITVF.cjs.map} +1 -1
- package/dist/{chunk-OZWYCRRK.cjs → chunk-BQ5RIWWT.cjs} +30 -30
- package/dist/{chunk-OZWYCRRK.cjs.map → chunk-BQ5RIWWT.cjs.map} +1 -1
- package/dist/chunk-CIHERNDO.cjs +18 -0
- package/dist/{chunk-L52N2LVE.cjs.map → chunk-CIHERNDO.cjs.map} +1 -1
- package/dist/{chunk-NP4EWPIP.cjs → chunk-CJYWHUZR.cjs} +6 -6
- package/dist/{chunk-NP4EWPIP.cjs.map → chunk-CJYWHUZR.cjs.map} +1 -1
- package/dist/{chunk-6W4X4EFI.cjs → chunk-CMGNRMPA.cjs} +5 -5
- package/dist/{chunk-6W4X4EFI.cjs.map → chunk-CMGNRMPA.cjs.map} +1 -1
- package/dist/{chunk-YONJRX6N.cjs → chunk-CPG7II4E.cjs} +28 -28
- package/dist/{chunk-YONJRX6N.cjs.map → chunk-CPG7II4E.cjs.map} +1 -1
- package/dist/{chunk-GGBNUDLK.cjs → chunk-CPR6EHG7.cjs} +70 -70
- package/dist/{chunk-GGBNUDLK.cjs.map → chunk-CPR6EHG7.cjs.map} +1 -1
- package/dist/{chunk-DZWIVRYP.cjs → chunk-DDIW4HLU.cjs} +36 -36
- package/dist/{chunk-DZWIVRYP.cjs.map → chunk-DDIW4HLU.cjs.map} +1 -1
- package/dist/{chunk-Z3VNKPPH.cjs → chunk-E3Y7RI3J.cjs} +4 -4
- package/dist/{chunk-Z3VNKPPH.cjs.map → chunk-E3Y7RI3J.cjs.map} +1 -1
- package/dist/{chunk-WT7CCCIN.cjs → chunk-GNX3FBBX.cjs} +4 -4
- package/dist/{chunk-WT7CCCIN.cjs.map → chunk-GNX3FBBX.cjs.map} +1 -1
- package/dist/{chunk-IIWJQEBC.cjs → chunk-H7PHOTX2.cjs} +10 -10
- package/dist/{chunk-IIWJQEBC.cjs.map → chunk-H7PHOTX2.cjs.map} +1 -1
- package/dist/{chunk-LQHXUSWZ.cjs → chunk-HQDR43JC.cjs} +6 -6
- package/dist/{chunk-LQHXUSWZ.cjs.map → chunk-HQDR43JC.cjs.map} +1 -1
- package/dist/{chunk-6LQN4BMX.cjs → chunk-IDCD5TEZ.cjs} +5 -5
- package/dist/{chunk-6LQN4BMX.cjs.map → chunk-IDCD5TEZ.cjs.map} +1 -1
- package/dist/{chunk-INEPPS4H.cjs → chunk-IW7CDJTA.cjs} +4 -4
- package/dist/{chunk-INEPPS4H.cjs.map → chunk-IW7CDJTA.cjs.map} +1 -1
- package/dist/{chunk-6YOZKXV6.cjs → chunk-JR2YSBD5.cjs} +4 -4
- package/dist/{chunk-6YOZKXV6.cjs.map → chunk-JR2YSBD5.cjs.map} +1 -1
- package/dist/{chunk-ZFKWCGA5.cjs → chunk-JV5TVUOU.cjs} +4 -4
- package/dist/{chunk-ZFKWCGA5.cjs.map → chunk-JV5TVUOU.cjs.map} +1 -1
- package/dist/{chunk-IFOGBMAE.cjs → chunk-KLMNCBLY.cjs} +9 -6
- package/dist/chunk-KLMNCBLY.cjs.map +1 -0
- package/dist/{chunk-JDI7E4RI.cjs → chunk-KTQOGT2L.cjs} +14 -14
- package/dist/{chunk-JDI7E4RI.cjs.map → chunk-KTQOGT2L.cjs.map} +1 -1
- package/dist/{chunk-OKHKJ5ML.cjs → chunk-KXIRVKCJ.cjs} +11 -11
- package/dist/{chunk-OKHKJ5ML.cjs.map → chunk-KXIRVKCJ.cjs.map} +1 -1
- package/dist/{chunk-4ED7CNOX.cjs → chunk-M6IKVKU5.cjs} +40 -3
- package/dist/chunk-M6IKVKU5.cjs.map +1 -0
- package/dist/{chunk-XKDUPU7B.cjs → chunk-NJCK6V6U.cjs} +4 -4
- package/dist/{chunk-XKDUPU7B.cjs.map → chunk-NJCK6V6U.cjs.map} +1 -1
- package/dist/{chunk-OVKJ7OVZ.cjs → chunk-NNUK5ZM7.cjs} +6 -6
- package/dist/{chunk-OVKJ7OVZ.cjs.map → chunk-NNUK5ZM7.cjs.map} +1 -1
- package/dist/{chunk-MJHIBCF7.cjs → chunk-NY3YUCOW.cjs} +4 -4
- package/dist/{chunk-MJHIBCF7.cjs.map → chunk-NY3YUCOW.cjs.map} +1 -1
- package/dist/{chunk-R4TP3VS3.cjs → chunk-OEREKAAP.cjs} +101 -101
- package/dist/{chunk-R4TP3VS3.cjs.map → chunk-OEREKAAP.cjs.map} +1 -1
- package/dist/{chunk-ENUZ35NR.cjs → chunk-P5AQYC4D.cjs} +4 -4
- package/dist/{chunk-ENUZ35NR.cjs.map → chunk-P5AQYC4D.cjs.map} +1 -1
- package/dist/{chunk-ZZZ6AZDR.cjs → chunk-P77YTTJ3.cjs} +4 -4
- package/dist/{chunk-ZZZ6AZDR.cjs.map → chunk-P77YTTJ3.cjs.map} +1 -1
- package/dist/{chunk-5I2VUNZR.cjs → chunk-PGJYD67F.cjs} +6 -6
- package/dist/{chunk-5I2VUNZR.cjs.map → chunk-PGJYD67F.cjs.map} +1 -1
- package/dist/{chunk-BYN7UMC5.cjs → chunk-PL7HUTM3.cjs} +8 -8
- package/dist/{chunk-BYN7UMC5.cjs.map → chunk-PL7HUTM3.cjs.map} +1 -1
- package/dist/{chunk-T7J352JD.cjs → chunk-QMIHLJMH.cjs} +6 -6
- package/dist/{chunk-T7J352JD.cjs.map → chunk-QMIHLJMH.cjs.map} +1 -1
- package/dist/{chunk-WJP6ICDM.cjs → chunk-R7CJEIA5.cjs} +4 -4
- package/dist/{chunk-WJP6ICDM.cjs.map → chunk-R7CJEIA5.cjs.map} +1 -1
- package/dist/{chunk-IE677C4I.cjs → chunk-SBWIA4PL.cjs} +21 -21
- package/dist/{chunk-IE677C4I.cjs.map → chunk-SBWIA4PL.cjs.map} +1 -1
- package/dist/{chunk-GVTKFVXH.cjs → chunk-SGYSDABK.cjs} +6 -6
- package/dist/{chunk-GVTKFVXH.cjs.map → chunk-SGYSDABK.cjs.map} +1 -1
- package/dist/{chunk-PIB7K7HI.cjs → chunk-T23UTIRI.cjs} +8 -8
- package/dist/{chunk-PIB7K7HI.cjs.map → chunk-T23UTIRI.cjs.map} +1 -1
- package/dist/{chunk-E4BLRF2L.cjs → chunk-TDO5UWXF.cjs} +5 -5
- package/dist/{chunk-E4BLRF2L.cjs.map → chunk-TDO5UWXF.cjs.map} +1 -1
- package/dist/{chunk-U5T46QCS.cjs → chunk-TKHCIHE2.cjs} +3 -3
- package/dist/chunk-TKHCIHE2.cjs.map +1 -0
- package/dist/{chunk-4IOYQLKG.cjs → chunk-TP5PGWIO.cjs} +3 -3
- package/dist/{chunk-4IOYQLKG.cjs.map → chunk-TP5PGWIO.cjs.map} +1 -1
- package/dist/{chunk-A4Z5CTLF.cjs → chunk-TT4IF52H.cjs} +39 -39
- package/dist/{chunk-A4Z5CTLF.cjs.map → chunk-TT4IF52H.cjs.map} +1 -1
- package/dist/{chunk-WIAWHETB.cjs → chunk-ULPTSXXO.cjs} +4 -4
- package/dist/{chunk-WIAWHETB.cjs.map → chunk-ULPTSXXO.cjs.map} +1 -1
- package/dist/{chunk-K5MYEWD5.cjs → chunk-UZHWXKS6.cjs} +7 -7
- package/dist/{chunk-K5MYEWD5.cjs.map → chunk-UZHWXKS6.cjs.map} +1 -1
- package/dist/{chunk-XW524KUX.cjs → chunk-VJCSEMXT.cjs} +4 -4
- package/dist/{chunk-XW524KUX.cjs.map → chunk-VJCSEMXT.cjs.map} +1 -1
- package/dist/{chunk-B5442TKX.cjs → chunk-VRK3IQAP.cjs} +4 -4
- package/dist/{chunk-B5442TKX.cjs.map → chunk-VRK3IQAP.cjs.map} +1 -1
- package/dist/{chunk-MYRKL3JO.cjs → chunk-VWTOIGX4.cjs} +4 -4
- package/dist/{chunk-MYRKL3JO.cjs.map → chunk-VWTOIGX4.cjs.map} +1 -1
- package/dist/{chunk-KZKGZ7IG.cjs → chunk-WAV762GP.cjs} +14 -14
- package/dist/{chunk-KZKGZ7IG.cjs.map → chunk-WAV762GP.cjs.map} +1 -1
- package/dist/{chunk-DMNX556N.cjs → chunk-WR2SF44W.cjs} +10 -10
- package/dist/{chunk-DMNX556N.cjs.map → chunk-WR2SF44W.cjs.map} +1 -1
- package/dist/{chunk-M2D4POJV.cjs → chunk-WXUU3COI.cjs} +9 -9
- package/dist/{chunk-M2D4POJV.cjs.map → chunk-WXUU3COI.cjs.map} +1 -1
- package/dist/{chunk-FGEBA3TL.cjs → chunk-X5LANJDZ.cjs} +26 -26
- package/dist/{chunk-FGEBA3TL.cjs.map → chunk-X5LANJDZ.cjs.map} +1 -1
- package/dist/{chunk-MY7Q3QE2.cjs → chunk-XKVPSOKP.cjs} +4 -4
- package/dist/{chunk-MY7Q3QE2.cjs.map → chunk-XKVPSOKP.cjs.map} +1 -1
- package/dist/{chunk-ONZOVO2B.cjs → chunk-Y24V6HPR.cjs} +24 -24
- package/dist/{chunk-ONZOVO2B.cjs.map → chunk-Y24V6HPR.cjs.map} +1 -1
- package/dist/{chunk-KGGSBJMU.cjs → chunk-YGXOKB5Z.cjs} +4 -4
- package/dist/{chunk-KGGSBJMU.cjs.map → chunk-YGXOKB5Z.cjs.map} +1 -1
- package/dist/{chunk-YAPXIANR.cjs → chunk-YVCOQHCE.cjs} +5 -5
- package/dist/{chunk-YAPXIANR.cjs.map → chunk-YVCOQHCE.cjs.map} +1 -1
- package/dist/{chunk-LCNKKSN3.cjs → chunk-ZTXQIIFV.cjs} +6 -6
- package/dist/{chunk-LCNKKSN3.cjs.map → chunk-ZTXQIIFV.cjs.map} +1 -1
- package/dist/citations/index.cjs +8 -8
- package/dist/connect/docx.cjs +28 -28
- package/dist/connect/index.cjs +1683 -1497
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/viewer.cjs +41 -41
- package/dist/connection/ConnectedWorkflowBuilder.d.ts +11 -0
- package/dist/connection/ConnectedWorkflowBuilder.js +42 -3
- package/dist/connection/ConnectedWorkflowBuilder.js.map +2 -2
- package/dist/connection/conversation/ConnectedConversation.js +143 -55
- package/dist/connection/conversation/ConnectedConversation.js.map +2 -2
- package/dist/connection/conversation/ConnectedConversationPage.js +36 -2
- package/dist/connection/conversation/ConnectedConversationPage.js.map +2 -2
- package/dist/connection/conversation/ConversationSendButton.d.ts +1 -0
- package/dist/connection/conversation/ConversationSendButton.js +3 -2
- package/dist/connection/conversation/ConversationSendButton.js.map +2 -2
- package/dist/connection/conversation/DocumentTargetNotice.d.ts +7 -0
- package/dist/connection/conversation/DocumentTargetNotice.js +45 -0
- package/dist/connection/conversation/DocumentTargetNotice.js.map +7 -0
- package/dist/connection/conversation/documentSwitch.d.ts +9 -0
- package/dist/connection/conversation/documentSwitch.js +40 -0
- package/dist/connection/conversation/documentSwitch.js.map +7 -0
- package/dist/connection/workflows/ConnectedWorkflowsPage.js +10 -3
- package/dist/connection/workflows/ConnectedWorkflowsPage.js.map +2 -2
- package/dist/connection/workflows/useWorkflowsPageState.d.ts +1 -0
- package/dist/connection/workflows/useWorkflowsPageState.js.map +2 -2
- package/dist/dashboards/index.cjs +23 -23
- package/dist/docx/NativeDocxEditor.js +3 -1
- package/dist/docx/NativeDocxEditor.js.map +2 -2
- package/dist/docx/index.cjs +38 -38
- package/dist/docx/signatures/SignatureFields.d.ts +2 -0
- package/dist/docx/signatures/SignatureFields.js +40 -2
- package/dist/docx/signatures/SignatureFields.js.map +2 -2
- package/dist/embed/index.cjs +50 -50
- package/dist/facts/index.cjs +44 -44
- package/dist/files/index.cjs +226 -226
- package/dist/home/index.cjs +32 -32
- package/dist/index.cjs +1694 -1694
- package/dist/provider/environment.d.ts +5 -0
- package/dist/provider/environment.js +1 -1
- package/dist/provider/environment.js.map +2 -2
- package/dist/provider/requests.d.ts +2 -0
- package/dist/provider/requests.js.map +2 -2
- package/dist/retrieval/index.cjs +4 -4
- package/dist/settings/index.cjs +147 -147
- package/dist/shell/index.cjs +91 -91
- package/dist/studio/index.cjs +42 -42
- package/dist/styles.css +1 -1
- package/dist/workflow-canvas/index.cjs +116 -74
- package/dist/workflow-canvas/index.cjs.map +1 -1
- package/dist/workspaces/index.cjs +27 -27
- package/node_modules/@arbidocs/coedit/package.json +1 -1
- package/package.json +5 -5
- package/dist/ai-EMNMGDAN.cjs.map +0 -1
- package/dist/chunk-4ED7CNOX.cjs.map +0 -1
- package/dist/chunk-IFOGBMAE.cjs.map +0 -1
- package/dist/chunk-L52N2LVE.cjs +0 -18
- package/dist/chunk-U5T46QCS.cjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/connection/conversation/ConnectedConversation.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * ConnectedConversationThread\n *\n * The open conversation, connected: its messages (or what an empty one shows), and the composer\n * dock under them (a failed or orphaned turn's alert, the agent's pending question, the running\n * turn's control bar, the composer). It reads and writes the session; what is the host's own (its\n * model picker, what the conversation reads, a settings button) comes in as props.\n */\n\nimport {\n ArbiBotHeadIcon as ArbiBotIcon,\n READ_ONLY_MEETING_TEXT,\n buildPendingFirstTurn,\n getOrphanedLastMessage,\n refuseChatWhenOffline,\n resolveConfigModel,\n selectDraft,\n transcriptSegmentOf,\n useAnswerAgentQuestion,\n useArbiSession,\n useArbiSessionState,\n useArbiSessionStore,\n useAskAgain,\n useAutoSendDraft,\n useConfig,\n useConnectRunStream,\n useConversationData,\n useConversationIsShared,\n useConversationItems,\n useConversationTurn,\n useDocumentAutoAdd,\n useDocuments,\n useThreadDocument,\n useUserSettings,\n useWorkspaceRole,\n} from '@arbidocs/react'\nimport { getDocumentTitle } from '@arbidocs/react/ui'\nimport { useOpenDocumentId, useTurnDocumentId, useAssistantCopy } from '@arbidocs/react/shell'\nimport type { ChatMessage as Message } from '@arbidocs/sdk/browser'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { ConversationFilter } from '../../chat/TranscriptLine'\nimport { ComposerAccessories, ComposerTools, PaModeBadge } from '../../chat/ConversationParts'\nimport { ComposerContainer, ComposerDock, ScrollToBottom } from '../../chat/Composer'\nimport {\n ConversationBody,\n ConversationLoading,\n ConversationMain,\n} from '../../chat/ConversationLayout'\nimport { DraftingDocChip } from '../../chat/DraftingDocChip'\nimport { PendingAnswerBar } from '../../chat/PendingAnswerBar'\nimport { TaskEmptyState } from '../../agents/tasks/TaskEmptyState'\nimport {\n CallConversationEmpty,\n ProvisionalSpeakers,\n} from '../../calls/connected/CallConversationParts'\nimport { useProvisionalSpeakers } from '../../calls/connected/provisional-speakers'\nimport {\n TranscriptRunsContext,\n threadTranscriptRuns,\n} from '../../calls/connected/transcript-runs-context'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { useStartRequestedDraft } from './useStartRequestedDraft'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { ConnectedConversationStart } from '../workflows/ConnectedConversationStart'\nimport { useSlashSkillWorkflows } from '../workflows/useSlashSkillWorkflows'\nimport { AgentControlBar } from './AgentControlBar'\nimport { AskAgainContext } from './AskAgainContext'\nimport { ConnectedConversationEmpty } from './ConnectedConversationEmpty'\nimport { ConnectedMessageArea as MessageArea } from './ConnectedMessageArea'\nimport { ConversationAlert } from './ConversationAlert'\nimport { ConversationComposer } from './ConversationComposer'\nimport { ConversationSendButton } from './ConversationSendButton'\nimport { MicButton } from './MicButton'\nimport type { MessageTimerSubscription } from './MessageLiveSeconds'\nimport { useMeetingThreadMessages } from './useMeetingThreadMessages'\nimport { useThreadScroll } from './useThreadScroll'\n\nexport interface ConnectedConversationThreadProps {\n /** Controlled selection, mirrored into the shared session without route parsing. */\n conversationId?: string | null\n /** The composer's model picker (the host's). Absent: none. */\n modelSelector?: ReactNode\n}\n\n/** The thread body as the page frames it: the page fills the composer's context and tools. */\ninterface ConversationThreadProps {\n modelSelector?: ReactNode\n /** Left in the composer: what the conversation reads (the page's context button). */\n composerAccessory?: ReactNode\n /** Beside the microphone (the page's developer settings button). */\n composerTools?: ReactNode\n}\n\nexport function ConversationThread({\n modelSelector,\n composerAccessory,\n composerTools,\n}: ConversationThreadProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const session = useArbiSessionStore()\n const environment = useBlocksEnvironment()\n const { notify, openUpload, openSkillsBrowser } = environment\n\n // Generation state for all conversations (background detection, stream control).\n const generatingConversations = useArbiSessionState((state) => state.generating)\n const { data: userSettings } = useUserSettings()\n\n const currentConversationId = useArbiSessionState((state) => state.conversationId)\n // The open task, if it is one: an empty task shows itself rather than the generic empty page.\n const { conversations } = useConversationItems()\n const currentTask = currentConversationId\n ? conversations.find((c) => c.external_id === currentConversationId)\n : undefined\n\n // The assistant message currently streaming in this conversation: target for out-of-band\n // control (pause / resume / status probe).\n const streamingAssistantId = currentConversationId\n ? (generatingConversations[currentConversationId]?.streamingAssistantId ?? null)\n : null\n\n const editingMessageId = useArbiSessionState((state) => state.editingMessageId)\n\n const {\n selectedWorkspaceId,\n parentMessageId,\n messageHistory,\n threadsLoading,\n isEmptyConversation,\n } = useConversationData()\n\n // Attach to a run that is generating in THIS conversation but that this client did not start\n // itself: a background/scheduled agent run, or the same account on another device.\n useConnectRunStream(currentConversationId, messageHistory)\n\n // The in-flight assistant message drives the docked control bar's live status and steps\n // drawer; its timer subscription feeds the elapsed ticker.\n const streamingMessage = useMemo(\n () =>\n streamingAssistantId\n ? (messageHistory as Message[]).find((m) => m.external_id === streamingAssistantId)\n : undefined,\n [messageHistory, streamingAssistantId]\n )\n const queuedUserMessages = useMemo(\n () =>\n (messageHistory as Message[])\n .filter((message) => message.role === 'user' && message.status === 'queued')\n .map(({ external_id, content }) => ({ external_id, content })),\n [messageHistory]\n )\n const streamingLiveTimer = useMemo<MessageTimerSubscription | undefined>(\n () =>\n streamingMessage\n ? {\n conversationId: streamingMessage.conversation_ext_id,\n messageId: streamingMessage.external_id,\n isUser: false,\n isGenerating: true,\n }\n : undefined,\n [streamingMessage]\n )\n\n // The agent paused to ask a question (ask_user / Human-in-the-loop). Find the latest assistant\n // message still awaiting an answer, so the prompt docks (chips + the composer) instead of a\n // mid-stream input card.\n const pendingAnswer = useMemo(() => {\n for (let i = messageHistory.length - 1; i >= 0; i--) {\n const m = messageHistory[i]\n if (m.role === 'assistant' && m.pendingUserInput) {\n return {\n request: m.pendingUserInput,\n assistantMessageExtId: m.external_id,\n conversationExtId: m.conversation_ext_id,\n }\n }\n }\n return null\n }, [messageHistory])\n\n // Send an answer to a pending agent question (from a chip or the composer).\n const answerQuestion = useAnswerAgentQuestion()\n const submitAnswer = useCallback(\n async (answer: string) => {\n const trimmed = answer.trim()\n if (!pendingAnswer || !trimmed) return\n session.setDraft('')\n try {\n await answerQuestion(pendingAnswer, trimmed)\n } catch (err) {\n console.error('Failed to answer agent', err)\n notify('Failed to send your answer', 'error')\n }\n },\n [pendingAnswer, answerQuestion, session, notify]\n )\n\n // All workspace documents, for counts and auto-add.\n const { data: allWorkspaceDocuments = [], isLoading: isLoadingWorkspaceDocuments } =\n useDocuments()\n\n // The parent message's configuration, for the model it ran with.\n const parentMessage = useMemo(() => {\n if (!parentMessageId || !messageHistory) return null\n return messageHistory.find((m) => m.external_id === parentMessageId)\n }, [parentMessageId, messageHistory])\n const parentConfigName = parentMessage?.config_ext_id || userSettings?.last_config || 'default'\n const { data: parentConfig } = useConfig(parentConfigName, { enabled: !!parentConfigName })\n\n // Reconcile stale isGenerating state with backend truth. If the backend says the last message\n // is completed/failed but this client still thinks it is generating, clear the stale state\n // (covers a silent SSE death, a page reload, etc.).\n useEffect(() => {\n for (const [convId, genState] of Object.entries(generatingConversations)) {\n if (!genState.isGenerating) continue\n const conv = conversations.find((c) => c.external_id === convId)\n if (!conv?.last_message_status) continue\n if (conv.last_message_status === 'completed' || conv.last_message_status === 'failed') {\n session.setConversationGenerating(convId, false, null)\n }\n }\n }, [conversations, generatingConversations, session])\n\n // Continuing a thread or branch runs on its config, and the picker shows that config's model,\n // unless the person picks another. Only runs on parentMessageId changes, never on\n // parentConfig/messageHistory updates, so the person can freely toggle the model picker without\n // it being overwritten. Skipped while the parent is a freshly-created streaming message\n // (config_ext_id is null).\n useEffect(() => {\n if (!parentMessageId) return\n const parentMsg = messageHistory?.find((m) => m.external_id === parentMessageId)\n if (parentMsg?.config_ext_id && parentConfig && 'QueryLLM' in parentConfig) {\n session.setChatModelName(resolveConfigModel(parentConfig))\n session.setChatModel(parentMsg.config_ext_id)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMessageId])\n\n // Thread vs chat: in a thread, a comment without an @Assistant mention never expects a reply.\n // A shared chat is a thread, and so is a solo one set to answer only when @mentioned.\n const isSharedConversation = useConversationIsShared()\n const assistantMode =\n isSharedConversation || currentTask?.assistant_mode === 'mentioned' ? 'mentioned' : 'always'\n\n // A thread call's meeting thread is read-only for workspace guests (they may join the call).\n const isGuest = useWorkspaceRole() === 'guest'\n const meetingReadOnly = isGuest && !!currentTask?.meeting_call_ext_id\n const meetingCallExtId = currentTask?.meeting_call_ext_id ?? null\n const threadMessages = useMeetingThreadMessages(meetingCallExtId, messageHistory)\n\n const [firstTurnPrompt, setFirstTurnPrompt] = useState<string | null>(null)\n const currentUserExtId = useArbiSession().user?.external_id ?? ''\n const pendingFirstTurn = useMemo(\n () => (firstTurnPrompt ? buildPendingFirstTurn(firstTurnPrompt, currentUserExtId) : null),\n [firstTurnPrompt, currentUserExtId]\n )\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const {\n isGenerating,\n hasError,\n lastFailedMessage,\n lastErrorMessage,\n handleCreateBranch,\n handleSendMessage,\n handleAbortGeneration,\n handleRetrySend,\n handleRetryOrphanedMessage,\n } = useConversationTurn({ selectedWorkspaceId, parentMessageId })\n const handleAskAgain = useAskAgain({ isGenerating, handleCreateBranch })\n\n // A PA conversation: any of its questions carries the personal_agent tool.\n const paMode = useArbiSessionState((state) => state.paMode)\n useEffect(() => {\n if (messageHistory.length === 0) return\n const hasPaTool = messageHistory.some(\n (m) =>\n m.role === 'user' && m.tools && 'personal_agent' in (m.tools as Record<string, unknown>)\n )\n if (hasPaTool !== paMode) session.setPaMode(hasPaTool)\n }, [messageHistory, paMode, session])\n\n // A meeting thread shows everything, typed comments only, or its transcript only.\n const [meetingView, setMeetingView] = useState<'all' | 'chat' | 'transcript'>('all')\n useEffect(() => setMeetingView('all'), [currentConversationId])\n const shownThreadMessages = useMemo(() => {\n if (!meetingCallExtId || meetingView === 'all') return threadMessages\n const isLine = (m: (typeof threadMessages)[number]) =>\n !!transcriptSegmentOf(m.tools ?? m.meta?.tools)\n return threadMessages.filter((m) => (meetingView === 'transcript' ? isLine(m) : !isLine(m)))\n }, [meetingCallExtId, meetingView, threadMessages])\n const transcriptLineCount = useMemo(\n () =>\n meetingCallExtId\n ? threadMessages.filter((m) => transcriptSegmentOf(m.tools ?? m.meta?.tools)).length\n : 0,\n [meetingCallExtId, threadMessages]\n )\n\n // Visible messages: while editing, the edited message and everything after it are hidden.\n const visibleMessages = useMemo(() => {\n if (!editingMessageId) return shownThreadMessages\n const editIndex = shownThreadMessages.findIndex((m) => m.external_id === editingMessageId)\n if (editIndex < 0) return shownThreadMessages\n return shownThreadMessages.slice(0, editIndex)\n }, [shownThreadMessages, editingMessageId])\n // Transcript lines group into runs per speaker; each speaker's newest line lights up as they talk.\n const transcriptRunInfo = useMemo(\n () => threadTranscriptRuns(meetingCallExtId ? visibleMessages : []),\n [meetingCallExtId, visibleMessages]\n )\n // While I am in this meeting's call: \"<speaker> is speaking\u2026\" until their line arrives.\n const threadLines = useMemo(\n () =>\n threadMessages.flatMap((m) => {\n const seg = transcriptSegmentOf(m.tools ?? m.meta?.tools)\n return seg\n ? [\n {\n speakerId: seg.speaker_ext_id,\n at: Date.parse(seg.started_at ?? m.created_at ?? '') || 0,\n },\n ]\n : []\n }),\n [threadMessages]\n )\n const provisionalSpeakers = useProvisionalSpeakers(meetingCallExtId, threadLines)\n const showProvisional = !!meetingCallExtId && meetingView !== 'chat'\n\n // The latest context usage, from the last assistant message. finalContextUsage (the true\n // per-turn total from response.completed, with spent_gbp/budget_gbp) first; agentSteps only\n // cover function_call/reasoning items, so a step's own `context` is at best a mid-turn partial.\n // They are read only for messages saved before finalContextUsage existed.\n const latestContextUsage = useMemo(() => {\n const messages = messageHistory as Message[]\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role !== 'assistant') continue\n if (msg.finalContextUsage) return msg.finalContextUsage\n if (!msg.agentSteps) continue\n for (let j = msg.agentSteps.length - 1; j >= 0; j--) {\n if (msg.agentSteps[j].context) return msg.agentSteps[j].context\n }\n }\n return null\n }, [messageHistory])\n\n // The edited message's parent, which the edit branches from.\n const editedMessageParentId = useMemo(() => {\n if (!editingMessageId) return null\n const editedMsg = messageHistory.find((m) => m.external_id === editingMessageId)\n return editedMsg?.parent_message_ext_id ?? null\n }, [editingMessageId, messageHistory])\n\n const isBranching = !!editingMessageId\n\n // Send, after an orphaned question (one left without its answer) if there is one.\n const handleSendMessageWithOrphanCheck = useCallback(async () => {\n const orphanedMessage = getOrphanedLastMessage(messageHistory, isGenerating, assistantMode)\n const draftMessage = selectDraft(session.getState()).trim()\n\n if (orphanedMessage) {\n // An orphaned question and an empty draft: ask it again.\n if (draftMessage === '') {\n await handleRetryOrphanedMessage(\n orphanedMessage.content,\n orphanedMessage.parent_message_ext_id ?? null,\n orphanedMessage.external_id\n )\n return\n }\n\n // An orphaned question and a new draft: the draft replaces it.\n await handleRetryOrphanedMessage(\n draftMessage,\n orphanedMessage.parent_message_ext_id ?? null,\n orphanedMessage.external_id\n )\n session.setDraft('')\n return\n }\n\n // No orphaned question: a normal send.\n await handleSendMessage()\n }, [\n messageHistory,\n isGenerating,\n assistantMode,\n handleRetryOrphanedMessage,\n handleSendMessage,\n session,\n ])\n\n // An edit sends as a branch; anything else sends normally.\n const handleSendMessageForEdit = useCallback(async () => {\n if (isBranching && editedMessageParentId) {\n const draftMessage = selectDraft(session.getState()).trim()\n if (!draftMessage) return\n\n // Clear the timers of the messages after the branch point.\n const editIndex = messageHistory.findIndex((m) => m.external_id === editingMessageId)\n if (editIndex >= 0) {\n Object.keys(session.getState().messageTimers).forEach((key) => {\n const keyIndex = parseInt(key)\n if (!isNaN(keyIndex) && keyIndex > editIndex) session.clearMessageTimer(key)\n })\n }\n\n session.completeEditing()\n session.setDraft('')\n await handleCreateBranch(editedMessageParentId, draftMessage)\n return\n }\n\n await handleSendMessageWithOrphanCheck()\n }, [\n isBranching,\n editedMessageParentId,\n editingMessageId,\n messageHistory,\n session,\n handleCreateBranch,\n handleSendMessageWithOrphanCheck,\n ])\n\n // While the agent waits on an answer, the composer answers the question (interject) instead of\n // sending a new message.\n const handleComposerSend = useCallback(() => {\n const draft = selectDraft(session.getState())\n if (pendingAnswer) {\n submitAnswer(draft)\n return\n }\n // A brand-new chat has no thread to show until the server confirms the question.\n if (!currentConversationId) setFirstTurnPrompt(draft.trim())\n handleSendMessageForEdit()\n }, [pendingAnswer, currentConversationId, submitAnswer, handleSendMessageForEdit, session])\n\n // The pending first turn lasts only while the new chat generates; once the thread exists it is\n // replaced.\n useEffect(() => {\n if (!isGenerating) setFirstTurnPrompt(null)\n }, [isGenerating])\n\n // The person's send (Enter or the Send button): `/skill` or `/skill text` opens the skill's form\n // instead of sending a bare command. Auto-sends of launched drafts go through handleComposerSend\n // directly, so a launched skill is never intercepted again.\n const slashWorkflows = useSlashSkillWorkflows({ landing: 'current-chat' })\n // The composer's slash menu offers the built-in skills; a picked one opens this launcher.\n const composerSkills = useMemo(\n () => ({ openFor: slashWorkflows.openFor }),\n [slashWorkflows.openFor]\n )\n const browseSkills = useMemo(\n () => (openSkillsBrowser ? () => openSkillsBrowser() : undefined),\n [openSkillsBrowser]\n )\n\n const { showScrollToBottom, scrollToBottom } = useThreadScroll({\n currentConversationId,\n parentMessageId,\n messageHistory,\n })\n\n const handleUserSend = useCallback(() => {\n const draft = selectDraft(session.getState())\n if (!pendingAnswer && slashWorkflows.openForDraft(draft)) {\n session.setDraft('')\n return\n }\n // Offline, nothing is sent.\n if (refuseChatWhenOffline()) return\n // Their message lands at the bottom of the thread: bring the view down to it, even when they\n // had scrolled up to read a streaming answer.\n scrollToBottom()\n handleComposerSend()\n }, [pendingAnswer, slashWorkflows, handleComposerSend, scrollToBottom, session])\n\n // Focus the input when editing starts, its caret at the end.\n useEffect(() => {\n if (editingMessageId && inputRef.current) {\n const textarea = inputRef.current\n textarea.focus()\n const len = textarea.value.length\n textarea.setSelectionRange(len, len)\n }\n }, [editingMessageId])\n\n // Drafting mode: the thread's own document, which the assistant reads on demand and edits as\n // tracked changes (sent as a reference, `active_artifact_ext_id`): the one the thread is bound to,\n // else one made in it and open. A document merely open beside an unbound thread is only offered\n // (attach): being open never turns a question into an edit.\n const threadDocument = useThreadDocument()\n const turnDocumentId = useTurnDocumentId({\n boundDocumentId: threadDocument.documentId,\n conversationId: currentConversationId,\n workspaceId: selectedWorkspaceId,\n })\n const assistant = useAssistantCopy()\n const openDocumentId = useOpenDocumentId({ workspaceId: selectedWorkspaceId })\n const findDocument = (id: string | null) =>\n id ? allWorkspaceDocuments?.find((d) => d.external_id === id) : undefined\n const draftingDocument = findDocument(turnDocumentId)\n const attachableDocument = turnDocumentId ? undefined : findDocument(openDocumentId)\n\n // Empty chats follow workspace documents; sending the first message freezes that selection.\n useDocumentAutoAdd({\n selectedWorkspaceId,\n allWorkspaceDocuments,\n hasContent:\n !isEmptyConversation || Boolean(parentMessageId || messageHistory?.length || isGenerating),\n })\n\n // A draft handed over by a launch or the workspace home's ask bar sends on arrival, but only\n // after the documents query settles. MUST stay declared after useDocumentAutoAdd: effects run in\n // declaration order, and sending before auto-add seeds the new chat's documents posts the\n // question with none attached.\n useAutoSendDraft(handleComposerSend, !isLoadingWorkspaceDocuments)\n // `openNewConversation({ prompt })`: the prompt becomes the draft, sent as above.\n useStartRequestedDraft()\n\n const orphanedMessage = getOrphanedLastMessage(messageHistory, isGenerating, assistantMode)\n\n return (\n <ConversationBody>\n <ConversationMain data-testid={`conversation-${currentConversationId}`}>\n {threadsLoading ? (\n <ConversationLoading />\n ) : messageHistory.length > 0 || isBranching ? (\n <AskAgainContext.Provider value={handleAskAgain}>\n <TranscriptRunsContext.Provider value={transcriptRunInfo}>\n {meetingCallExtId && transcriptLineCount > 0 && (\n <div className=\"flex justify-center py-2\">\n <ConversationFilter<typeof meetingView>\n value={meetingView}\n onChange={setMeetingView}\n options={[\n { value: 'all', label: 'All' },\n { value: 'chat', label: 'Chat', testId: 'chat-filter' },\n {\n value: 'transcript',\n label: 'Transcript',\n count: transcriptLineCount,\n testId: 'transcript-filter',\n },\n ]}\n />\n </div>\n )}\n <MessageArea ref={containerRef} messages={visibleMessages} />\n {showProvisional && <ProvisionalSpeakers ids={provisionalSpeakers} />}\n </TranscriptRunsContext.Provider>\n </AskAgainContext.Provider>\n ) : meetingCallExtId ? (\n // A meeting thread with nothing yet: call-aware, not the upload prompt.\n <div>\n <CallConversationEmpty callExtId={meetingCallExtId} />\n <ProvisionalSpeakers ids={provisionalSpeakers} />\n </div>\n ) : currentTask && currentTask.status != null ? (\n <TaskEmptyState task={currentTask} />\n ) : pendingFirstTurn && isGenerating ? (\n <MessageArea ref={containerRef} messages={pendingFirstTurn} />\n ) : (\n <ConnectedConversationEmpty\n hasDocuments={allWorkspaceDocuments.length > 0}\n onUsePrompt={(text) => {\n session.setDraft(text)\n inputRef?.current?.focus()\n }}\n onAddDocuments={(files) => openUpload?.(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={\n openUpload ? () => openUpload(undefined, { source: 'email' }) : undefined\n }\n below={<ConnectedConversationStart />}\n />\n )}\n </ConversationMain>\n\n {/* The composer dock: the thread's alerts and bars, then the composer. */}\n <ComposerDock\n floating={\n <ScrollToBottom\n className=\"left-auto right-3 translate-x-0\"\n show={showScrollToBottom}\n onClick={scrollToBottom}\n />\n }\n >\n <ComposerContainer className=\"mx-auto max-w-[49rem] px-3 sm:px-6\">\n {/* A question left without its answer (stopped, reloaded, the backend failed), else a\n send that failed. */}\n {orphanedMessage ? (\n <ConversationAlert\n type={\n messageHistory[messageHistory.length - 1]?.role === 'assistant' &&\n messageHistory[messageHistory.length - 1].status === 'failed'\n ? 'backend-failed'\n : 'orphaned'\n }\n message={orphanedMessage}\n onRetryOrphaned={handleRetryOrphanedMessage}\n />\n ) : hasError && lastFailedMessage ? (\n <ConversationAlert\n type=\"send-failed\"\n errorMessage={lastErrorMessage || undefined}\n onRetrySend={handleRetrySend}\n isRetrying={isGenerating}\n />\n ) : null}\n\n {pendingAnswer && (\n <PendingAnswerBar\n key={pendingAnswer.assistantMessageExtId}\n question={pendingAnswer.request.question}\n options={pendingAnswer.request.options}\n timeoutSeconds={pendingAnswer.request.timeout_seconds}\n onPick={submitAnswer}\n />\n )}\n\n {/* Out-of-band control (pause / resume / status) for the running turn. Hidden while\n ask_user is pending: that flow owns the composer. */}\n {isGenerating && !pendingAnswer && streamingAssistantId && (\n <AgentControlBar\n key={streamingAssistantId}\n assistantMessageExtId={streamingAssistantId}\n agentSteps={streamingMessage?.agentSteps}\n liveTimer={streamingLiveTimer}\n onStop={handleAbortGeneration}\n queuedMessages={queuedUserMessages}\n />\n )}\n\n {meetingReadOnly ? (\n <p\n className=\"py-3 text-center text-sm text-muted-foreground\"\n data-testid=\"meeting-thread-read-only\"\n >\n {READ_ONLY_MEETING_TEXT}\n </p>\n ) : (\n <ConversationComposer\n handleSendMessage={handleUserSend}\n placeholder={pendingAnswer ? assistant.answerPlaceholder : undefined}\n isGenerating={isGenerating}\n shouldOfferFollowUp={!showScrollToBottom}\n inputRef={inputRef}\n isBranching={isBranching}\n onCancelBranching={session.cancelEditing}\n modelSelector={paMode ? undefined : modelSelector}\n skills={composerSkills}\n onBrowseSkills={browseSkills}\n contextUsage={latestContextUsage}\n isConversationEmpty={messageHistory.length === 0 && !isBranching}\n leftButton={\n paMode ? (\n <PaModeBadge icon={<ArbiBotIcon size={16} className=\"shrink-0\" />} />\n ) : (\n <ComposerAccessories>\n {composerAccessory}\n {threadDocument.unavailable ? (\n <DraftingDocChip\n state=\"unavailable\"\n title=\"\"\n onDetach={() => threadDocument.bind(null)}\n />\n ) : draftingDocument ? (\n <DraftingDocChip\n title={getDocumentTitle(draftingDocument)}\n onDetach={\n threadDocument.documentId ? () => threadDocument.bind(null) : undefined\n }\n />\n ) : (\n attachableDocument && (\n <DraftingDocChip\n state=\"attach\"\n title={getDocumentTitle(attachableDocument)}\n onAttach={() => threadDocument.bind(attachableDocument.external_id)}\n />\n )\n )}\n </ComposerAccessories>\n )\n }\n rightButton={\n <ComposerTools>\n {composerTools}\n <MicButton inputRef={inputRef} />\n <ConversationSendButton\n onClick={handleUserSend}\n hasOrphanedMessage={!isGenerating && orphanedMessage !== null}\n />\n </ComposerTools>\n }\n />\n )}\n </ComposerContainer>\n </ComposerDock>\n {slashWorkflows.launcher}\n </ConversationBody>\n )\n}\n\n/**\n * The open conversation's thread, connected: its messages and the composer, with no tabs or history\n * around it. `ConnectedConversationPage` is the same thread in the tabbed workspace page.\n */\nexport function ConnectedConversationThread({\n conversationId,\n modelSelector,\n}: ConnectedConversationThreadProps) {\n const session = useArbiSessionStore()\n useEffect(() => {\n if (conversationId === undefined) return\n if (session.getState().conversationId !== conversationId) {\n session.setConversationId(conversationId)\n }\n }, [conversationId, session])\n return <ConversationThread modelSelector={modelSelector} />\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * ConnectedConversationThread\n *\n * The open conversation, connected: its messages (or what an empty one shows), and the composer\n * dock under them (a failed or orphaned turn's alert, the agent's pending question, the running\n * turn's control bar, the composer). It reads and writes the session; what is the host's own (its\n * model picker, what the conversation reads, a settings button) comes in as props.\n */\n\nimport {\n ArbiBotHeadIcon as ArbiBotIcon,\n READ_ONLY_MEETING_TEXT,\n buildPendingFirstTurn,\n getOrphanedLastMessage,\n refuseChatWhenOffline,\n resolveConfigModel,\n selectDraft,\n transcriptSegmentOf,\n useAnswerAgentQuestion,\n useArbiSession,\n useArbiSessionState,\n useArbiSessionStore,\n useAskAgain,\n useAutoSendDraft,\n useConfig,\n useConnectRunStream,\n useConversationData,\n useConversationIsShared,\n useConversationItems,\n useConversationTurn,\n useDocumentAutoAdd,\n useDocuments,\n useThreadDocument,\n useUserSettings,\n useWorkspaceRole,\n} from '@arbidocs/react'\nimport { getDocumentTitle } from '@arbidocs/react/ui'\nimport { useOpenDocumentId, useTurnDocumentId, useAssistantCopy } from '@arbidocs/react/shell'\nimport type { ChatMessage as Message } from '@arbidocs/sdk/browser'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { ConversationFilter } from '../../chat/TranscriptLine'\nimport { ComposerAccessories, ComposerTools, PaModeBadge } from '../../chat/ConversationParts'\nimport { ComposerContainer, ComposerDock, ScrollToBottom } from '../../chat/Composer'\nimport {\n ConversationBody,\n ConversationLoading,\n ConversationMain,\n} from '../../chat/ConversationLayout'\nimport { DraftingDocChip } from '../../chat/DraftingDocChip'\nimport { PendingAnswerBar } from '../../chat/PendingAnswerBar'\nimport { TaskEmptyState } from '../../agents/tasks/TaskEmptyState'\nimport {\n CallConversationEmpty,\n ProvisionalSpeakers,\n} from '../../calls/connected/CallConversationParts'\nimport { useProvisionalSpeakers } from '../../calls/connected/provisional-speakers'\nimport {\n TranscriptRunsContext,\n threadTranscriptRuns,\n} from '../../calls/connected/transcript-runs-context'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { useStartRequestedDraft } from './useStartRequestedDraft'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { ConnectedConversationStart } from '../workflows/ConnectedConversationStart'\nimport { useSlashSkillWorkflows } from '../workflows/useSlashSkillWorkflows'\nimport { AgentControlBar } from './AgentControlBar'\nimport { AskAgainContext } from './AskAgainContext'\nimport { ConnectedConversationEmpty } from './ConnectedConversationEmpty'\nimport { ConnectedMessageArea as MessageArea } from './ConnectedMessageArea'\nimport { ConversationAlert } from './ConversationAlert'\nimport { ConversationComposer } from './ConversationComposer'\nimport { DocumentTargetNotice } from './DocumentTargetNotice'\nimport { createDocumentSwitchGuard, switchInScope } from './documentSwitch'\nimport { ConversationSendButton } from './ConversationSendButton'\nimport { MicButton } from './MicButton'\nimport type { MessageTimerSubscription } from './MessageLiveSeconds'\nimport { useMeetingThreadMessages } from './useMeetingThreadMessages'\nimport { useThreadScroll } from './useThreadScroll'\n\nexport interface ConnectedConversationThreadProps {\n /** Controlled selection, mirrored into the shared session without route parsing. */\n conversationId?: string | null\n /** The composer's model picker (the host's). Absent: none. */\n modelSelector?: ReactNode\n}\n\n/** The thread body as the page frames it: the page fills the composer's context and tools. */\ninterface ConversationThreadProps {\n modelSelector?: ReactNode\n /** Left in the composer: what the conversation reads (the page's context button). */\n composerAccessory?: ReactNode\n /** Beside the microphone (the page's developer settings button). */\n composerTools?: ReactNode\n}\n\nexport function ConversationThread({\n modelSelector,\n composerAccessory,\n composerTools,\n}: ConversationThreadProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const session = useArbiSessionStore()\n const environment = useBlocksEnvironment()\n const { notify, openUpload, openSkillsBrowser } = environment\n\n // Generation state for all conversations (background detection, stream control).\n const generatingConversations = useArbiSessionState((state) => state.generating)\n const { data: userSettings } = useUserSettings()\n\n const currentConversationId = useArbiSessionState((state) => state.conversationId)\n // The open task, if it is one: an empty task shows itself rather than the generic empty page.\n const { conversations } = useConversationItems()\n const currentTask = currentConversationId\n ? conversations.find((c) => c.external_id === currentConversationId)\n : undefined\n\n // The assistant message currently streaming in this conversation: target for out-of-band\n // control (pause / resume / status probe).\n const streamingAssistantId = currentConversationId\n ? (generatingConversations[currentConversationId]?.streamingAssistantId ?? null)\n : null\n\n const editingMessageId = useArbiSessionState((state) => state.editingMessageId)\n\n const {\n selectedWorkspaceId,\n parentMessageId,\n messageHistory,\n threadsLoading,\n isEmptyConversation,\n } = useConversationData()\n\n // Attach to a run that is generating in THIS conversation but that this client did not start\n // itself: a background/scheduled agent run, or the same account on another device.\n useConnectRunStream(currentConversationId, messageHistory)\n\n // The in-flight assistant message drives the docked control bar's live status and steps\n // drawer; its timer subscription feeds the elapsed ticker.\n const streamingMessage = useMemo(\n () =>\n streamingAssistantId\n ? (messageHistory as Message[]).find((m) => m.external_id === streamingAssistantId)\n : undefined,\n [messageHistory, streamingAssistantId]\n )\n const queuedUserMessages = useMemo(\n () =>\n (messageHistory as Message[])\n .filter((message) => message.role === 'user' && message.status === 'queued')\n .map(({ external_id, content }) => ({ external_id, content })),\n [messageHistory]\n )\n const streamingLiveTimer = useMemo<MessageTimerSubscription | undefined>(\n () =>\n streamingMessage\n ? {\n conversationId: streamingMessage.conversation_ext_id,\n messageId: streamingMessage.external_id,\n isUser: false,\n isGenerating: true,\n }\n : undefined,\n [streamingMessage]\n )\n\n // The agent paused to ask a question (ask_user / Human-in-the-loop). Find the latest assistant\n // message still awaiting an answer, so the prompt docks (chips + the composer) instead of a\n // mid-stream input card.\n const pendingAnswer = useMemo(() => {\n for (let i = messageHistory.length - 1; i >= 0; i--) {\n const m = messageHistory[i]\n if (m.role === 'assistant' && m.pendingUserInput) {\n return {\n request: m.pendingUserInput,\n assistantMessageExtId: m.external_id,\n conversationExtId: m.conversation_ext_id,\n }\n }\n }\n return null\n }, [messageHistory])\n\n // Send an answer to a pending agent question (from a chip or the composer).\n const answerQuestion = useAnswerAgentQuestion()\n const submitAnswer = useCallback(\n async (answer: string) => {\n const trimmed = answer.trim()\n if (!pendingAnswer || !trimmed) return\n session.setDraft('')\n try {\n await answerQuestion(pendingAnswer, trimmed)\n } catch (err) {\n console.error('Failed to answer agent', err)\n notify('Failed to send your answer', 'error')\n }\n },\n [pendingAnswer, answerQuestion, session, notify]\n )\n\n // All workspace documents, for counts and auto-add.\n const { data: allWorkspaceDocuments = [], isLoading: isLoadingWorkspaceDocuments } =\n useDocuments()\n\n // The parent message's configuration, for the model it ran with.\n const parentMessage = useMemo(() => {\n if (!parentMessageId || !messageHistory) return null\n return messageHistory.find((m) => m.external_id === parentMessageId)\n }, [parentMessageId, messageHistory])\n const parentConfigName = parentMessage?.config_ext_id || userSettings?.last_config || 'default'\n const { data: parentConfig } = useConfig(parentConfigName, { enabled: !!parentConfigName })\n\n // Reconcile stale isGenerating state with backend truth. If the backend says the last message\n // is completed/failed but this client still thinks it is generating, clear the stale state\n // (covers a silent SSE death, a page reload, etc.).\n useEffect(() => {\n for (const [convId, genState] of Object.entries(generatingConversations)) {\n if (!genState.isGenerating) continue\n const conv = conversations.find((c) => c.external_id === convId)\n if (!conv?.last_message_status) continue\n if (conv.last_message_status === 'completed' || conv.last_message_status === 'failed') {\n session.setConversationGenerating(convId, false, null)\n }\n }\n }, [conversations, generatingConversations, session])\n\n // Continuing a thread or branch runs on its config, and the picker shows that config's model,\n // unless the person picks another. Only runs on parentMessageId changes, never on\n // parentConfig/messageHistory updates, so the person can freely toggle the model picker without\n // it being overwritten. Skipped while the parent is a freshly-created streaming message\n // (config_ext_id is null).\n useEffect(() => {\n if (!parentMessageId) return\n const parentMsg = messageHistory?.find((m) => m.external_id === parentMessageId)\n if (parentMsg?.config_ext_id && parentConfig && 'QueryLLM' in parentConfig) {\n session.setChatModelName(resolveConfigModel(parentConfig))\n session.setChatModel(parentMsg.config_ext_id)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMessageId])\n\n // Thread vs chat: in a thread, a comment without an @Assistant mention never expects a reply.\n // A shared chat is a thread, and so is a solo one set to answer only when @mentioned.\n const isSharedConversation = useConversationIsShared()\n const assistantMode =\n isSharedConversation || currentTask?.assistant_mode === 'mentioned' ? 'mentioned' : 'always'\n\n // A thread call's meeting thread is read-only for workspace guests (they may join the call).\n const isGuest = useWorkspaceRole() === 'guest'\n const meetingReadOnly = isGuest && !!currentTask?.meeting_call_ext_id\n const meetingCallExtId = currentTask?.meeting_call_ext_id ?? null\n const threadMessages = useMeetingThreadMessages(meetingCallExtId, messageHistory)\n\n const [firstTurnPrompt, setFirstTurnPrompt] = useState<string | null>(null)\n const currentUserExtId = useArbiSession().user?.external_id ?? ''\n const pendingFirstTurn = useMemo(\n () => (firstTurnPrompt ? buildPendingFirstTurn(firstTurnPrompt, currentUserExtId) : null),\n [firstTurnPrompt, currentUserExtId]\n )\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const {\n isGenerating,\n hasError,\n lastFailedMessage,\n lastErrorMessage,\n handleCreateBranch,\n handleSendMessage,\n handleAbortGeneration,\n handleRetrySend,\n handleRetryOrphanedMessage,\n } = useConversationTurn({ selectedWorkspaceId, parentMessageId })\n const handleAskAgain = useAskAgain({ isGenerating, handleCreateBranch })\n\n // A PA conversation: any of its questions carries the personal_agent tool.\n const paMode = useArbiSessionState((state) => state.paMode)\n useEffect(() => {\n if (messageHistory.length === 0) return\n const hasPaTool = messageHistory.some(\n (m) =>\n m.role === 'user' && m.tools && 'personal_agent' in (m.tools as Record<string, unknown>)\n )\n if (hasPaTool !== paMode) session.setPaMode(hasPaTool)\n }, [messageHistory, paMode, session])\n\n // A meeting thread shows everything, typed comments only, or its transcript only.\n const [meetingView, setMeetingView] = useState<'all' | 'chat' | 'transcript'>('all')\n useEffect(() => setMeetingView('all'), [currentConversationId])\n const shownThreadMessages = useMemo(() => {\n if (!meetingCallExtId || meetingView === 'all') return threadMessages\n const isLine = (m: (typeof threadMessages)[number]) =>\n !!transcriptSegmentOf(m.tools ?? m.meta?.tools)\n return threadMessages.filter((m) => (meetingView === 'transcript' ? isLine(m) : !isLine(m)))\n }, [meetingCallExtId, meetingView, threadMessages])\n const transcriptLineCount = useMemo(\n () =>\n meetingCallExtId\n ? threadMessages.filter((m) => transcriptSegmentOf(m.tools ?? m.meta?.tools)).length\n : 0,\n [meetingCallExtId, threadMessages]\n )\n\n // Visible messages: while editing, the edited message and everything after it are hidden.\n const visibleMessages = useMemo(() => {\n if (!editingMessageId) return shownThreadMessages\n const editIndex = shownThreadMessages.findIndex((m) => m.external_id === editingMessageId)\n if (editIndex < 0) return shownThreadMessages\n return shownThreadMessages.slice(0, editIndex)\n }, [shownThreadMessages, editingMessageId])\n // Transcript lines group into runs per speaker; each speaker's newest line lights up as they talk.\n const transcriptRunInfo = useMemo(\n () => threadTranscriptRuns(meetingCallExtId ? visibleMessages : []),\n [meetingCallExtId, visibleMessages]\n )\n // While I am in this meeting's call: \"<speaker> is speaking\u2026\" until their line arrives.\n const threadLines = useMemo(\n () =>\n threadMessages.flatMap((m) => {\n const seg = transcriptSegmentOf(m.tools ?? m.meta?.tools)\n return seg\n ? [\n {\n speakerId: seg.speaker_ext_id,\n at: Date.parse(seg.started_at ?? m.created_at ?? '') || 0,\n },\n ]\n : []\n }),\n [threadMessages]\n )\n const provisionalSpeakers = useProvisionalSpeakers(meetingCallExtId, threadLines)\n const showProvisional = !!meetingCallExtId && meetingView !== 'chat'\n\n // The latest context usage, from the last assistant message. finalContextUsage (the true\n // per-turn total from response.completed, with spent_gbp/budget_gbp) first; agentSteps only\n // cover function_call/reasoning items, so a step's own `context` is at best a mid-turn partial.\n // They are read only for messages saved before finalContextUsage existed.\n const latestContextUsage = useMemo(() => {\n const messages = messageHistory as Message[]\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]\n if (msg.role !== 'assistant') continue\n if (msg.finalContextUsage) return msg.finalContextUsage\n if (!msg.agentSteps) continue\n for (let j = msg.agentSteps.length - 1; j >= 0; j--) {\n if (msg.agentSteps[j].context) return msg.agentSteps[j].context\n }\n }\n return null\n }, [messageHistory])\n\n // The edited message's parent, which the edit branches from.\n const editedMessageParentId = useMemo(() => {\n if (!editingMessageId) return null\n const editedMsg = messageHistory.find((m) => m.external_id === editingMessageId)\n return editedMsg?.parent_message_ext_id ?? null\n }, [editingMessageId, messageHistory])\n\n const isBranching = !!editingMessageId\n\n // Send, after an orphaned question (one left without its answer) if there is one.\n const handleSendMessageWithOrphanCheck = useCallback(async () => {\n const orphanedMessage = getOrphanedLastMessage(messageHistory, isGenerating, assistantMode)\n const draftMessage = selectDraft(session.getState()).trim()\n\n if (orphanedMessage) {\n // An orphaned question and an empty draft: ask it again.\n if (draftMessage === '') {\n await handleRetryOrphanedMessage(\n orphanedMessage.content,\n orphanedMessage.parent_message_ext_id ?? null,\n orphanedMessage.external_id\n )\n return\n }\n\n // An orphaned question and a new draft: the draft replaces it.\n await handleRetryOrphanedMessage(\n draftMessage,\n orphanedMessage.parent_message_ext_id ?? null,\n orphanedMessage.external_id\n )\n session.setDraft('')\n return\n }\n\n // No orphaned question: a normal send.\n await handleSendMessage()\n }, [\n messageHistory,\n isGenerating,\n assistantMode,\n handleRetryOrphanedMessage,\n handleSendMessage,\n session,\n ])\n\n // An edit sends as a branch; anything else sends normally.\n const handleSendMessageForEdit = useCallback(async () => {\n if (isBranching && editedMessageParentId) {\n const draftMessage = selectDraft(session.getState()).trim()\n if (!draftMessage) return\n\n // Clear the timers of the messages after the branch point.\n const editIndex = messageHistory.findIndex((m) => m.external_id === editingMessageId)\n if (editIndex >= 0) {\n Object.keys(session.getState().messageTimers).forEach((key) => {\n const keyIndex = parseInt(key)\n if (!isNaN(keyIndex) && keyIndex > editIndex) session.clearMessageTimer(key)\n })\n }\n\n session.completeEditing()\n session.setDraft('')\n await handleCreateBranch(editedMessageParentId, draftMessage)\n return\n }\n\n await handleSendMessageWithOrphanCheck()\n }, [\n isBranching,\n editedMessageParentId,\n editingMessageId,\n messageHistory,\n session,\n handleCreateBranch,\n handleSendMessageWithOrphanCheck,\n ])\n\n // While the agent waits on an answer, the composer answers the question (interject) instead of\n // sending a new message.\n const handleComposerSend = useCallback(() => {\n const draft = selectDraft(session.getState())\n if (pendingAnswer) {\n submitAnswer(draft)\n return\n }\n // A brand-new chat has no thread to show until the server confirms the question.\n if (!currentConversationId) setFirstTurnPrompt(draft.trim())\n handleSendMessageForEdit()\n }, [pendingAnswer, currentConversationId, submitAnswer, handleSendMessageForEdit, session])\n\n // The pending first turn lasts only while the new chat generates; once the thread exists it is\n // replaced.\n useEffect(() => {\n if (!isGenerating) setFirstTurnPrompt(null)\n }, [isGenerating])\n\n // The person's send (Enter or the Send button): `/skill` or `/skill text` opens the skill's form\n // instead of sending a bare command. Auto-sends of launched drafts go through handleComposerSend\n // directly, so a launched skill is never intercepted again.\n const slashWorkflows = useSlashSkillWorkflows({ landing: 'current-chat' })\n // The composer's slash menu offers the built-in skills; a picked one opens this launcher.\n const composerSkills = useMemo(\n () => ({ openFor: slashWorkflows.openFor }),\n [slashWorkflows.openFor]\n )\n const browseSkills = useMemo(\n () => (openSkillsBrowser ? () => openSkillsBrowser() : undefined),\n [openSkillsBrowser]\n )\n\n const { showScrollToBottom, scrollToBottom } = useThreadScroll({\n currentConversationId,\n parentMessageId,\n messageHistory,\n })\n\n const scopeKey = JSON.stringify([selectedWorkspaceId, currentConversationId])\n const documentSwitchGuards = useRef(\n new Map<string, ReturnType<typeof createDocumentSwitchGuard>>()\n )\n if (!documentSwitchGuards.current.has(scopeKey)) {\n documentSwitchGuards.current.set(scopeKey, createDocumentSwitchGuard())\n }\n const switchingDocument = documentSwitchGuards.current.get(scopeKey)!\n const [, setSwitchState] = useState({ scope: scopeKey, pending: false, failed: false })\n const documentSwitchPending = switchingDocument.isPending()\n const documentSwitchFailed = switchingDocument.hasFailed()\n const handleUserSend = useCallback(() => {\n if (switchingDocument.isPending()) return\n if (switchingDocument.hasFailed()) {\n session.setAutoSendDraft(false)\n switchingDocument.acknowledgeFailure()\n setSwitchState({ scope: scopeKey, pending: false, failed: false })\n }\n const draft = selectDraft(session.getState())\n if (!pendingAnswer && slashWorkflows.openForDraft(draft)) {\n session.setDraft('')\n return\n }\n // Offline, nothing is sent.\n if (refuseChatWhenOffline()) return\n // Their message lands at the bottom of the thread: bring the view down to it, even when they\n // had scrolled up to read a streaming answer.\n scrollToBottom()\n handleComposerSend()\n }, [\n pendingAnswer,\n slashWorkflows,\n handleComposerSend,\n scrollToBottom,\n session,\n switchingDocument,\n scopeKey,\n ])\n\n // Focus the input when editing starts, its caret at the end.\n useEffect(() => {\n if (editingMessageId && inputRef.current) {\n const textarea = inputRef.current\n textarea.focus()\n const len = textarea.value.length\n textarea.setSelectionRange(len, len)\n }\n }, [editingMessageId])\n\n // Drafting mode: the thread's own document, which the assistant reads on demand and edits as\n // tracked changes (sent as a reference, `active_artifact_ext_id`): the one the thread is bound to,\n // else one made in it and open. A document merely open beside an unbound thread is only offered\n // (attach): being open never turns a question into an edit.\n const threadDocument = useThreadDocument()\n const turnDocumentId = useTurnDocumentId({\n boundDocumentId: threadDocument.documentId,\n conversationId: currentConversationId,\n workspaceId: selectedWorkspaceId,\n })\n const assistant = useAssistantCopy()\n const openDocumentId = useOpenDocumentId({ workspaceId: selectedWorkspaceId })\n const findDocument = (id: string | null) =>\n id ? allWorkspaceDocuments?.find((d) => d.external_id === id) : undefined\n const draftingDocument = findDocument(turnDocumentId)\n const attachableDocument = turnDocumentId ? undefined : findDocument(openDocumentId)\n const differentVisibleDocument = Boolean(\n turnDocumentId && openDocumentId && turnDocumentId !== openDocumentId\n )\n const targetTitle = draftingDocument ? getDocumentTitle(draftingDocument) : turnDocumentId\n const visibleDocument = findDocument(openDocumentId)\n const visibleTitle = visibleDocument ? getDocumentTitle(visibleDocument) : openDocumentId\n const sameTitle = targetTitle && visibleTitle && targetTitle === visibleTitle\n const switchThreadDocument = (documentId: string | null) => {\n if (isGenerating || switchingDocument.isPending()) return\n const isCurrent = () => {\n const state = session.getState()\n return (\n state.workspaceId === selectedWorkspaceId && state.conversationId === currentConversationId\n )\n }\n setSwitchState({ scope: scopeKey, pending: true, failed: false })\n void switchInScope(\n switchingDocument,\n () => threadDocument.bind(documentId),\n isCurrent,\n (pending) => setSwitchState({ scope: scopeKey, pending, failed: false }),\n () => {\n setSwitchState({ scope: scopeKey, pending: false, failed: true })\n session.setAutoSendDraft(false)\n }\n )\n }\n\n // Empty chats follow workspace documents; sending the first message freezes that selection.\n useDocumentAutoAdd({\n selectedWorkspaceId,\n allWorkspaceDocuments,\n hasContent:\n !isEmptyConversation || Boolean(parentMessageId || messageHistory?.length || isGenerating),\n })\n\n // A draft handed over by a launch or the workspace home's ask bar sends on arrival, but only\n // after the documents query settles. MUST stay declared after useDocumentAutoAdd: effects run in\n // declaration order, and sending before auto-add seeds the new chat's documents posts the\n // question with none attached.\n useAutoSendDraft(\n handleComposerSend,\n !isLoadingWorkspaceDocuments && !documentSwitchPending && !documentSwitchFailed,\n () => !switchingDocument.isPending()\n )\n // `openNewConversation({ prompt })`: the prompt becomes the draft, sent as above.\n useStartRequestedDraft()\n\n const orphanedMessage = getOrphanedLastMessage(messageHistory, isGenerating, assistantMode)\n\n return (\n <ConversationBody>\n <ConversationMain data-testid={`conversation-${currentConversationId}`}>\n {threadsLoading ? (\n <ConversationLoading />\n ) : messageHistory.length > 0 || isBranching ? (\n <AskAgainContext.Provider value={handleAskAgain}>\n <TranscriptRunsContext.Provider value={transcriptRunInfo}>\n {meetingCallExtId && transcriptLineCount > 0 && (\n <div className=\"flex justify-center py-2\">\n <ConversationFilter<typeof meetingView>\n value={meetingView}\n onChange={setMeetingView}\n options={[\n { value: 'all', label: 'All' },\n { value: 'chat', label: 'Chat', testId: 'chat-filter' },\n {\n value: 'transcript',\n label: 'Transcript',\n count: transcriptLineCount,\n testId: 'transcript-filter',\n },\n ]}\n />\n </div>\n )}\n <MessageArea ref={containerRef} messages={visibleMessages} />\n {showProvisional && <ProvisionalSpeakers ids={provisionalSpeakers} />}\n </TranscriptRunsContext.Provider>\n </AskAgainContext.Provider>\n ) : meetingCallExtId ? (\n // A meeting thread with nothing yet: call-aware, not the upload prompt.\n <div>\n <CallConversationEmpty callExtId={meetingCallExtId} />\n <ProvisionalSpeakers ids={provisionalSpeakers} />\n </div>\n ) : currentTask && currentTask.status != null ? (\n <TaskEmptyState task={currentTask} />\n ) : pendingFirstTurn && isGenerating ? (\n <MessageArea ref={containerRef} messages={pendingFirstTurn} />\n ) : (\n <ConnectedConversationEmpty\n hasDocuments={allWorkspaceDocuments.length > 0}\n onUsePrompt={(text) => {\n session.setDraft(text)\n inputRef?.current?.focus()\n }}\n onAddDocuments={(files) => openUpload?.(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={\n openUpload ? () => openUpload(undefined, { source: 'email' }) : undefined\n }\n below={<ConnectedConversationStart />}\n />\n )}\n </ConversationMain>\n\n {/* The composer dock: the thread's alerts and bars, then the composer. */}\n <ComposerDock\n floating={\n <ScrollToBottom\n className=\"left-auto right-3 translate-x-0\"\n show={showScrollToBottom}\n onClick={scrollToBottom}\n />\n }\n >\n <ComposerContainer className=\"mx-auto max-w-[49rem] px-3 sm:px-6\">\n {/* A question left without its answer (stopped, reloaded, the backend failed), else a\n send that failed. */}\n {orphanedMessage ? (\n <ConversationAlert\n type={\n messageHistory[messageHistory.length - 1]?.role === 'assistant' &&\n messageHistory[messageHistory.length - 1].status === 'failed'\n ? 'backend-failed'\n : 'orphaned'\n }\n message={orphanedMessage}\n onRetryOrphaned={handleRetryOrphanedMessage}\n />\n ) : hasError && lastFailedMessage ? (\n <ConversationAlert\n type=\"send-failed\"\n errorMessage={lastErrorMessage || undefined}\n onRetrySend={handleRetrySend}\n isRetrying={isGenerating}\n />\n ) : null}\n\n {pendingAnswer && (\n <PendingAnswerBar\n key={pendingAnswer.assistantMessageExtId}\n question={pendingAnswer.request.question}\n options={pendingAnswer.request.options}\n timeoutSeconds={pendingAnswer.request.timeout_seconds}\n onPick={submitAnswer}\n />\n )}\n\n {/* Out-of-band control (pause / resume / status) for the running turn. Hidden while\n ask_user is pending: that flow owns the composer. */}\n {isGenerating && !pendingAnswer && streamingAssistantId && (\n <AgentControlBar\n key={streamingAssistantId}\n assistantMessageExtId={streamingAssistantId}\n agentSteps={streamingMessage?.agentSteps}\n liveTimer={streamingLiveTimer}\n onStop={handleAbortGeneration}\n queuedMessages={queuedUserMessages}\n />\n )}\n\n {meetingReadOnly ? (\n <p\n className=\"py-3 text-center text-sm text-muted-foreground\"\n data-testid=\"meeting-thread-read-only\"\n >\n {READ_ONLY_MEETING_TEXT}\n </p>\n ) : (\n <>\n {documentSwitchPending && (\n <p className=\"mx-3 mb-2 text-sm text-muted-foreground\" role=\"status\">\n Switching this thread\u2019s document\u2026\n </p>\n )}\n {documentSwitchFailed && (\n <div className=\"mx-3 mb-2 text-sm text-destructive\" role=\"status\">\n <p>\n The document switch failed. Check which document this thread will use before\n sending.\n </p>\n <button\n type=\"button\"\n className=\"underline\"\n onClick={() => {\n session.setAutoSendDraft(false)\n switchingDocument.acknowledgeFailure()\n setSwitchState({ scope: scopeKey, pending: false, failed: false })\n }}\n >\n Use the current thread document\n </button>\n </div>\n )}\n {!paMode && differentVisibleDocument && (\n <DocumentTargetNotice\n target={sameTitle ? `${targetTitle} (${turnDocumentId})` : targetTitle!}\n visible={sameTitle ? `${visibleTitle} (${openDocumentId})` : visibleTitle!}\n onSwitch={\n visibleDocument ? () => switchThreadDocument(openDocumentId!) : undefined\n }\n disabled={isGenerating || documentSwitchPending}\n />\n )}\n <ConversationComposer\n handleSendMessage={handleUserSend}\n placeholder={pendingAnswer ? assistant.answerPlaceholder : undefined}\n isGenerating={isGenerating}\n shouldOfferFollowUp={!showScrollToBottom}\n inputRef={inputRef}\n isBranching={isBranching}\n onCancelBranching={session.cancelEditing}\n modelSelector={paMode ? undefined : modelSelector}\n skills={composerSkills}\n onBrowseSkills={browseSkills}\n contextUsage={latestContextUsage}\n isConversationEmpty={messageHistory.length === 0 && !isBranching}\n leftButton={\n paMode ? (\n <PaModeBadge icon={<ArbiBotIcon size={16} className=\"shrink-0\" />} />\n ) : (\n <ComposerAccessories>\n {composerAccessory}\n {threadDocument.unavailable ? (\n <DraftingDocChip\n state=\"unavailable\"\n title=\"\"\n disabled={isGenerating || documentSwitchPending}\n onDetach={() => switchThreadDocument(null)}\n />\n ) : draftingDocument ? (\n <DraftingDocChip\n title={getDocumentTitle(draftingDocument)}\n disabled={isGenerating || documentSwitchPending}\n onDetach={\n threadDocument.documentId ? () => switchThreadDocument(null) : undefined\n }\n />\n ) : (\n attachableDocument && (\n <DraftingDocChip\n state=\"attach\"\n title={getDocumentTitle(attachableDocument)}\n disabled={isGenerating || documentSwitchPending}\n onAttach={() => switchThreadDocument(attachableDocument.external_id)}\n />\n )\n )}\n </ComposerAccessories>\n )\n }\n rightButton={\n <ComposerTools>\n {composerTools}\n <MicButton inputRef={inputRef} />\n <ConversationSendButton\n onClick={handleUserSend}\n disabled={documentSwitchPending}\n hasOrphanedMessage={!isGenerating && orphanedMessage !== null}\n />\n </ComposerTools>\n }\n />\n </>\n )}\n </ComposerContainer>\n </ComposerDock>\n {slashWorkflows.launcher}\n </ConversationBody>\n )\n}\n\n/**\n * The open conversation's thread, connected: its messages and the composer, with no tabs or history\n * around it. `ConnectedConversationPage` is the same thread in the tabbed workspace page.\n */\nexport function ConnectedConversationThread({\n conversationId,\n modelSelector,\n}: ConnectedConversationThreadProps) {\n const session = useArbiSessionStore()\n useEffect(() => {\n if (conversationId === undefined) return\n if (session.getState().conversationId !== conversationId) {\n session.setConversationId(conversationId)\n }\n }, [conversationId, session])\n return <ConversationThread modelSelector={modelSelector} />\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["orphanedMessage"]
|
|
7
7
|
}
|
|
@@ -15,7 +15,16 @@ import {
|
|
|
15
15
|
useTransientView,
|
|
16
16
|
useWorkspaces
|
|
17
17
|
} from "@arbidocs/react";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Button,
|
|
20
|
+
DropdownMenu,
|
|
21
|
+
DropdownMenuContent,
|
|
22
|
+
DropdownMenuItem,
|
|
23
|
+
DropdownMenuTrigger,
|
|
24
|
+
getDocumentTitle,
|
|
25
|
+
getEntityLabel
|
|
26
|
+
} from "@arbidocs/react/ui";
|
|
27
|
+
import { CalendarClock, MoreHorizontal } from "lucide-react";
|
|
19
28
|
import { parseVersionRef } from "@arbidocs/react/artifacts";
|
|
20
29
|
import { ConnectedThreadTabs, useThreadTabs } from "../threads/ConnectedThreadTabs.js";
|
|
21
30
|
import { ConnectedThreadTaskBar } from "../tasks/ConnectedThreadTaskBar.js";
|
|
@@ -26,6 +35,7 @@ import { useAddFromUrls } from "../files/upload/useAddFromUrls.js";
|
|
|
26
35
|
import { UrlInputDialog } from "../../files/UrlInputDialog.js";
|
|
27
36
|
import { closeDialog, openDialog } from "../../provider/dialog-registry.js";
|
|
28
37
|
import { useBlocksEnvironment } from "../../provider/environment.js";
|
|
38
|
+
import { useOpenWorkflow } from "../workflows/useOpenWorkflow.js";
|
|
29
39
|
import { filterConversationRows } from "../../agents/tasks/ConversationListView.js";
|
|
30
40
|
import { FilterInput } from "../../shell/FilterInput.js";
|
|
31
41
|
import { ConnectedFilterDropdown as FilterDropdown } from "../ConnectedFilterDropdown.js";
|
|
@@ -56,6 +66,7 @@ function ConnectedConversationPage({
|
|
|
56
66
|
const { openFiles } = useBlocksEnvironment();
|
|
57
67
|
useEffect(() => () => session.setActiveCitationKey(null), [session]);
|
|
58
68
|
const { isInChat, setActiveTab } = useThreadTabs();
|
|
69
|
+
const openWorkflow = useOpenWorkflow();
|
|
59
70
|
const [doneFilter, setDoneFilter] = useTransientView(
|
|
60
71
|
"threads.done",
|
|
61
72
|
"exclude"
|
|
@@ -234,7 +245,30 @@ function ConnectedConversationPage({
|
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
247
|
),
|
|
237
|
-
showPaMode && /* @__PURE__ */ jsx(NewPaChatButton, { isCompact, onClick: () => startNewChat(true) })
|
|
248
|
+
showPaMode && /* @__PURE__ */ jsx(NewPaChatButton, { isCompact, onClick: () => startNewChat(true) }),
|
|
249
|
+
!isInChat && openWorkflow && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
250
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
251
|
+
Button,
|
|
252
|
+
{
|
|
253
|
+
variant: "ghost",
|
|
254
|
+
size: "icon",
|
|
255
|
+
"aria-label": "More actions",
|
|
256
|
+
"data-testid": "threads-more",
|
|
257
|
+
children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
|
|
258
|
+
}
|
|
259
|
+
) }),
|
|
260
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: /* @__PURE__ */ jsxs(
|
|
261
|
+
DropdownMenuItem,
|
|
262
|
+
{
|
|
263
|
+
onSelect: () => openWorkflow(void 0, { preset: "schedule" }),
|
|
264
|
+
"data-testid": "threads-schedule-task",
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ jsx(CalendarClock, { className: "size-4" }),
|
|
267
|
+
" Schedule a task\u2026"
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
) })
|
|
271
|
+
] })
|
|
238
272
|
] })
|
|
239
273
|
}
|
|
240
274
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/connection/conversation/ConnectedConversationPage.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useThreadTagFilter } from '../threads/useThreadTagFilter'\nimport { TagFilterChips } from '../tags/TagCombineFilter'\n/** The tabbed conversation page; host-specific composer context stays in a slot. */\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport {\n useArbiSessionStore,\n useArbiSessionState,\n useConversationDocuments,\n useConversationItems,\n useDocuments,\n useTags,\n useWorkspaceRole,\n useUserSettings,\n useTransientView,\n useWorkspaces,\n} from '@arbidocs/react'\nimport { getDocumentTitle, getEntityLabel } from '@arbidocs/react/ui'\nimport { parseVersionRef } from '@arbidocs/react/artifacts'\nimport { ConnectedThreadTabs, useThreadTabs } from '../threads/ConnectedThreadTabs'\nimport { ConnectedThreadTaskBar } from '../tasks/ConnectedThreadTaskBar'\nimport { ConnectedThreadsPage } from '../threads-page/ConnectedThreadsPage'\nimport { ConnectedConversationCallActions } from '../../calls/connected/ConnectedConversationCallActions'\nimport { ConnectedWorkspaceEntity } from '../entities/ConnectedWorkspaceEntity'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { UrlInputDialog } from '../../files/UrlInputDialog'\nimport { closeDialog, openDialog } from '../../provider/dialog-registry'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { filterConversationRows } from '../../agents/tasks/ConversationListView'\nimport { FilterInput } from '../../shell/FilterInput'\nimport { ConnectedFilterDropdown as FilterDropdown } from '../ConnectedFilterDropdown'\nimport {\n ConversationHistoryPane,\n ConversationHeaderActions,\n ConversationSettingsButton,\n NewChatButton,\n NewPaChatButton,\n} from '../../chat/ConversationParts'\nimport { ConversationHeader, ConversationRoot } from '../../chat/ConversationLayout'\nimport { SemanticSearchButton } from './SemanticSearchButton'\nimport { ConversationThread } from './ConnectedConversation'\nimport { ConnectedChatContextButton } from '../chat-context/ConnectedChatContextButton'\nimport type { UploadSource } from '../../provider/environment'\n\nconst DEFAULT_THREAD_VISIBILITY = { me: null, workspace: null } as {\n me: 'include' | 'exclude' | null\n workspace: 'include' | 'exclude' | null\n}\nexport interface ConnectedConversationPageProps {\n /** Controlled selection: opens the stored (or, with null, a new) thread's tab, without route parsing. */\n conversationId?: string | null\n /** The composer's model picker (the host's). Absent: none. */\n modelSelector?: ReactNode\n /** Wraps the conversation search results (the host's crash boundary, say). */\n ResultsBoundary?: ComponentType<{ children: ReactNode }>\n /** Where \"go to documents\" leads. Absent: the environment's `openFiles`. */\n onGoToDocuments?: () => void\n}\n\n/**\n * The open conversation in the tabbed workspace page: its thread tabs, task bar, history with\n * filters and search, and the URL-upload dialog around `ConnectedConversationThread`'s body.\n */\nexport function ConnectedConversationPage({\n conversationId,\n modelSelector,\n ResultsBoundary,\n onGoToDocuments,\n}: ConnectedConversationPageProps) {\n const session = useArbiSessionStore()\n useEffect(() => {\n if (conversationId === undefined) return\n const tabId = conversationId ?? 'new'\n if (session.getState().tabs.activeThread !== tabId) session.openThreadTab(conversationId)\n }, [conversationId, session])\n const { openFiles } = useBlocksEnvironment()\n useEffect(() => () => session.setActiveCitationKey(null), [session])\n const { isInChat, setActiveTab } = useThreadTabs()\n const [doneFilter, setDoneFilter] = useTransientView(\n 'threads.done',\n 'exclude' as 'include' | 'exclude' | null\n )\n const [visibilityFilters, setVisibilityFilters] = useTransientView(\n 'threads.visibility',\n DEFAULT_THREAD_VISIBILITY\n )\n const [meetingFilter, setMeetingFilter] = useTransientView(\n 'threads.meetings',\n null as 'include' | 'exclude' | null\n )\n const [threadFilter, setThreadFilter] = useTransientView('topbar.filter', '')\n\n // Get experimental flags from API\n const { data: userSettings } = useUserSettings()\n const isDeveloper = userSettings?.developer === true\n const showPaMode = userSettings?.show_pa_mode ?? false\n const searchEnabled = userSettings?.show_conversation_search === true\n // Conversation (message) semantic search is opened from the header icon; it lives on the list, so opening it from a\n // thread goes back to the list first.\n const [semanticSearchOpen, setSemanticSearchOpen] = useState(false)\n\n // Handlers for modals\n const handleSelectSource = (source: UploadSource) => {\n // Match the Files \"Add\" menu exactly: internet opens the URL dialog directly,\n // the others open their focused upload modal. (No processing-modal guard.)\n if (source === 'internet') {\n setUrlDialogOpen(true)\n return\n }\n openDialog('upload', { tab: 'upload', source })\n }\n\n const handleGoToDocuments = () => {\n // Switch to the Documents/Files page instead of opening the in-chat modal\n // preview. Dismiss the modal first in case another entry point left it open.\n closeDialog('conversationDocuments')\n if (onGoToDocuments) onGoToDocuments()\n else openFiles()\n }\n\n const handleStatusClick = () => {\n openDialog('conversationDocuments')\n }\n\n // Get current conversation ID from store\n const currentConversationId = useArbiSessionState((state) => state.conversationId)\n const { conversations: taskItems } = useConversationItems()\n const conversations = taskItems\n\n // Read-only members (`guest`) can read but not upload. Non-members also can't upload.\n const workspaceId = useArbiSessionState((state) => state.workspaceId)\n const { data: workspaces = [] } = useWorkspaces()\n const workspace = workspaces.find((item) => item.external_id === workspaceId)\n const workspaceRole = useWorkspaceRole()\n const isGuest = workspaceRole === 'guest'\n const canUpload = workspaceRole !== null && !isGuest\n\n // \"From the internet\" opens the URL dialog inline (parity with the Files Add\n // menu); it runs the upload itself and surfaces any error in the dialog.\n const [urlDialogOpen, setUrlDialogOpen] = useState(false)\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openDialog('upload', { tab: 'upload', files, source: 'computer' }),\n })\n const uploadDisabledReason = isGuest\n ? 'Read-only members cannot upload documents in this workspace'\n : 'You cannot upload to this workspace'\n\n // What the conversation reads: its documents, and a pinned one.\n const { data: allWorkspaceDocuments = [] } = useDocuments()\n const { data: conversationDocuments } = useConversationDocuments()\n const pinnedDocumentId = useArbiSessionState((state) => state.pinnedDocumentId)\n // A pinned older version (`doc-\u2026@v2`, \"Ask about this version\" in the viewer) names its document.\n const pinnedRef = parseVersionRef(pinnedDocumentId)\n const pinnedDocument =\n conversationDocuments?.find((d) => d.external_id === pinnedRef.docId) ??\n allWorkspaceDocuments?.find((d) => d.external_id === pinnedRef.docId)\n\n const pinnedTitle = pinnedDocument\n ? `${getDocumentTitle(pinnedDocument)}${pinnedRef.version ? ` (version ${pinnedRef.version})` : ''}`\n : null\n\n // Start a new conversation (optionally in Personal Assistant mode) in a new chat tab.\n const startNewChat = (asPaChat: boolean) => {\n session.clearConversationError(currentConversationId || 'new')\n session.startNewConversation()\n session.openThreadTab(null)\n if (asPaChat) session.setPaMode(true)\n }\n\n // The filter is part of the first tab, next to its counter, and only while that tab is selected\n // (the list). With a thread open the tab is just the glyph and the count.\n const threadFilterDropdown = (\n <FilterDropdown\n stateKey=\"threads\"\n filterStates={\n new Map([\n ['done', doneFilter],\n ['me', visibilityFilters.me],\n ['workspace', visibilityFilters.workspace],\n ['meeting', meetingFilter],\n ])\n }\n onFiltersChange={(filters) => {\n setDoneFilter(filters.get('done') ?? null)\n setMeetingFilter(filters.get('meeting') ?? null)\n setVisibilityFilters({\n me: filters.get('me') ?? null,\n workspace: filters.get('workspace') ?? null,\n })\n }}\n options={[\n {\n id: 'me',\n label: 'Only Me',\n count: conversations.filter((thread) => !thread.is_shared).length,\n },\n {\n id: 'workspace',\n label: 'Workspace',\n count: conversations.filter((thread) => thread.is_shared).length,\n },\n {\n id: 'meeting',\n label: 'Meetings',\n count: conversations.filter((thread) => !!thread.meeting_call_ext_id).length,\n },\n {\n id: 'done',\n label: 'Done',\n count: conversations.filter((thread) => thread.status === 'done').length,\n },\n ]}\n hasExternalActiveState={Boolean(threadFilter.trim())}\n renderChevron\n disableHoverOpen\n align=\"start\"\n />\n )\n\n const threadFilterBox = (\n <FilterInput\n filter={threadFilterDropdown}\n borderless\n value={threadFilter}\n onChange={setThreadFilter}\n fields={['text']}\n onFieldsChange={() => {}}\n hideFieldPicker\n ariaLabel=\"Filter threads\"\n // Done threads are hidden by default: say so, or a completed thread looks lost.\n labels={doneFilter === 'exclude' ? { placeholder: 'Done hidden' } : undefined}\n className=\"w-44 max-w-[40vw]\"\n />\n )\n // The list tab counts the threads the list shows (its filters and search applied).\n const { data: workspaceTags = [] } = useTags()\n const { tagId: threadTagId, clear: clearThreadTag } = useThreadTagFilter()\n const historyCount = filterConversationRows(\n threadTagId\n ? conversations.filter((row) => row.label_ext_ids?.includes(threadTagId))\n : conversations,\n threadFilter,\n (row) =>\n workspaceTags\n .filter((tag) => row.label_ext_ids?.includes(tag.external_id))\n .map((tag) => tag.name)\n .join(' '),\n doneFilter,\n visibilityFilters,\n meetingFilter\n ).length\n const historyFiltered =\n Boolean(threadTagId) ||\n Boolean(threadFilter.trim()) ||\n doneFilter !== null ||\n meetingFilter !== null ||\n Object.values(visibilityFilters).some(Boolean)\n\n return (\n <>\n {/* The chat scroll surface is the outer `[data-scroll-container]` (DockZone).\n This column must GROW with its content (not stay viewport-capped) so the\n sticky tab header (top-0) and sticky message-input footer (bottom-0) have\n a containing block that spans the whole thread \u2014 otherwise `position:\n sticky` is clamped to a viewport-tall box and both unpin after one\n screen of scroll. Hence `min-h-full` (fill the scroller, but no phantom\n 100vh scroll inside this nested scroller) + `shrink-0` (don't let the\n flex parent shrink this back below its content). */}\n <ConversationRoot>\n {/* Fixed Tab Header */}\n <ConversationHeader>\n <ConnectedThreadTabs\n historyCount={historyCount}\n historyFiltered={historyFiltered}\n historyActions={\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {threadFilterBox}\n {threadTagId && (\n <TagFilterChips\n tags={workspaceTags}\n combination={new Map([[threadTagId, 'include']])}\n onChange={clearThreadTag}\n />\n )}\n </div>\n }\n rightContent={(isCompact) => (\n <ConversationHeaderActions>\n {searchEnabled && (\n <SemanticSearchButton\n active={semanticSearchOpen && !isInChat}\n onClick={() => {\n if (isInChat) {\n setActiveTab('history')\n setSemanticSearchOpen(true)\n } else {\n setSemanticSearchOpen((open) => !open)\n }\n }}\n />\n )}\n {isInChat && <ConnectedConversationCallActions />}\n {/* New threads start from the list's launcher: in an open thread, New Task goes\n back to the list with the launcher focused; on the list, the launcher is it. */}\n {isInChat && (\n <NewChatButton\n entityLabel=\"Task\"\n labels={{ newEntity: () => 'New Task' }}\n isCompact={isCompact}\n onClick={() => {\n startNewChat(false)\n }}\n />\n )}\n {/* Starts a fresh conversation in Personal Assistant mode: the first message\n routes through OpenClaw delegation (/v1/openclaw via the /pa handler)\n instead of the RAG pipeline. Gated by the same experimental flag as the\n in-input toggle. */}\n {showPaMode && (\n <NewPaChatButton isCompact={isCompact} onClick={() => startNewChat(true)} />\n )}\n </ConversationHeaderActions>\n )}\n />\n {/* A task thread says what it is: its activity, and Mark done for an editor. Pinned with the tabs. */}\n {isInChat && <ConnectedThreadTaskBar conversationId={currentConversationId} />}\n </ConversationHeader>\n\n {/* The open thread: its messages and the composer. */}\n {isInChat && (\n <ConversationThread\n modelSelector={modelSelector}\n // Left in the composer: what the conversation reads (its documents and facts).\n composerAccessory={\n <ConnectedChatContextButton\n documents={conversationDocuments || []}\n pinnedTitle={pinnedTitle}\n onStatusClick={handleStatusClick}\n onSelectSource={handleSelectSource}\n onAddFromSources={handleGoToDocuments}\n canUpload={canUpload}\n uploadDisabledReason={uploadDisabledReason}\n />\n }\n composerTools={\n isDeveloper && (\n <ConversationSettingsButton\n label={`${getEntityLabel('CONVERSATION')} Settings`}\n onClick={() => openDialog('conversationSettings')}\n />\n )\n }\n />\n )}\n\n {!isInChat && (\n <ConversationHistoryPane>\n {/* Semantic search (opened from the header icon) is a toolbar row atop\n the history view: the list shows through until a search is run, then results\n replace it. */}\n {searchEnabled && semanticSearchOpen ? (\n <ConnectedThreadsPage showHeader={false} search ResultsBoundary={ResultsBoundary} />\n ) : (\n <ConnectedThreadsPage showHeader={false} />\n )}\n </ConversationHistoryPane>\n )}\n </ConversationRoot>\n\n {/* \"From the internet\" dialog, opened from the Upload \u2192 source menu. */}\n <UrlInputDialog\n open={urlDialogOpen}\n onOpenChange={setUrlDialogOpen}\n onUrlsSubmit={uploadUrls}\n titleWorkspaceName={workspace?.name}\n titleIcon={\n workspace && <ConnectedWorkspaceEntity workspace={workspace} size={24} variant=\"icon\" />\n }\n />\n </>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["import { useThreadTagFilter } from '../threads/useThreadTagFilter'\nimport { TagFilterChips } from '../tags/TagCombineFilter'\n/** The tabbed conversation page; host-specific composer context stays in a slot. */\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport {\n useArbiSessionStore,\n useArbiSessionState,\n useConversationDocuments,\n useConversationItems,\n useDocuments,\n useTags,\n useWorkspaceRole,\n useUserSettings,\n useTransientView,\n useWorkspaces,\n} from '@arbidocs/react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n getDocumentTitle,\n getEntityLabel,\n} from '@arbidocs/react/ui'\nimport { CalendarClock, MoreHorizontal } from 'lucide-react'\nimport { parseVersionRef } from '@arbidocs/react/artifacts'\nimport { ConnectedThreadTabs, useThreadTabs } from '../threads/ConnectedThreadTabs'\nimport { ConnectedThreadTaskBar } from '../tasks/ConnectedThreadTaskBar'\nimport { ConnectedThreadsPage } from '../threads-page/ConnectedThreadsPage'\nimport { ConnectedConversationCallActions } from '../../calls/connected/ConnectedConversationCallActions'\nimport { ConnectedWorkspaceEntity } from '../entities/ConnectedWorkspaceEntity'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { UrlInputDialog } from '../../files/UrlInputDialog'\nimport { closeDialog, openDialog } from '../../provider/dialog-registry'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { useOpenWorkflow } from '../workflows/useOpenWorkflow'\nimport { filterConversationRows } from '../../agents/tasks/ConversationListView'\nimport { FilterInput } from '../../shell/FilterInput'\nimport { ConnectedFilterDropdown as FilterDropdown } from '../ConnectedFilterDropdown'\nimport {\n ConversationHistoryPane,\n ConversationHeaderActions,\n ConversationSettingsButton,\n NewChatButton,\n NewPaChatButton,\n} from '../../chat/ConversationParts'\nimport { ConversationHeader, ConversationRoot } from '../../chat/ConversationLayout'\nimport { SemanticSearchButton } from './SemanticSearchButton'\nimport { ConversationThread } from './ConnectedConversation'\nimport { ConnectedChatContextButton } from '../chat-context/ConnectedChatContextButton'\nimport type { UploadSource } from '../../provider/environment'\n\nconst DEFAULT_THREAD_VISIBILITY = { me: null, workspace: null } as {\n me: 'include' | 'exclude' | null\n workspace: 'include' | 'exclude' | null\n}\nexport interface ConnectedConversationPageProps {\n /** Controlled selection: opens the stored (or, with null, a new) thread's tab, without route parsing. */\n conversationId?: string | null\n /** The composer's model picker (the host's). Absent: none. */\n modelSelector?: ReactNode\n /** Wraps the conversation search results (the host's crash boundary, say). */\n ResultsBoundary?: ComponentType<{ children: ReactNode }>\n /** Where \"go to documents\" leads. Absent: the environment's `openFiles`. */\n onGoToDocuments?: () => void\n}\n\n/**\n * The open conversation in the tabbed workspace page: its thread tabs, task bar, history with\n * filters and search, and the URL-upload dialog around `ConnectedConversationThread`'s body.\n */\nexport function ConnectedConversationPage({\n conversationId,\n modelSelector,\n ResultsBoundary,\n onGoToDocuments,\n}: ConnectedConversationPageProps) {\n const session = useArbiSessionStore()\n useEffect(() => {\n if (conversationId === undefined) return\n const tabId = conversationId ?? 'new'\n if (session.getState().tabs.activeThread !== tabId) session.openThreadTab(conversationId)\n }, [conversationId, session])\n const { openFiles } = useBlocksEnvironment()\n useEffect(() => () => session.setActiveCitationKey(null), [session])\n const { isInChat, setActiveTab } = useThreadTabs()\n // The list's \"\u2026\": Schedule a task (a rare action), a new scheduled workflow made in the Workflows\n // page's builder; its runs are threads in this list. Absent in an open thread or without workflows.\n const openWorkflow = useOpenWorkflow()\n const [doneFilter, setDoneFilter] = useTransientView(\n 'threads.done',\n 'exclude' as 'include' | 'exclude' | null\n )\n const [visibilityFilters, setVisibilityFilters] = useTransientView(\n 'threads.visibility',\n DEFAULT_THREAD_VISIBILITY\n )\n const [meetingFilter, setMeetingFilter] = useTransientView(\n 'threads.meetings',\n null as 'include' | 'exclude' | null\n )\n const [threadFilter, setThreadFilter] = useTransientView('topbar.filter', '')\n\n // Get experimental flags from API\n const { data: userSettings } = useUserSettings()\n const isDeveloper = userSettings?.developer === true\n const showPaMode = userSettings?.show_pa_mode ?? false\n const searchEnabled = userSettings?.show_conversation_search === true\n // Conversation (message) semantic search is opened from the header icon; it lives on the list, so opening it from a\n // thread goes back to the list first.\n const [semanticSearchOpen, setSemanticSearchOpen] = useState(false)\n\n // Handlers for modals\n const handleSelectSource = (source: UploadSource) => {\n // Match the Files \"Add\" menu exactly: internet opens the URL dialog directly,\n // the others open their focused upload modal. (No processing-modal guard.)\n if (source === 'internet') {\n setUrlDialogOpen(true)\n return\n }\n openDialog('upload', { tab: 'upload', source })\n }\n\n const handleGoToDocuments = () => {\n // Switch to the Documents/Files page instead of opening the in-chat modal\n // preview. Dismiss the modal first in case another entry point left it open.\n closeDialog('conversationDocuments')\n if (onGoToDocuments) onGoToDocuments()\n else openFiles()\n }\n\n const handleStatusClick = () => {\n openDialog('conversationDocuments')\n }\n\n // Get current conversation ID from store\n const currentConversationId = useArbiSessionState((state) => state.conversationId)\n const { conversations: taskItems } = useConversationItems()\n const conversations = taskItems\n\n // Read-only members (`guest`) can read but not upload. Non-members also can't upload.\n const workspaceId = useArbiSessionState((state) => state.workspaceId)\n const { data: workspaces = [] } = useWorkspaces()\n const workspace = workspaces.find((item) => item.external_id === workspaceId)\n const workspaceRole = useWorkspaceRole()\n const isGuest = workspaceRole === 'guest'\n const canUpload = workspaceRole !== null && !isGuest\n\n // \"From the internet\" opens the URL dialog inline (parity with the Files Add\n // menu); it runs the upload itself and surfaces any error in the dialog.\n const [urlDialogOpen, setUrlDialogOpen] = useState(false)\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openDialog('upload', { tab: 'upload', files, source: 'computer' }),\n })\n const uploadDisabledReason = isGuest\n ? 'Read-only members cannot upload documents in this workspace'\n : 'You cannot upload to this workspace'\n\n // What the conversation reads: its documents, and a pinned one.\n const { data: allWorkspaceDocuments = [] } = useDocuments()\n const { data: conversationDocuments } = useConversationDocuments()\n const pinnedDocumentId = useArbiSessionState((state) => state.pinnedDocumentId)\n // A pinned older version (`doc-\u2026@v2`, \"Ask about this version\" in the viewer) names its document.\n const pinnedRef = parseVersionRef(pinnedDocumentId)\n const pinnedDocument =\n conversationDocuments?.find((d) => d.external_id === pinnedRef.docId) ??\n allWorkspaceDocuments?.find((d) => d.external_id === pinnedRef.docId)\n\n const pinnedTitle = pinnedDocument\n ? `${getDocumentTitle(pinnedDocument)}${pinnedRef.version ? ` (version ${pinnedRef.version})` : ''}`\n : null\n\n // Start a new conversation (optionally in Personal Assistant mode) in a new chat tab.\n const startNewChat = (asPaChat: boolean) => {\n session.clearConversationError(currentConversationId || 'new')\n session.startNewConversation()\n session.openThreadTab(null)\n if (asPaChat) session.setPaMode(true)\n }\n\n // The filter is part of the first tab, next to its counter, and only while that tab is selected\n // (the list). With a thread open the tab is just the glyph and the count.\n const threadFilterDropdown = (\n <FilterDropdown\n stateKey=\"threads\"\n filterStates={\n new Map([\n ['done', doneFilter],\n ['me', visibilityFilters.me],\n ['workspace', visibilityFilters.workspace],\n ['meeting', meetingFilter],\n ])\n }\n onFiltersChange={(filters) => {\n setDoneFilter(filters.get('done') ?? null)\n setMeetingFilter(filters.get('meeting') ?? null)\n setVisibilityFilters({\n me: filters.get('me') ?? null,\n workspace: filters.get('workspace') ?? null,\n })\n }}\n options={[\n {\n id: 'me',\n label: 'Only Me',\n count: conversations.filter((thread) => !thread.is_shared).length,\n },\n {\n id: 'workspace',\n label: 'Workspace',\n count: conversations.filter((thread) => thread.is_shared).length,\n },\n {\n id: 'meeting',\n label: 'Meetings',\n count: conversations.filter((thread) => !!thread.meeting_call_ext_id).length,\n },\n {\n id: 'done',\n label: 'Done',\n count: conversations.filter((thread) => thread.status === 'done').length,\n },\n ]}\n hasExternalActiveState={Boolean(threadFilter.trim())}\n renderChevron\n disableHoverOpen\n align=\"start\"\n />\n )\n\n const threadFilterBox = (\n <FilterInput\n filter={threadFilterDropdown}\n borderless\n value={threadFilter}\n onChange={setThreadFilter}\n fields={['text']}\n onFieldsChange={() => {}}\n hideFieldPicker\n ariaLabel=\"Filter threads\"\n // Done threads are hidden by default: say so, or a completed thread looks lost.\n labels={doneFilter === 'exclude' ? { placeholder: 'Done hidden' } : undefined}\n className=\"w-44 max-w-[40vw]\"\n />\n )\n // The list tab counts the threads the list shows (its filters and search applied).\n const { data: workspaceTags = [] } = useTags()\n const { tagId: threadTagId, clear: clearThreadTag } = useThreadTagFilter()\n const historyCount = filterConversationRows(\n threadTagId\n ? conversations.filter((row) => row.label_ext_ids?.includes(threadTagId))\n : conversations,\n threadFilter,\n (row) =>\n workspaceTags\n .filter((tag) => row.label_ext_ids?.includes(tag.external_id))\n .map((tag) => tag.name)\n .join(' '),\n doneFilter,\n visibilityFilters,\n meetingFilter\n ).length\n const historyFiltered =\n Boolean(threadTagId) ||\n Boolean(threadFilter.trim()) ||\n doneFilter !== null ||\n meetingFilter !== null ||\n Object.values(visibilityFilters).some(Boolean)\n\n return (\n <>\n {/* The chat scroll surface is the outer `[data-scroll-container]` (DockZone).\n This column must GROW with its content (not stay viewport-capped) so the\n sticky tab header (top-0) and sticky message-input footer (bottom-0) have\n a containing block that spans the whole thread \u2014 otherwise `position:\n sticky` is clamped to a viewport-tall box and both unpin after one\n screen of scroll. Hence `min-h-full` (fill the scroller, but no phantom\n 100vh scroll inside this nested scroller) + `shrink-0` (don't let the\n flex parent shrink this back below its content). */}\n <ConversationRoot>\n {/* Fixed Tab Header */}\n <ConversationHeader>\n <ConnectedThreadTabs\n historyCount={historyCount}\n historyFiltered={historyFiltered}\n historyActions={\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {threadFilterBox}\n {threadTagId && (\n <TagFilterChips\n tags={workspaceTags}\n combination={new Map([[threadTagId, 'include']])}\n onChange={clearThreadTag}\n />\n )}\n </div>\n }\n rightContent={(isCompact) => (\n <ConversationHeaderActions>\n {searchEnabled && (\n <SemanticSearchButton\n active={semanticSearchOpen && !isInChat}\n onClick={() => {\n if (isInChat) {\n setActiveTab('history')\n setSemanticSearchOpen(true)\n } else {\n setSemanticSearchOpen((open) => !open)\n }\n }}\n />\n )}\n {isInChat && <ConnectedConversationCallActions />}\n {/* New threads start from the list's launcher: in an open thread, New Task goes\n back to the list with the launcher focused; on the list, the launcher is it. */}\n {isInChat && (\n <NewChatButton\n entityLabel=\"Task\"\n labels={{ newEntity: () => 'New Task' }}\n isCompact={isCompact}\n onClick={() => {\n startNewChat(false)\n }}\n />\n )}\n {/* Starts a fresh conversation in Personal Assistant mode: the first message\n routes through OpenClaw delegation (/v1/openclaw via the /pa handler)\n instead of the RAG pipeline. Gated by the same experimental flag as the\n in-input toggle. */}\n {showPaMode && (\n <NewPaChatButton isCompact={isCompact} onClick={() => startNewChat(true)} />\n )}\n {!isInChat && openWorkflow && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"More actions\"\n data-testid=\"threads-more\"\n >\n <MoreHorizontal className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onSelect={() => openWorkflow(undefined, { preset: 'schedule' })}\n data-testid=\"threads-schedule-task\"\n >\n <CalendarClock className=\"size-4\" /> Schedule a task\u2026\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </ConversationHeaderActions>\n )}\n />\n {/* A task thread says what it is: its activity, and Mark done for an editor. Pinned with the tabs. */}\n {isInChat && <ConnectedThreadTaskBar conversationId={currentConversationId} />}\n </ConversationHeader>\n\n {/* The open thread: its messages and the composer. */}\n {isInChat && (\n <ConversationThread\n modelSelector={modelSelector}\n // Left in the composer: what the conversation reads (its documents and facts).\n composerAccessory={\n <ConnectedChatContextButton\n documents={conversationDocuments || []}\n pinnedTitle={pinnedTitle}\n onStatusClick={handleStatusClick}\n onSelectSource={handleSelectSource}\n onAddFromSources={handleGoToDocuments}\n canUpload={canUpload}\n uploadDisabledReason={uploadDisabledReason}\n />\n }\n composerTools={\n isDeveloper && (\n <ConversationSettingsButton\n label={`${getEntityLabel('CONVERSATION')} Settings`}\n onClick={() => openDialog('conversationSettings')}\n />\n )\n }\n />\n )}\n\n {!isInChat && (\n <ConversationHistoryPane>\n {/* Semantic search (opened from the header icon) is a toolbar row atop\n the history view: the list shows through until a search is run, then results\n replace it. */}\n {searchEnabled && semanticSearchOpen ? (\n <ConnectedThreadsPage showHeader={false} search ResultsBoundary={ResultsBoundary} />\n ) : (\n <ConnectedThreadsPage showHeader={false} />\n )}\n </ConversationHistoryPane>\n )}\n </ConversationRoot>\n\n {/* \"From the internet\" dialog, opened from the Upload \u2192 source menu. */}\n <UrlInputDialog\n open={urlDialogOpen}\n onOpenChange={setUrlDialogOpen}\n onUrlsSubmit={uploadUrls}\n titleWorkspaceName={workspace?.name}\n titleIcon={\n workspace && <ConnectedWorkspaceEntity workspace={workspace} size={24} variant=\"icon\" />\n }\n />\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ export interface ConversationSendButtonProps {
|
|
|
2
2
|
onClick: () => void;
|
|
3
3
|
/** The thread ends in a question with no answer: an empty composer offers to retry it. */
|
|
4
4
|
hasOrphanedMessage: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* The composer's send button. It subscribes to the draft itself, so the conversation does not
|
|
@@ -6,7 +6,8 @@ import { hasMessageBody, useAssistantName } from "@arbidocs/react/shell";
|
|
|
6
6
|
import { ComposerSend } from "../../chat/Composer.js";
|
|
7
7
|
const ConversationSendButton = memo(function ConversationSendButton2({
|
|
8
8
|
onClick,
|
|
9
|
-
hasOrphanedMessage
|
|
9
|
+
hasOrphanedMessage,
|
|
10
|
+
disabled = false
|
|
10
11
|
}) {
|
|
11
12
|
const draftMessage = useArbiSessionState(selectDraft);
|
|
12
13
|
const assistantName = useAssistantName();
|
|
@@ -17,7 +18,7 @@ const ConversationSendButton = memo(function ConversationSendButton2({
|
|
|
17
18
|
{
|
|
18
19
|
onSend: onClick,
|
|
19
20
|
isRetry: showRetryIcon,
|
|
20
|
-
disabled: !showRetryIcon && !hasMessageBody(draftMessage, recipients)
|
|
21
|
+
disabled: disabled || !showRetryIcon && !hasMessageBody(draftMessage, recipients)
|
|
21
22
|
}
|
|
22
23
|
);
|
|
23
24
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/connection/conversation/ConversationSendButton.tsx"],
|
|
4
|
-
"sourcesContent": ["import { memo } from 'react'\nimport { assistantLabels, selectDraft, useArbiSessionState } from '@arbidocs/react'\nimport { hasMessageBody, useAssistantName } from '@arbidocs/react/shell'\nimport { ComposerSend } from '../../chat/Composer'\n\nexport interface ConversationSendButtonProps {\n onClick: () => void\n /** The thread ends in a question with no answer: an empty composer offers to retry it. */\n hasOrphanedMessage: boolean\n}\n\n/**\n * The composer's send button. It subscribes to the draft itself, so the conversation does not\n * re-render on every keystroke.\n *\n * Only an empty draft disables it. A failed send never does: the alert above the composer offers\n * Retry, and a new message must still go out (it replies past the failed turn).\n */\nexport const ConversationSendButton = memo(function ConversationSendButton({\n onClick,\n hasOrphanedMessage,\n}: ConversationSendButtonProps) {\n const draftMessage = useArbiSessionState(selectDraft)\n // The assistant's name alone (a shared thread's starting draft) is not a message.\n const assistantName = useAssistantName()\n const recipients = assistantLabels(assistantName)\n\n // Show retry icon when orphaned and no draft, send icon otherwise\n const showRetryIcon = hasOrphanedMessage && draftMessage === ''\n\n return (\n <ComposerSend\n onSend={onClick}\n isRetry={showRetryIcon}\n disabled={!showRetryIcon && !hasMessageBody(draftMessage, recipients)}\n />\n )\n})\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["import { memo } from 'react'\nimport { assistantLabels, selectDraft, useArbiSessionState } from '@arbidocs/react'\nimport { hasMessageBody, useAssistantName } from '@arbidocs/react/shell'\nimport { ComposerSend } from '../../chat/Composer'\n\nexport interface ConversationSendButtonProps {\n onClick: () => void\n /** The thread ends in a question with no answer: an empty composer offers to retry it. */\n hasOrphanedMessage: boolean\n disabled?: boolean\n}\n\n/**\n * The composer's send button. It subscribes to the draft itself, so the conversation does not\n * re-render on every keystroke.\n *\n * Only an empty draft disables it. A failed send never does: the alert above the composer offers\n * Retry, and a new message must still go out (it replies past the failed turn).\n */\nexport const ConversationSendButton = memo(function ConversationSendButton({\n onClick,\n hasOrphanedMessage,\n disabled = false,\n}: ConversationSendButtonProps) {\n const draftMessage = useArbiSessionState(selectDraft)\n // The assistant's name alone (a shared thread's starting draft) is not a message.\n const assistantName = useAssistantName()\n const recipients = assistantLabels(assistantName)\n\n // Show retry icon when orphaned and no draft, send icon otherwise\n const showRetryIcon = hasOrphanedMessage && draftMessage === ''\n\n return (\n <ComposerSend\n onSend={onClick}\n isRetry={showRetryIcon}\n disabled={disabled || (!showRetryIcon && !hasMessageBody(draftMessage, recipients))}\n />\n )\n})\n"],
|
|
5
|
+
"mappings": ";AAiCI;AAjCJ,SAAS,YAAY;AACrB,SAAS,iBAAiB,aAAa,2BAA2B;AAClE,SAAS,gBAAgB,wBAAwB;AACjD,SAAS,oBAAoB;AAgBtB,MAAM,yBAAyB,KAAK,SAASA,wBAAuB;AAAA,EACzE;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAAgC;AAC9B,QAAM,eAAe,oBAAoB,WAAW;AAEpD,QAAM,gBAAgB,iBAAiB;AACvC,QAAM,aAAa,gBAAgB,aAAa;AAGhD,QAAM,gBAAgB,sBAAsB,iBAAiB;AAE7D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU,YAAa,CAAC,iBAAiB,CAAC,eAAe,cAAc,UAAU;AAAA;AAAA,EACnF;AAEJ,CAAC;",
|
|
6
6
|
"names": ["ConversationSendButton"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Clarify which document a turn will edit when the viewer shows another document. */
|
|
2
|
+
export declare function DocumentTargetNotice({ target, visible, onSwitch, disabled, }: {
|
|
3
|
+
target: string;
|
|
4
|
+
visible: string;
|
|
5
|
+
onSwitch?: () => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
function DocumentTargetNotice({
|
|
4
|
+
target,
|
|
5
|
+
visible,
|
|
6
|
+
onSwitch,
|
|
7
|
+
disabled = false
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsxs(
|
|
10
|
+
"div",
|
|
11
|
+
{
|
|
12
|
+
className: "mx-3 mb-2 rounded-md border border-amber-400/50 bg-amber-50 p-2 text-sm text-foreground dark:bg-amber-950",
|
|
13
|
+
role: "note",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
16
|
+
"This thread will edit ",
|
|
17
|
+
/* @__PURE__ */ jsx("strong", { children: target }),
|
|
18
|
+
". The viewer shows ",
|
|
19
|
+
/* @__PURE__ */ jsx("strong", { children: visible }),
|
|
20
|
+
"."
|
|
21
|
+
] }),
|
|
22
|
+
onSwitch ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23
|
+
/* @__PURE__ */ jsx("p", { children: "Switching changes the thread\u2019s document for everyone in the thread." }),
|
|
24
|
+
/* @__PURE__ */ jsxs(
|
|
25
|
+
"button",
|
|
26
|
+
{
|
|
27
|
+
type: "button",
|
|
28
|
+
className: "mt-1 underline disabled:opacity-50",
|
|
29
|
+
onClick: onSwitch,
|
|
30
|
+
disabled,
|
|
31
|
+
children: [
|
|
32
|
+
"Switch this thread to ",
|
|
33
|
+
visible
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
] }) : /* @__PURE__ */ jsx("p", { children: "The visible document is unavailable; it cannot be attached here." })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
DocumentTargetNotice
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=DocumentTargetNotice.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/connection/conversation/DocumentTargetNotice.tsx"],
|
|
4
|
+
"sourcesContent": ["/** Clarify which document a turn will edit when the viewer shows another document. */\nexport function DocumentTargetNotice({\n target,\n visible,\n onSwitch,\n disabled = false,\n}: {\n target: string\n visible: string\n onSwitch?: () => void\n disabled?: boolean\n}) {\n return (\n <div\n className=\"mx-3 mb-2 rounded-md border border-amber-400/50 bg-amber-50 p-2 text-sm text-foreground dark:bg-amber-950\"\n role=\"note\"\n >\n <p>\n This thread will edit <strong>{target}</strong>. The viewer shows <strong>{visible}</strong>\n .\n </p>\n {onSwitch ? (\n <>\n <p>Switching changes the thread\u2019s document for everyone in the thread.</p>\n <button\n type=\"button\"\n className=\"mt-1 underline disabled:opacity-50\"\n onClick={onSwitch}\n disabled={disabled}\n >\n Switch this thread to {visible}\n </button>\n </>\n ) : (\n <p>The visible document is unavailable; it cannot be attached here.</p>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAiBM,SAKE,UAJsB,KADxB;AAhBC,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,MAAK;AAAA,MAEL;AAAA,6BAAC,OAAE;AAAA;AAAA,UACqB,oBAAC,YAAQ,kBAAO;AAAA,UAAS;AAAA,UAAmB,oBAAC,YAAQ,mBAAQ;AAAA,UAAS;AAAA,WAE9F;AAAA,QACC,WACC,iCACE;AAAA,8BAAC,OAAE,sFAAmE;AAAA,UACtE;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACT;AAAA,cACD;AAAA;AAAA,gBACwB;AAAA;AAAA;AAAA,UACzB;AAAA,WACF,IAEA,oBAAC,OAAE,8EAAgE;AAAA;AAAA;AAAA,EAEvE;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Guard a send until an explicit thread-document switch has settled. */
|
|
2
|
+
export declare function createDocumentSwitchGuard(): {
|
|
3
|
+
isPending: () => boolean;
|
|
4
|
+
hasFailed: () => boolean;
|
|
5
|
+
acknowledgeFailure: () => void;
|
|
6
|
+
switch(bind: () => Promise<boolean>, onPending: (pending: boolean) => void): Promise<boolean>;
|
|
7
|
+
};
|
|
8
|
+
/** Only the initiating conversation may receive the result of its pending switch. */
|
|
9
|
+
export declare function switchInScope(guard: ReturnType<typeof createDocumentSwitchGuard>, bind: () => Promise<boolean>, isCurrent: () => boolean, onPending: (pending: boolean) => void, onFailure: () => void): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
function createDocumentSwitchGuard() {
|
|
3
|
+
let pending = false;
|
|
4
|
+
let failed = false;
|
|
5
|
+
return {
|
|
6
|
+
isPending: () => pending,
|
|
7
|
+
hasFailed: () => failed,
|
|
8
|
+
acknowledgeFailure: () => {
|
|
9
|
+
failed = false;
|
|
10
|
+
},
|
|
11
|
+
async switch(bind, onPending) {
|
|
12
|
+
if (pending) return false;
|
|
13
|
+
pending = true;
|
|
14
|
+
failed = false;
|
|
15
|
+
onPending(true);
|
|
16
|
+
try {
|
|
17
|
+
const ok = await bind();
|
|
18
|
+
failed = !ok;
|
|
19
|
+
return ok;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
failed = true;
|
|
22
|
+
throw error;
|
|
23
|
+
} finally {
|
|
24
|
+
pending = false;
|
|
25
|
+
onPending(false);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function switchInScope(guard, bind, isCurrent, onPending, onFailure) {
|
|
31
|
+
const ok = await guard.switch(bind, (pending) => {
|
|
32
|
+
if (isCurrent()) onPending(pending);
|
|
33
|
+
});
|
|
34
|
+
if (!ok && isCurrent()) onFailure();
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
createDocumentSwitchGuard,
|
|
38
|
+
switchInScope
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=documentSwitch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/connection/conversation/documentSwitch.ts"],
|
|
4
|
+
"sourcesContent": ["/** Guard a send until an explicit thread-document switch has settled. */\nexport function createDocumentSwitchGuard() {\n let pending = false\n let failed = false\n return {\n isPending: () => pending,\n hasFailed: () => failed,\n acknowledgeFailure: () => {\n failed = false\n },\n async switch(bind: () => Promise<boolean>, onPending: (pending: boolean) => void) {\n if (pending) return false\n pending = true\n failed = false\n onPending(true)\n try {\n const ok = await bind()\n failed = !ok\n return ok\n } catch (error) {\n failed = true\n throw error\n } finally {\n pending = false\n onPending(false)\n }\n },\n }\n}\n\n/** Only the initiating conversation may receive the result of its pending switch. */\nexport async function switchInScope(\n guard: ReturnType<typeof createDocumentSwitchGuard>,\n bind: () => Promise<boolean>,\n isCurrent: () => boolean,\n onPending: (pending: boolean) => void,\n onFailure: () => void\n) {\n const ok = await guard.switch(bind, (pending) => {\n if (isCurrent()) onPending(pending)\n })\n if (!ok && isCurrent()) onFailure()\n}\n"],
|
|
5
|
+
"mappings": ";AACO,SAAS,4BAA4B;AAC1C,MAAI,UAAU;AACd,MAAI,SAAS;AACb,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB,oBAAoB,MAAM;AACxB,eAAS;AAAA,IACX;AAAA,IACA,MAAM,OAAO,MAA8B,WAAuC;AAChF,UAAI,QAAS,QAAO;AACpB,gBAAU;AACV,eAAS;AACT,gBAAU,IAAI;AACd,UAAI;AACF,cAAM,KAAK,MAAM,KAAK;AACtB,iBAAS,CAAC;AACV,eAAO;AAAA,MACT,SAAS,OAAO;AACd,iBAAS;AACT,cAAM;AAAA,MACR,UAAE;AACA,kBAAU;AACV,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;AAGA,eAAsB,cACpB,OACA,MACA,WACA,WACA,WACA;AACA,QAAM,KAAK,MAAM,MAAM,OAAO,MAAM,CAAC,YAAY;AAC/C,QAAI,UAAU,EAAG,WAAU,OAAO;AAAA,EACpC,CAAC;AACD,MAAI,CAAC,MAAM,UAAU,EAAG,WAAU;AACpC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|