feat: better composer etc
This commit is contained in:
@@ -40,13 +40,19 @@ export function useContextSuggestions({
|
||||
cwd: cwd || undefined
|
||||
})
|
||||
|
||||
if (stillCurrent()) {setContextSuggestions((result.items || []).filter(i => i.text))}
|
||||
if (stillCurrent()) {
|
||||
setContextSuggestions((result.items || []).filter(i => i.text))
|
||||
}
|
||||
} catch {
|
||||
if (stillCurrent()) {setContextSuggestions([])}
|
||||
if (stillCurrent()) {
|
||||
setContextSuggestions([])
|
||||
}
|
||||
}
|
||||
}, [activeSessionId, activeSessionIdRef, currentCwd, requestGateway])
|
||||
|
||||
useEffect(() => {
|
||||
if (gatewayState === 'open' && activeSessionId) {void refresh()}
|
||||
if (gatewayState === 'open' && activeSessionId) {
|
||||
void refresh()
|
||||
}
|
||||
}, [activeSessionId, gatewayState, refresh])
|
||||
}
|
||||
|
||||
@@ -16,10 +16,15 @@ export function useCwdActions({ activeSessionId, activeSessionIdRef, currentCwd,
|
||||
async (cwd: string) => {
|
||||
const target = cwd.trim()
|
||||
|
||||
if (!target || activeSessionIdRef.current) {return}
|
||||
if (!target || activeSessionIdRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await requestGateway<{ branch?: string; cwd?: string }>('config.get', { key: 'project', cwd: target })
|
||||
const info = await requestGateway<{ branch?: string; cwd?: string }>('config.get', {
|
||||
key: 'project',
|
||||
cwd: target
|
||||
})
|
||||
|
||||
if (!activeSessionIdRef.current && ($currentCwd.get() || target) === (info.cwd || target)) {
|
||||
setCurrentBranch(info.branch || '')
|
||||
@@ -35,7 +40,9 @@ export function useCwdActions({ activeSessionId, activeSessionIdRef, currentCwd,
|
||||
async (cwd: string) => {
|
||||
const trimmed = cwd.trim()
|
||||
|
||||
if (!trimmed) {return}
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
|
||||
const persistGlobal = async () => {
|
||||
const info = await requestGateway<{ branch?: string; cwd?: string; value?: string }>('config.set', {
|
||||
@@ -46,7 +53,9 @@ export function useCwdActions({ activeSessionId, activeSessionIdRef, currentCwd,
|
||||
|
||||
setCurrentCwd(info.cwd || info.value || trimmed)
|
||||
|
||||
if (!activeSessionId) {setCurrentBranch(info.branch || '')}
|
||||
if (!activeSessionId) {
|
||||
setCurrentBranch(info.branch || '')
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeSessionId) {
|
||||
@@ -101,7 +110,9 @@ export function useCwdActions({ activeSessionId, activeSessionIdRef, currentCwd,
|
||||
multiple: false
|
||||
})
|
||||
|
||||
if (paths?.[0]) {await changeSessionCwd(paths[0])}
|
||||
if (paths?.[0]) {
|
||||
await changeSessionCwd(paths[0])
|
||||
}
|
||||
}, [changeSessionCwd, currentCwd])
|
||||
|
||||
return { browseSessionCwd, changeSessionCwd, refreshProjectBranch }
|
||||
|
||||
@@ -25,7 +25,9 @@ export function useModelControls({ activeSessionId, queryClient, requestGateway
|
||||
|
||||
queryClient.setQueryData<ModelOptionsResponse>(['model-options', activeSessionId || 'global'], patch)
|
||||
|
||||
if (includeGlobal) {queryClient.setQueryData<ModelOptionsResponse>(['model-options', 'global'], patch)}
|
||||
if (includeGlobal) {
|
||||
queryClient.setQueryData<ModelOptionsResponse>(['model-options', 'global'], patch)
|
||||
}
|
||||
},
|
||||
[activeSessionId, queryClient]
|
||||
)
|
||||
@@ -34,9 +36,13 @@ export function useModelControls({ activeSessionId, queryClient, requestGateway
|
||||
try {
|
||||
const result = await getGlobalModelInfo()
|
||||
|
||||
if (typeof result.model === 'string') {setCurrentModel(result.model)}
|
||||
if (typeof result.model === 'string') {
|
||||
setCurrentModel(result.model)
|
||||
}
|
||||
|
||||
if (typeof result.provider === 'string') {setCurrentProvider(result.provider)}
|
||||
if (typeof result.provider === 'string') {
|
||||
setCurrentProvider(result.provider)
|
||||
}
|
||||
} catch {
|
||||
// The delayed session.info event still updates this once the agent is ready.
|
||||
}
|
||||
@@ -56,7 +62,9 @@ export function useModelControls({ activeSessionId, queryClient, requestGateway
|
||||
command: `/model ${selection.model} --provider ${selection.provider}${selection.persistGlobal ? ' --global' : ''}`
|
||||
})
|
||||
|
||||
if (selection.persistGlobal) {void refreshCurrentModel()}
|
||||
if (selection.persistGlobal) {
|
||||
void refreshCurrentModel()
|
||||
}
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: selection.persistGlobal ? ['model-options'] : ['model-options', activeSessionId]
|
||||
})
|
||||
|
||||
@@ -87,7 +87,13 @@ describe('usePreviewRouting', () => {
|
||||
const target = previewTarget('/work/demo.html')
|
||||
|
||||
registerSessionPreview('session-1', target, 'tool-result')
|
||||
render(<PreviewRoutingHarness onEvent={handler => { handleEvent = handler }} />)
|
||||
render(
|
||||
<PreviewRoutingHarness
|
||||
onEvent={handler => {
|
||||
handleEvent = handler
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect($previewTarget.get()).toEqual({ ...target, renderMode: 'preview' })
|
||||
@@ -95,7 +101,13 @@ describe('usePreviewRouting', () => {
|
||||
})
|
||||
|
||||
it('does not infer previews from assistant prose', async () => {
|
||||
render(<PreviewRoutingHarness onEvent={handler => { handleEvent = handler }} />)
|
||||
render(
|
||||
<PreviewRoutingHarness
|
||||
onEvent={handler => {
|
||||
handleEvent = handler
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
act(() => {
|
||||
$messages.set([
|
||||
@@ -109,7 +121,13 @@ describe('usePreviewRouting', () => {
|
||||
})
|
||||
|
||||
it('registers structured tool-result preview targets', async () => {
|
||||
render(<PreviewRoutingHarness onEvent={handler => { handleEvent = handler }} />)
|
||||
render(
|
||||
<PreviewRoutingHarness
|
||||
onEvent={handler => {
|
||||
handleEvent = handler
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
act(() =>
|
||||
handleEvent({
|
||||
@@ -127,7 +145,13 @@ describe('usePreviewRouting', () => {
|
||||
})
|
||||
|
||||
it('registers html previews from edit inline diffs', async () => {
|
||||
render(<PreviewRoutingHarness onEvent={handler => { handleEvent = handler }} />)
|
||||
render(
|
||||
<PreviewRoutingHarness
|
||||
onEvent={handler => {
|
||||
handleEvent = handler
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
act(() =>
|
||||
handleEvent({
|
||||
|
||||
@@ -113,18 +113,32 @@ export function usePreviewRouting({
|
||||
|
||||
const registerStructuredPreview = useCallback(
|
||||
async (event: RpcEvent) => {
|
||||
if (event.session_id && event.session_id !== activeSessionIdRef.current && event.session_id !== previewSessionId) {return}
|
||||
if (
|
||||
event.session_id &&
|
||||
event.session_id !== activeSessionIdRef.current &&
|
||||
event.session_id !== previewSessionId
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!event.type.startsWith('tool.')) {return}
|
||||
if (!event.type.startsWith('tool.')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!previewSessionId) {return}
|
||||
if (!previewSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
const candidate = structuredPreviewCandidate(event.payload)
|
||||
|
||||
if (!candidate) {return}
|
||||
if (!candidate) {
|
||||
return
|
||||
}
|
||||
const desktop = window.hermesDesktop
|
||||
|
||||
if (!desktop?.normalizePreviewTarget) {return}
|
||||
if (!desktop?.normalizePreviewTarget) {
|
||||
return
|
||||
}
|
||||
const sessionId = previewSessionId
|
||||
const cwd = currentCwd || ''
|
||||
const target = await desktop.normalizePreviewTarget(candidate, cwd || undefined).catch(() => null)
|
||||
@@ -146,7 +160,9 @@ export function usePreviewRouting({
|
||||
async (url: string, context?: string) => {
|
||||
const sessionId = activeSessionIdRef.current
|
||||
|
||||
if (!sessionId) {throw new Error('No active session for background restart')}
|
||||
if (!sessionId) {
|
||||
throw new Error('No active session for background restart')
|
||||
}
|
||||
|
||||
const cwd = $currentCwd.get() || currentCwd || ''
|
||||
|
||||
@@ -159,7 +175,9 @@ export function usePreviewRouting({
|
||||
|
||||
const taskId = result.task_id || ''
|
||||
|
||||
if (!taskId) {throw new Error('Background restart did not return a task id')}
|
||||
if (!taskId) {
|
||||
throw new Error('Background restart did not return a task id')
|
||||
}
|
||||
|
||||
beginPreviewServerRestart(taskId, url)
|
||||
|
||||
@@ -175,18 +193,26 @@ export function usePreviewRouting({
|
||||
if (event.type === 'preview.restart.complete') {
|
||||
const { task_id, text } = asRecord(event.payload)
|
||||
|
||||
if (typeof task_id === 'string' && task_id) {completePreviewServerRestart(task_id, typeof text === 'string' ? text : '')}
|
||||
if (typeof task_id === 'string' && task_id) {
|
||||
completePreviewServerRestart(task_id, typeof text === 'string' ? text : '')
|
||||
}
|
||||
} else if (event.type === 'preview.restart.progress') {
|
||||
const { task_id, text } = asRecord(event.payload)
|
||||
|
||||
if (typeof task_id === 'string' && task_id) {progressPreviewServerRestart(task_id, typeof text === 'string' ? text : '')}
|
||||
if (typeof task_id === 'string' && task_id) {
|
||||
progressPreviewServerRestart(task_id, typeof text === 'string' ? text : '')
|
||||
}
|
||||
}
|
||||
|
||||
if (event.session_id && event.session_id !== activeSessionIdRef.current) {return}
|
||||
if (event.session_id && event.session_id !== activeSessionIdRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
void registerStructuredPreview(event)
|
||||
|
||||
if ($previewTarget.get()?.kind === 'url' && gatewayEventCompletedFileDiff(event)) {requestPreviewReload()}
|
||||
if ($previewTarget.get()?.kind === 'url' && gatewayEventCompletedFileDiff(event)) {
|
||||
requestPreviewReload()
|
||||
}
|
||||
},
|
||||
[activeSessionIdRef, baseHandleGatewayEvent, registerStructuredPreview]
|
||||
)
|
||||
|
||||
@@ -22,10 +22,14 @@ interface RouteResumeOptions {
|
||||
// parsed. If the hash references a real session, defer; resume picks it up
|
||||
// next tick. Without this, ctrl+R on `#/:sessionId` flashes 5 loading states.
|
||||
function rawHashLooksLikeSession(): boolean {
|
||||
if (typeof window === 'undefined') {return false}
|
||||
if (typeof window === 'undefined') {
|
||||
return false
|
||||
}
|
||||
const hash = window.location.hash.replace(/^#/, '')
|
||||
|
||||
if (!hash || hash === '/') {return false}
|
||||
if (!hash || hash === '/') {
|
||||
return false
|
||||
}
|
||||
|
||||
return !hash.startsWith('/settings') && !hash.startsWith('/skills') && !hash.startsWith('/artifacts')
|
||||
}
|
||||
@@ -46,7 +50,9 @@ export function useRouteResume({
|
||||
startFreshSessionDraft
|
||||
}: RouteResumeOptions) {
|
||||
useEffect(() => {
|
||||
if (currentView !== 'chat' || gatewayState !== 'open') {return}
|
||||
if (currentView !== 'chat' || gatewayState !== 'open') {
|
||||
return
|
||||
}
|
||||
|
||||
if (routedSessionId) {
|
||||
const cachedRuntime = runtimeIdByStoredSessionIdRef.current.get(routedSessionId)
|
||||
@@ -56,7 +62,9 @@ export function useRouteResume({
|
||||
Boolean(cachedRuntime) &&
|
||||
cachedRuntime === activeSessionIdRef.current
|
||||
|
||||
if (!alreadyActive) {void resumeSession(routedSessionId, true)}
|
||||
if (!alreadyActive) {
|
||||
void resumeSession(routedSessionId, true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -655,16 +655,16 @@ export function useSessionActions({
|
||||
setFreshDraftReady(false)
|
||||
setSelectedStoredSessionId(storedSessionId)
|
||||
selectedStoredSessionIdRef.current = storedSessionId
|
||||
const stored = $sessions.get().find(session => session.id === storedSessionId)
|
||||
const stored = $sessions.get().find(session => session.id === storedSessionId)
|
||||
|
||||
if (stored) {
|
||||
setCurrentUsage(current => ({
|
||||
...current,
|
||||
input: stored.input_tokens || 0,
|
||||
output: stored.output_tokens || 0,
|
||||
total: (stored.input_tokens || 0) + (stored.output_tokens || 0)
|
||||
}))
|
||||
}
|
||||
if (stored) {
|
||||
setCurrentUsage(current => ({
|
||||
...current,
|
||||
input: stored.input_tokens || 0,
|
||||
output: stored.output_tokens || 0,
|
||||
total: (stored.input_tokens || 0) + (stored.output_tokens || 0)
|
||||
}))
|
||||
}
|
||||
|
||||
setMessages(previousMessages)
|
||||
navigate(sessionRoute(storedSessionId), { replace: true })
|
||||
|
||||
Reference in New Issue
Block a user