Commit c489e520 authored by edy's avatar edy

Fix scoped home chat navigation

parent 48cde513
Pipeline #18445 failed
......@@ -147,7 +147,7 @@ declare global {
skills: WorkspaceSummary["skills"];
modelConfig: AppConfig["expertModelConfig"] | null;
systemSummary: SystemSummary | null;
sessions: SessionSummary[];
sessions: WorkspaceSummary["sessions"];
messages: ChatMessage[];
messagesBySession: Record<string, ChatMessage[]>;
logs: LogEntry[];
......@@ -202,6 +202,14 @@ declare global {
dismissed: boolean;
}>;
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?: {
lobsterKey?: string;
workspacePath?: string;
......@@ -504,10 +512,16 @@ export default function App() {
}
return viewMode === "chat" ? HOME_CHAT_PROJECT_ID : activeProject?.id;
}, [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(
() => activeSessionId || preferredSessionId,
[activeSessionId, preferredSessionId]
() => scopedSessions.some((session) => session.id === activeSessionId) ? activeSessionId : preferredSessionId,
[activeSessionId, preferredSessionId, scopedSessions]
);
const {
messagesBySession,
......@@ -572,7 +586,7 @@ export default function App() {
});
const hasVisibleConversation = messages.length > 0 || sendPhase !== "idle";
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 {
pendingHomeIntentSuggestion,
......@@ -711,12 +725,12 @@ export default function App() {
}, [desktopApi.chat, updateSessionMessages]);
useEffect(() => {
if (!isBound || !visibleSessionId) {
if (!isBound || !visibleSessionId || !hasVisibleSession) {
return;
}
void loadMessages(visibleSessionId, true, false);
}, [isBound, loadMessages, visibleSessionId]);
}, [hasVisibleSession, isBound, loadMessages, visibleSessionId]);
const {
projectActionPending,
......@@ -776,6 +790,7 @@ export default function App() {
selectedSkillId,
defaultSkillId: DEFAULT_SKILL.id,
setViewMode,
handleNavSelection,
setSelectedSkillId,
setActiveProjectSession: setActiveSessionId,
setPrompt,
......@@ -875,6 +890,7 @@ export default function App() {
if (
!isBound
|| !visibleSessionId
|| !hasVisibleSession
|| !workspace?.chatReady
|| !canExchangeMessages(workspace, runtimeStatus, gatewayStatus)
) {
......@@ -882,20 +898,20 @@ export default function App() {
}
void loadMessages(visibleSessionId, true, false);
}, [gatewayStatus, isBound, loadMessages, runtimeStatus, sendPhase, visibleSessionId, workspace?.chatReady]);
}, [gatewayStatus, hasVisibleSession, isBound, loadMessages, runtimeStatus, sendPhase, visibleSessionId, workspace?.chatReady]);
useEffect(() => {
let cancelled = false;
async function hydrateSidebarSessionTitles() {
if (!sessions.length) {
if (!scopedSessions.length) {
if (!cancelled) {
setSidebarSessionTitles({});
}
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];
if (cachedMessages?.length) {
return [session.id, deriveSidebarSessionTitle(toPlainMessages(cachedMessages))] as const;
......@@ -919,7 +935,7 @@ export default function App() {
return () => {
cancelled = true;
};
}, [desktopApi.chat, messagesBySession, sessions]);
}, [desktopApi.chat, messagesBySession, scopedSessions]);
useEffect(() => {
if (!effectiveSkills.some((skill) => skill.id === selectedSkillId)) {
......@@ -936,7 +952,7 @@ export default function App() {
runtimeTelemetry,
config,
systemSummary,
sessions,
sessions: scopedSessions,
messages: toPlainMessages(messages),
messagesBySession: Object.fromEntries(
Object.entries(messagesBySession).map(([sessionId, sessionMessages]) => [sessionId, toPlainMessages(sessionMessages)])
......@@ -1312,8 +1328,8 @@ export default function App() {
ui={ui}
showBindEntry={showBindEntry}
projectActionPending={projectActionPending}
sessions={sessions}
activeSessionId={activeSessionId}
sessions={scopedSessions}
activeSessionId={visibleSessionId ?? ""}
sidebarSessionTitles={sidebarSessionTitles}
sendPhase={sendPhase}
activeStreamSessionId={activeStreamRef.current?.sessionId}
......
......@@ -91,16 +91,12 @@ export function useChatSessionsController(deps: UseChatSessionsControllerDeps) {
return
}
const hasActiveSession = activeSessionId
? nextSessions.some((session) => session.id === activeSessionId)
: false
const nextSessionId = resolvePreferredSessionId(nextSessions, activeSessionId)
if (hasActiveSession) {
if (nextSessions.some((session) => session.id === activeSessionId)) {
return
}
if (nextSessionId) {
setActiveProjectSession(nextSessionId)
if (nextSessions[0]) {
setActiveProjectSession(nextSessions[0].id)
} else if (sessionScopeProjectId === HOME_CHAT_PROJECT_ID) {
const homeSession = await desktopApi.chat.createSessionForProject(HOME_CHAT_PROJECT_ID, homeSessionTitle)
if (cancelled) {
......
......@@ -47,16 +47,23 @@ export function useHomeNavigation(deps: UseHomeNavigationDeps) {
clearSessions()
setActiveProjectSession(EMPTY_SESSION_ID)
clearAllSessionMessages()
} else {
const homeSessions = await desktopApi.chat.listSessionsByProject(HOME_CHAT_PROJECT_ID).catch(() => [])
setActiveProjectSession(homeSessions[0]?.id ?? EMPTY_SESSION_ID)
}
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)
return await desktopApi.workspace.getSummary().catch(() => workspace)
}, [
clearAllSessionMessages,
clearSessions,
desktopApi.chat,
desktopApi.workspace,
projectActionPending,
setActiveProjectSession,
......
......@@ -24,6 +24,7 @@ interface UseSmokeActionHandlersDeps {
selectedSkillId: string
defaultSkillId: string
setViewMode: (viewMode: ViewMode | ((current: ViewMode) => ViewMode)) => void
handleNavSelection: (viewMode: ViewMode) => void | Promise<void>
setSelectedSkillId: (skillId: string) => void
setActiveProjectSession: (sessionId: string) => void
setPrompt: (prompt: string) => void
......@@ -111,6 +112,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
selectedSkillId,
defaultSkillId,
setViewMode,
handleNavSelection,
setSelectedSkillId,
setActiveProjectSession,
setPrompt,
......@@ -270,9 +272,49 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
throw new Error(`Unknown smoke expert entry: ${normalizedExpertId}`)
},
navigateToView: async (nextView) => {
setViewMode(nextView)
await handleNavSelection(nextView)
await waitForSmokeUiReady(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) => {
const nextWorkspacePath = options?.workspacePath
const nextExpertModelConfig = options?.expertModelConfig
......@@ -404,6 +446,7 @@ export function useSmokeActionHandlers(deps: UseSmokeActionHandlersDeps): NonNul
desktopApi.workspace,
dismissHomeIntentSuggestion,
expertDefinitions,
handleNavSelection,
prompt,
resolveExpertDisplayName,
resolveExpertProject,
......
......@@ -9,7 +9,6 @@ import type {
RuntimeCloudStatus,
RuntimeStatus,
RuntimeTelemetryStatus,
SessionSummary,
SystemSummary,
WorkspaceSummary
} from "@qjclaw/shared-types"
......@@ -44,7 +43,7 @@ export interface UseSmokeSnapshotDeps {
runtimeTelemetry: RuntimeTelemetryStatus | null
config: AppConfig | null
systemSummary: SystemSummary | null
sessions: SessionSummary[]
sessions: WorkspaceSummary["sessions"]
messages: ChatMessage[]
messagesBySession: Record<string, ChatMessage[]>
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