Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qjclaw-dmg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qjclaw-dmg
Commits
c489e520
Commit
c489e520
authored
May 09, 2026
by
edy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix scoped home chat navigation
parent
48cde513
Pipeline
#18445
failed
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
86 additions
and
25 deletions
+86
-25
App.tsx
apps/ui/src/App.tsx
+30
-14
useChatSessionsController.ts
apps/ui/src/features/chat/useChatSessionsController.ts
+3
-7
useHomeNavigation.ts
apps/ui/src/features/shell/useHomeNavigation.ts
+8
-1
useSmokeActionHandlers.ts
apps/ui/src/features/smoke/useSmokeActionHandlers.ts
+44
-1
useSmokeSnapshot.ts
apps/ui/src/features/smoke/useSmokeSnapshot.ts
+1
-2
No files found.
apps/ui/src/App.tsx
View file @
c489e520
...
@@ -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
&&
s
copedS
essions
.
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
(
!
s
copedS
essions
.
length
)
{
if
(
!
cancelled
)
{
if
(
!
cancelled
)
{
setSidebarSessionTitles
({});
setSidebarSessionTitles
({});
}
}
return
;
return
;
}
}
const
nextEntries
=
await
Promise
.
all
(
sessions
.
map
(
async
(
session
,
index
)
=>
{
const
nextEntries
=
await
Promise
.
all
(
s
copedS
essions
.
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
,
s
copedS
essions
]);
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=
{
s
copedS
essions
}
activeSessionId=
{
activeSessionId
}
activeSessionId=
{
visibleSessionId
??
""
}
sidebarSessionTitles=
{
sidebarSessionTitles
}
sidebarSessionTitles=
{
sidebarSessionTitles
}
sendPhase=
{
sendPhase
}
sendPhase=
{
sendPhase
}
activeStreamSessionId=
{
activeStreamRef
.
current
?.
sessionId
}
activeStreamSessionId=
{
activeStreamRef
.
current
?.
sessionId
}
...
...
apps/ui/src/features/chat/useChatSessionsController.ts
View file @
c489e520
...
@@ -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
(
nextSession
Id
)
{
if
(
nextSession
s
[
0
]
)
{
setActiveProjectSession
(
nextSession
I
d
)
setActiveProjectSession
(
nextSession
s
[
0
].
i
d
)
}
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
)
{
...
...
apps/ui/src/features/shell/useHomeNavigation.ts
View file @
c489e520
...
@@ -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
,
...
...
apps/ui/src/features/smoke/useSmokeActionHandlers.ts
View file @
c489e520
...
@@ -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
,
...
...
apps/ui/src/features/smoke/useSmokeSnapshot.ts
View file @
c489e520
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment