>({})
const [sidebarCollapsed, setSidebarCollapsed] = useState(true)
const activeConversation = activeConversationForState(conversations, activeId, showArchived)
const addDefaultConversation = useCallback(() => {
addConversation(fallbackTarget)
}, [addConversation, fallbackTarget])
const archiveConversationSafely = useArchiveConversationSafely({
addConversation,
archiveConversation,
conversations,
fallbackTarget,
})
useActiveConversationSync(activeConversation, activeId, setActiveId)
const handleStatusChange = useCallback((id: string, status: AgentStatus) => {
setStatuses((current) => current[id] === status ? current : { ...current, [id]: status })
}, [])
const forkConversationUntilMessage = useCallback((sourceId: string, messageId: string) => {
const targetId = forkConversation(sourceId)
if (!targetId) return
cloneAiWorkspaceSessionUntilMessage(sourceId, targetId, messageId)
}, [forkConversation])
const { trackedRenameConversation, trackedTitleConversationFromAnswer } = useTrackedConversationActions({
conversations,
renameConversation,
titleConversationFromAnswer,
})
const toggleSidebarCollapsed = useCallback(() => {
setSidebarCollapsed((current) => {
const next = !current
trackAiWorkspaceSidebarToggled(next, workspace.mode)
return next
})
}, [workspace.mode])
useAiWorkspaceNewChatEvent(workspace.open, addDefaultConversation)
useEffect(() => {
updateDefaultConversationTargets(fallbackTarget.id)
}, [fallbackTarget.id, updateDefaultConversationTargets])
return {
activeConversation,
activeId,
addDefaultConversation,
archiveConversationSafely,
canArchiveConversation,
closeConversationSafely: closeConversation,
conversations,
fallbackTarget,
forkConversationUntilMessage,
groups,
handleStatusChange,
renameConversation: trackedRenameConversation,
reorderConversation,
restoreConversation,
sidebarCollapsed,
setActiveId,
setConversationTarget,
setShowArchived,
showArchived,
statuses,
markConversationActivity,
titleConversationFromAnswer: trackedTitleConversationFromAnswer,
toggleSidebarCollapsed,
updateDefaultConversationTargets,
}
}
function AiWorkspaceLayout({ model, workspace }: { model: AiWorkspaceModel; workspace: ResolvedAiWorkspaceProps }) {
const sizing = useAiWorkspaceSizing(workspace.mode)
if (workspace.mode === 'side') {
return
}
return (
{!model.sidebarCollapsed && (
)}
)
}
function ConversationSessions({
model,
onMessageHistoryScrollStateChange,
workspace,
}: {
model: AiWorkspaceModel
onMessageHistoryScrollStateChange?: (scrolled: boolean) => void
workspace: ResolvedAiWorkspaceProps
}) {
return (
{model.conversations.map((conversation) => {
const target = resolveTarget(conversation, model.groups, model.fallbackTarget)
return (
model.archiveConversationSafely(conversation.id)}
onClose={workspace.onClose}
onDock={workspace.onDock}
onFileCreated={workspace.onFileCreated}
onFileModified={workspace.onFileModified}
onForkMessage={(messageId) => model.forkConversationUntilMessage(conversation.id, messageId)}
onMessageHistoryScrollStateChange={onMessageHistoryScrollStateChange}
onOpenAiSettings={workspace.onOpenAiSettings}
onOpenNote={workspace.onOpenNote}
onPopOut={workspace.onPopOut}
onRestoreVaultAiGuidance={workspace.onRestoreVaultAiGuidance}
onSelectTarget={(targetId) => model.setConversationTarget(conversation.id, targetId)}
onStatusChange={model.handleStatusChange}
onPromptSubmitted={model.markConversationActivity}
onTitleFromAnswer={model.titleConversationFromAnswer}
onUnsupportedAiPaste={workspace.onUnsupportedAiPaste}
onVaultChanged={workspace.onVaultChanged}
openTabs={workspace.openTabs}
target={target}
vaultAiGuidanceStatus={workspace.vaultAiGuidanceStatus}
vaultPath={workspace.vaultPath}
vaultPaths={workspace.vaultPaths}
/>
)
})}
)
}
export function AiWorkspace(props: AiWorkspaceProps) {
const workspace = resolveAiWorkspaceProps(props)
const model = useAiWorkspaceModel(workspace)
const { onActiveConversationChange } = workspace
useEffect(() => {
if (!workspace.open || !model.activeId) return
onActiveConversationChange?.(model.activeId)
}, [model.activeId, onActiveConversationChange, workspace.open])
if (!workspace.open || !model.activeConversation) return null
return
}