Commit c489e520 authored by edy's avatar edy

Fix scoped home chat navigation

parent 48cde513
Pipeline #18445 failed
...@@ -147,7 +147,7 @@ declare global { ...@@ -147,7 +147,7 @@ declare global {
skills: WorkspaceSummary["skills"]; skills: WorkspaceSummary["skills"];
modelConfig: AppConfig["expertModelConfig"] | null; modelConfig: AppConfig["expertModelConfig"] | null;
systemSummary: SystemSummary | null; systemSummary: SystemSummary | null;
sessions: SessionSummary[]; sessions: WorkspaceSummary["sessions"];
messages: ChatMessage[]; messages: ChatMessage[];
messagesBySession: Record<string, ChatMessage[]>; messagesBySession: Record<string, ChatMessage[]>;
logs: LogEntry[]; logs: LogEntry[];
...@@ -202,6 +202,14 @@ declare global { ...@@ -202,6 +202,14 @@ declare global {
dismissed: boolean; dismissed: boolean;
}>; }>;
navigateToView(view: ViewMode): Promise<{ view: ViewMode }>; navigateToView(view: ViewMode): Promise<{ view: ViewMode }>;
runHomeChatNavigationScenario(view?: "plugins" | "settings" | "knowledge"): Promise<{
sourceView: "plugins" | "settings" | "knowledge";
viewMode: "chat";
sessionScopeProjectId?: string;
activeSessionId: string;
activeSessionProjectId: string;
messageCount: number;
}>;
saveSettingsConfig(options?: { saveSettingsConfig(options?: {
lobsterKey?: string; lobsterKey?: string;
workspacePath?: string; workspacePath?: string;
...@@ -504,10 +512,16 @@ export default function App() { ...@@ -504,10 +512,16 @@ export default function App() {
} }
return viewMode === "chat" ? HOME_CHAT_PROJECT_ID : activeProject?.id; return viewMode === "chat" ? HOME_CHAT_PROJECT_ID : activeProject?.id;
}, [activeProject?.id, bindingRequired, viewMode]); }, [activeProject?.id, bindingRequired, viewMode]);
const preferredSessionId = useMemo(() => resolvePreferredSessionId(sessions, activeSessionId), [activeSessionId, sessions]); const scopedSessions = useMemo(
() => sessionScopeProjectId
? sessions.filter((session) => session.projectId === sessionScopeProjectId)
: sessions,
[sessionScopeProjectId, sessions]
);
const preferredSessionId = useMemo(() => resolvePreferredSessionId(scopedSessions, activeSessionId), [activeSessionId, scopedSessions]);
const visibleSessionId = useMemo( const visibleSessionId = useMemo(
() => activeSessionId || preferredSessionId, () => scopedSessions.some((session) => session.id === activeSessionId) ? activeSessionId : preferredSessionId,
[activeSessionId, preferredSessionId] [activeSessionId, preferredSessionId, scopedSessions]
); );
const { const {
messagesBySession, messagesBySession,
...@@ -572,7 +586,7 @@ export default function App() { ...@@ -572,7 +586,7 @@ export default function App() {
}); });
const hasVisibleConversation = messages.length > 0 || sendPhase !== "idle"; const hasVisibleConversation = messages.length > 0 || sendPhase !== "idle";
const showStartupOverlay = startupStateActive && !hasVisibleConversation; const showStartupOverlay = startupStateActive && !hasVisibleConversation;
const hasVisibleSession = Boolean(visibleSessionId && sessions.some((session) => session.id === visibleSessionId)); const hasVisibleSession = Boolean(visibleSessionId && scopedSessions.some((session) => session.id === visibleSessionId));
const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving; const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving;
const { const {
pendingHomeIntentSuggestion, pendingHomeIntentSuggestion,
...@@ -711,12 +725,12 @@ export default function App() { ...@@ -711,12 +725,12 @@ export default function App() {
}, [desktopApi.chat, updateSessionMessages]); }, [desktopApi.chat, updateSessionMessages]);
useEffect(() => { useEffect(() => {
if (!isBound || !visibleSessionId) { if (!isBound || !visibleSessionId || !hasVisibleSession) {
return; return;
} }
void loadMessages(visibleSessionId, true, false); void loadMessages(visibleSessionId, true, false);
}, [isBound, loadMessages, visibleSessionId]); }, [hasVisibleSession, isBound, loadMessages, visibleSessionId]);
const { const {
projectActionPending, projectActionPending,
...@@ -776,6 +790,7 @@ export default function App() { ...@@ -776,6 +790,7 @@ export default function App() {
selectedSkillId, selectedSkillId,
defaultSkillId: DEFAULT_SKILL.id, defaultSkillId: DEFAULT_SKILL.id,
setViewMode, setViewMode,
handleNavSelection,
setSelectedSkillId, setSelectedSkillId,
setActiveProjectSession: setActiveSessionId, setActiveProjectSession: setActiveSessionId,
setPrompt, setPrompt,
...@@ -875,6 +890,7 @@ export default function App() { ...@@ -875,6 +890,7 @@ export default function App() {
if ( if (
!isBound !isBound
|| !visibleSessionId || !visibleSessionId
|| !hasVisibleSession
|| !workspace?.chatReady || !workspace?.chatReady
|| !canExchangeMessages(workspace, runtimeStatus, gatewayStatus) || !canExchangeMessages(workspace, runtimeStatus, gatewayStatus)
) { ) {
...@@ -882,20 +898,20 @@ export default function App() { ...@@ -882,20 +898,20 @@ export default function App() {
} }
void loadMessages(visibleSessionId, true, false); void loadMessages(visibleSessionId, true, false);
}, [gatewayStatus, isBound, loadMessages, runtimeStatus, sendPhase, visibleSessionId, workspace?.chatReady]); }, [gatewayStatus, hasVisibleSession, isBound, loadMessages, runtimeStatus, sendPhase, visibleSessionId, workspace?.chatReady]);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
async function hydrateSidebarSessionTitles() { async function hydrateSidebarSessionTitles() {
if (!sessions.length) { if (!scopedSessions.length) {
if (!cancelled) { if (!cancelled) {
setSidebarSessionTitles({}); setSidebarSessionTitles({});
} }
return; return;
} }
const nextEntries = await Promise.all(sessions.map(async (session, index) => { const nextEntries = await Promise.all(scopedSessions.map(async (session, index) => {
const cachedMessages = messagesBySession[session.id]; const cachedMessages = messagesBySession[session.id];
if (cachedMessages?.length) { if (cachedMessages?.length) {
return [session.id, deriveSidebarSessionTitle(toPlainMessages(cachedMessages))] as const; return [session.id, deriveSidebarSessionTitle(toPlainMessages(cachedMessages))] as const;
...@@ -919,7 +935,7 @@ export default function App() { ...@@ -919,7 +935,7 @@ export default function App() {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [desktopApi.chat, messagesBySession, sessions]); }, [desktopApi.chat, messagesBySession, scopedSessions]);
useEffect(() => { useEffect(() => {
if (!effectiveSkills.some((skill) => skill.id === selectedSkillId)) { if (!effectiveSkills.some((skill) => skill.id === selectedSkillId)) {
...@@ -936,7 +952,7 @@ export default function App() { ...@@ -936,7 +952,7 @@ export default function App() {
runtimeTelemetry, runtimeTelemetry,
config, config,
systemSummary, systemSummary,
sessions, sessions: scopedSessions,
messages: toPlainMessages(messages), messages: toPlainMessages(messages),
messagesBySession: Object.fromEntries( messagesBySession: Object.fromEntries(
Object.entries(messagesBySession).map(([sessionId, sessionMessages]) => [sessionId, toPlainMessages(sessionMessages)]) Object.entries(messagesBySession).map(([sessionId, sessionMessages]) => [sessionId, toPlainMessages(sessionMessages)])
...@@ -1312,8 +1328,8 @@ export default function App() { ...@@ -1312,8 +1328,8 @@ export default function App() {
ui={ui} ui={ui}
showBindEntry={showBindEntry} showBindEntry={showBindEntry}
projectActionPending={projectActionPending} projectActionPending={projectActionPending}
sessions={sessions} sessions={scopedSessions}
activeSessionId={activeSessionId} activeSessionId={visibleSessionId ?? ""}
sidebarSessionTitles={sidebarSessionTitles} sidebarSessionTitles={sidebarSessionTitles}
sendPhase={sendPhase} sendPhase={sendPhase}
activeStreamSessionId={activeStreamRef.current?.sessionId} activeStreamSessionId={activeStreamRef.current?.sessionId}
......
...@@ -91,16 +91,12 @@ export function useChatSessionsController(deps: UseChatSessionsControllerDeps) { ...@@ -91,16 +91,12 @@ export function useChatSessionsController(deps: UseChatSessionsControllerDeps) {
return return
} }
const hasActiveSession = activeSessionId if (nextSessions.some((session) => session.id === activeSessionId)) {
? nextSessions.some((session) => session.id === activeSessionId)
: false
const nextSessionId = resolvePreferredSessionId(nextSessions, activeSessionId)
if (hasActiveSession) {
return return
} }
if (nextSessionId) { if (nextSessions[0]) {
setActiveProjectSession(nextSessionId) setActiveProjectSession(nextSessions[0].id)
} else if (sessionScopeProjectId === HOME_CHAT_PROJECT_ID) { } else if (sessionScopeProjectId === HOME_CHAT_PROJECT_ID) {
const homeSession = await desktopApi.chat.createSessionForProject(HOME_CHAT_PROJECT_ID, homeSessionTitle) const homeSession = await desktopApi.chat.createSessionForProject(HOME_CHAT_PROJECT_ID, homeSessionTitle)
if (cancelled) { if (cancelled) {
......
...@@ -47,16 +47,23 @@ export function useHomeNavigation(deps: UseHomeNavigationDeps) { ...@@ -47,16 +47,23 @@ export function useHomeNavigation(deps: UseHomeNavigationDeps) {
clearSessions() clearSessions()
setActiveProjectSession(EMPTY_SESSION_ID) setActiveProjectSession(EMPTY_SESSION_ID)
clearAllSessionMessages() clearAllSessionMessages()
} else {
const homeSessions = await desktopApi.chat.listSessionsByProject(HOME_CHAT_PROJECT_ID).catch(() => [])
setActiveProjectSession(homeSessions[0]?.id ?? EMPTY_SESSION_ID)
} }
return workspace ?? null return workspace ?? null
} }
setActiveProjectSession(EMPTY_SESSION_ID) const homeSessions = resetConversation
? []
: await desktopApi.chat.listSessionsByProject(HOME_CHAT_PROJECT_ID).catch(() => [])
setActiveProjectSession(homeSessions[0]?.id ?? EMPTY_SESSION_ID)
await (switchProjectPreservingMessages ?? switchProject)(HOME_CHAT_PROJECT_ID) await (switchProjectPreservingMessages ?? switchProject)(HOME_CHAT_PROJECT_ID)
return await desktopApi.workspace.getSummary().catch(() => workspace) return await desktopApi.workspace.getSummary().catch(() => workspace)
}, [ }, [
clearAllSessionMessages, clearAllSessionMessages,
clearSessions, clearSessions,
desktopApi.chat,
desktopApi.workspace, desktopApi.workspace,
projectActionPending, projectActionPending,
setActiveProjectSession, setActiveProjectSession,
......
...@@ -24,6 +24,7 @@ interface UseSmokeActionHandlersDeps { ...@@ -24,6 +24,7 @@ interface UseSmokeActionHandlersDeps {
selectedSkillId: string selectedSkillId: string
defaultSkillId: string defaultSkillId: string
setViewMode: (viewMode: ViewMode | ((current: ViewMode) => ViewMode)) => void setViewMode: (viewMode: ViewMode | ((current: ViewMode) => ViewMode)) => void
handleNavSelection: (viewMode: ViewMode) => void | Promise<void>
setSelectedSkillId: (skillId: string) => void setSelectedSkillId: (skillId: string) => void
setActiveProjectSession: (sessionId: string) => void setActiveProjectSession: (sessionId: string) => void
setPrompt: (prompt: string) => void setPrompt: (prompt: string) => void
...@@ -111,6 +112,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul ...@@ -111,6 +112,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
selectedSkillId, selectedSkillId,
defaultSkillId, defaultSkillId,
setViewMode, setViewMode,
handleNavSelection,
setSelectedSkillId, setSelectedSkillId,
setActiveProjectSession, setActiveProjectSession,
setPrompt, setPrompt,
...@@ -270,9 +272,49 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul ...@@ -270,9 +272,49 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
throw new Error(`Unknown smoke expert entry: ${normalizedExpertId}`) throw new Error(`Unknown smoke expert entry: ${normalizedExpertId}`)
}, },
navigateToView: async (nextView) => { navigateToView: async (nextView) => {
setViewMode(nextView) await handleNavSelection(nextView)
await waitForSmokeUiReady(nextView)
return { view: nextView } return { view: nextView }
}, },
runHomeChatNavigationScenario: async (sourceView = "settings") => {
const resolvedSourceView = sourceView
setViewMode(resolvedSourceView)
await waitForSmokeUiReady(resolvedSourceView)
await handleNavSelection("chat")
await waitForSmokeUiReady("chat")
const started = Date.now()
while (Date.now() - started < 10000) {
const state = window.__QJC_SMOKE__
const activeSessionId = state?.activeSessionId ?? ""
const activeSession = state?.sessions.find((session) => session.id === activeSessionId)
if (
state?.viewMode === "chat"
&& state.ui?.sessionScopeProjectId === HOME_CHAT_PROJECT_ID
&& activeSession?.projectId === HOME_CHAT_PROJECT_ID
) {
return {
sourceView: resolvedSourceView,
viewMode: state.viewMode,
sessionScopeProjectId: state.ui.sessionScopeProjectId,
activeSessionId,
activeSessionProjectId: activeSession.projectId,
messageCount: state.messages.length
}
}
await new Promise<void>((resolve) => window.setTimeout(resolve, 50))
}
const state = window.__QJC_SMOKE__
throw new Error(
"Home chat navigation did not settle on a home session: "
+ JSON.stringify({
viewMode: state?.viewMode,
sessionScopeProjectId: state?.ui?.sessionScopeProjectId,
activeSessionId: state?.activeSessionId,
sessionProjectIds: state?.sessions.map((session) => session.projectId)
})
)
},
saveSettingsConfig: async (options) => { saveSettingsConfig: async (options) => {
const nextWorkspacePath = options?.workspacePath const nextWorkspacePath = options?.workspacePath
const nextExpertModelConfig = options?.expertModelConfig const nextExpertModelConfig = options?.expertModelConfig
...@@ -404,6 +446,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul ...@@ -404,6 +446,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
desktopApi.workspace, desktopApi.workspace,
dismissHomeIntentSuggestion, dismissHomeIntentSuggestion,
expertDefinitions, expertDefinitions,
handleNavSelection,
prompt, prompt,
resolveExpertDisplayName, resolveExpertDisplayName,
resolveExpertProject, resolveExpertProject,
......
...@@ -9,7 +9,6 @@ import type { ...@@ -9,7 +9,6 @@ import type {
RuntimeCloudStatus, RuntimeCloudStatus,
RuntimeStatus, RuntimeStatus,
RuntimeTelemetryStatus, RuntimeTelemetryStatus,
SessionSummary,
SystemSummary, SystemSummary,
WorkspaceSummary WorkspaceSummary
} from "@qjclaw/shared-types" } from "@qjclaw/shared-types"
...@@ -44,7 +43,7 @@ export interface UseSmokeSnapshotDeps { ...@@ -44,7 +43,7 @@ export interface UseSmokeSnapshotDeps {
runtimeTelemetry: RuntimeTelemetryStatus | null runtimeTelemetry: RuntimeTelemetryStatus | null
config: AppConfig | null config: AppConfig | null
systemSummary: SystemSummary | null systemSummary: SystemSummary | null
sessions: SessionSummary[] sessions: WorkspaceSummary["sessions"]
messages: ChatMessage[] messages: ChatMessage[]
messagesBySession: Record<string, ChatMessage[]> messagesBySession: Record<string, ChatMessage[]>
activeSessionId: string activeSessionId: string
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment