Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
257e0fb
feat: separate Agent into independent module with dedicated page and …
EurFelux Mar 12, 2026
16fe8df
refactor: Migrate Tailwind arbitrary values to semantic classes
EurFelux Mar 12, 2026
043de67
refactor: migrate agent-only components to agents directory and share…
EurFelux Mar 12, 2026
4cd4783
refactor: Extract AgentChatNavbar component for better organization
EurFelux Mar 12, 2026
f6c5114
refactor: Move sessions into AgentChat component
EurFelux Mar 12, 2026
7a1f53e
Merge remote-tracking branch 'origin' into feat/13329
EurFelux Mar 12, 2026
f741301
fix: improve AgentPage loading states and apiServer guard
EurFelux Mar 12, 2026
3f711e3
refactor: merge duplicate useActiveAgent hooks into one
EurFelux Mar 12, 2026
840f6db
fix: Update migration log message from 201 to 202
EurFelux Mar 12, 2026
638ff96
fix: Fix import order and remove duplicate topicPosition access
EurFelux Mar 12, 2026
38629b9
fix: Fix conditional rendering of agent chat messages
EurFelux Mar 12, 2026
13434b0
feat: add drawer for agent side panel when it's not showing
EurFelux Mar 12, 2026
562357c
refactor: Remove explicit FC types from agent components
EurFelux Mar 12, 2026
5392585
refactor: Migrate Tailwind CSS custom properties to new syntax
EurFelux Mar 12, 2026
7ccb901
fix: Stabilize agent session initialization with useRef
EurFelux Mar 12, 2026
b21ee1d
feat: add keyboard shortcut to create new agent session
EurFelux Mar 12, 2026
5e0ed11
Merge remote-tracking branch 'origin' into feat/13329
EurFelux Mar 13, 2026
e9f2008
style: Remove border from agent chat right sidebar
EurFelux Mar 13, 2026
7df3622
fix: check API server runtime status in AgentPage guard
EurFelux Mar 13, 2026
f309d85
feat: improve agent chat UI with better loading states and layout
EurFelux Mar 13, 2026
4d79d90
refactor: Move API server running state to Redux store
EurFelux Mar 13, 2026
a997037
feat: pin agents tab
EurFelux Mar 13, 2026
4f2095b
style: Improve agent chat loading spinner style
EurFelux Mar 13, 2026
898816d
refactor: Extract Container component in AgentChat
EurFelux Mar 13, 2026
006c6ea
fix: Fix API server loading state check in AgentPage
EurFelux Mar 13, 2026
ce82876
refactor: Use type-only import for PropsWithChildren
EurFelux Mar 13, 2026
8ecff66
fix: Fix animation
EurFelux Mar 13, 2026
3cfd36a
feat: add empty state UI for agents page
EurFelux Mar 13, 2026
ab0d23d
feat: redesign server_not_running state UI in AgentPage
EurFelux Mar 14, 2026
77a95cb
feat: redesign enable_server warning state UI in AgentPage
EurFelux Mar 14, 2026
90564fe
refactor: Extract Container component in AgentPage
EurFelux Mar 14, 2026
0f6e8ac
refactor: extract status screen components into components/status
EurFelux Mar 14, 2026
6b78186
fix: Prevent layout overflow in AgentPage
EurFelux Mar 14, 2026
51f6d54
chore: Ignore .context/vitest-temp directory
EurFelux Mar 14, 2026
e90f4ad
feat: Add missing translations for agents API server warnings
EurFelux Mar 14, 2026
689484a
refactor: remove unified list order and inline assistant list hooks
EurFelux Mar 14, 2026
5939240
fix: Adjust border radius and padding in AgentChat and AgentSidePanel…
kangfenmao Mar 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/renderer/src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ErrorBoundary } from './components/ErrorBoundary'
import TabsContainer from './components/Tab/TabContainer'
import NavigationHandler from './handler/NavigationHandler'
import { useNavbarPosition } from './hooks/useSettings'
import AgentPage from './pages/agents/AgentPage'
import CodeToolsPage from './pages/code/CodeToolsPage'
import FilesPage from './pages/files/FilesPage'
import HomePage from './pages/home/HomePage'
Expand All @@ -31,6 +32,7 @@ const Router: FC = () => {
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/agents" element={<AgentPage />} />
<Route path="/store" element={<AssistantPresetsPage />} />
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
<Route path="/translate" element={<TranslatePage />} />
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/Popups/agent/AgentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { permissionModeCards } from '@renderer/config/agent'
import { isWin } from '@renderer/config/constant'
import { useAgents } from '@renderer/hooks/agents/useAgents'
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
import SelectAgentBaseModelButton from '@renderer/pages/agents/components/SelectAgentBaseModelButton'
import type {
AddAgentForm,
AgentEntity,
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/src/components/Tab/TabContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
LayoutGrid,
Monitor,
Moon,
MousePointerClick,
NotepadText,
Palette,
Settings,
Expand Down Expand Up @@ -86,9 +87,12 @@ const getTabIcon = (
return <LayoutGrid size={14} />
}

// TODO: Add TabId as type instead of string
switch (tabId) {
case 'home':
return <Home size={14} />
case 'agents':
return <MousePointerClick size={14} />
case 'store':
return <Sparkle size={14} />
case 'translate':
Expand Down
5 changes: 4 additions & 1 deletion src/renderer/src/components/app/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
MessageSquare,
Monitor,
Moon,
MousePointerClick,
NotepadText,
Palette,
Settings,
Expand Down Expand Up @@ -126,10 +127,11 @@ const MainMenus: FC = () => {
const { theme } = useTheme()

const isRoute = (path: string): string => (pathname === path && !minappShow ? 'active' : '')
const isRoutes = (path: string): string => (pathname.startsWith(path) && !minappShow ? 'active' : '')
const isRoutes = (path: string): string => (pathname.startsWith(path) && path !== '/' && !minappShow ? 'active' : '')

const iconMap = {
assistants: <MessageSquare size={18} className="icon" />,
agents: <MousePointerClick size={18} className="icon" />,
store: <Sparkle size={18} className="icon" />,
paintings: <Palette size={18} className="icon" />,
translate: <Languages size={18} className="icon" />,
Expand All @@ -143,6 +145,7 @@ const MainMenus: FC = () => {

const pathMap = {
assistants: '/',
agents: '/agents',
store: '/store',
paintings: `/paintings/${defaultPaintingProvider}`,
translate: '/translate',
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { SidebarIcon } from '@renderer/types'
*/
export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [
'assistants',
'agents',
'store',
'paintings',
'translate',
Expand Down
18 changes: 17 additions & 1 deletion src/renderer/src/hooks/agents/useActiveAgent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { useAppDispatch } from '@renderer/store'
import { setActiveAgentId as setActiveAgentIdAction } from '@renderer/store/runtime'
import { useCallback } from 'react'

import { useRuntime } from '../useRuntime'
import { useAgent } from './useAgent'
import { useAgentSessionInitializer } from './useAgentSessionInitializer'

export const useActiveAgent = () => {
const { chat } = useRuntime()
const { activeAgentId } = chat
return useAgent(activeAgentId)
const dispatch = useAppDispatch()
const { initializeAgentSession } = useAgentSessionInitializer()

const setActiveAgentId = useCallback(
async (id: string) => {
dispatch(setActiveAgentIdAction(id))
await initializeAgentSession(id)
},
[dispatch, initializeAgentSession]
)

return { ...useAgent(activeAgentId), setActiveAgentId }
}
33 changes: 15 additions & 18 deletions src/renderer/src/hooks/agents/useAgentSessionInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { loggerService } from '@logger'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useAppDispatch } from '@renderer/store'
import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { useCallback, useEffect } from 'react'
import { setActiveSessionIdAction } from '@renderer/store/runtime'
import { useCallback, useEffect, useRef } from 'react'

import { useAgentClient } from './useAgentClient'

Expand All @@ -19,6 +19,10 @@ export const useAgentSessionInitializer = () => {
const { chat } = useRuntime()
const { activeAgentId, activeSessionIdMap } = chat

// Use a ref to keep the callback stable across activeSessionIdMap changes
const activeSessionIdMapRef = useRef(activeSessionIdMap)
activeSessionIdMapRef.current = activeSessionIdMap

/**
* Initialize session for the given agent by loading its sessions
* and setting the latest one as active
Expand All @@ -28,11 +32,9 @@ export const useAgentSessionInitializer = () => {
if (!agentId) return

try {
// Check if this agent already has an active session
const currentSessionId = activeSessionIdMap[agentId]
if (currentSessionId) {
// Session already exists, just switch to session view
dispatch(setActiveTopicOrSessionAction('session'))
// Check if this agent has already been initialized (key exists in map)
if (agentId in activeSessionIdMapRef.current) {
// Already initialized, nothing to do
return
}

Expand All @@ -46,33 +48,28 @@ export const useAgentSessionInitializer = () => {

// Set the latest session as active
dispatch(setActiveSessionIdAction({ agentId, sessionId: latestSession.id }))
dispatch(setActiveTopicOrSessionAction('session'))
} else {
// No sessions exist, we might want to create one
// But for now, just switch to session view and let the Sessions component handle it
dispatch(setActiveTopicOrSessionAction('session'))
// Mark as initialized with no session (null vs undefined distinction)
dispatch(setActiveSessionIdAction({ agentId, sessionId: null }))
}
} catch (error) {
logger.error('Failed to initialize agent session:', error as Error)
// Even if loading fails, switch to session view
dispatch(setActiveTopicOrSessionAction('session'))
}
},
[client, dispatch, activeSessionIdMap]
[client, dispatch]
)

/**
* Auto-initialize when activeAgentId changes
*/
useEffect(() => {
if (activeAgentId) {
// Check if we need to initialize this agent's session
const hasActiveSession = activeSessionIdMap[activeAgentId]
if (!hasActiveSession) {
// Check if we need to initialize this agent's session (key not yet in map)
if (!(activeAgentId in activeSessionIdMapRef.current)) {
initializeAgentSession(activeAgentId)
}
}
}, [activeAgentId, activeSessionIdMap, initializeAgentSession])
}, [activeAgentId, initializeAgentSession])

return {
initializeAgentSession
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/src/hooks/agents/useCreateDefaultSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { loggerService } from '@logger'
import { useAgent } from '@renderer/hooks/agents/useAgent'
import { useSessions } from '@renderer/hooks/agents/useSessions'
import { useAppDispatch } from '@renderer/store'
import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { setActiveSessionIdAction } from '@renderer/store/runtime'
import type { CreateSessionForm } from '@renderer/types'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -36,7 +36,6 @@ export const useCreateDefaultSession = (agentId: string | null) => {

if (created) {
dispatch(setActiveSessionIdAction({ agentId, sessionId: created.id }))
dispatch(setActiveTopicOrSessionAction('session'))
}

return created
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/src/i18n/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ const titleKeyMap = {
paintings: 'title.paintings',
settings: 'title.settings',
translate: 'title.translate',
openclaw: 'openclaw.title'
openclaw: 'openclaw.title',
agents: 'agent.sidebar_title'
} as const

export const getTitleLabel = (key: string): string => {
Expand Down Expand Up @@ -181,6 +182,7 @@ export const getThemeModeLabel = (key: string): string => {

const sidebarIconKeyMap = {
assistants: 'assistants.title',
agents: 'agent.sidebar_title',
store: 'assistants.presets.title',
paintings: 'paintings.title',
translate: 'translate.title',
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} tasks completed"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Selected",
"selectedItems": "Selected {{count}} items",
"selectedMessages": "Selected {{count}} messages",
"sessions": "Sessions",
"settings": "Settings",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "智能体",
"todo": {
"panel": {
"title": "已完成 {{completed}}/{{total}} 个任务"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "已选择",
"selectedItems": "已选择 {{count}} 项",
"selectedMessages": "选中 {{count}} 条消息",
"sessions": "会话",
"settings": "设置",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "已完成 {{completed}}/{{total}} 個任務"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "已選擇",
"selectedItems": "已選擇 {{count}} 項",
"selectedMessages": "已選取 {{count}} 則訊息",
"sessions": "[to be translated]:Sessions",
"settings": "設定",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/de-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} Aufgaben abgeschlossen"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Ausgewählt",
"selectedItems": "{{count}} Elemente ausgewählt",
"selectedMessages": "{{count}} Nachrichten ausgewählt",
"sessions": "[to be translated]:Sessions",
"settings": "Einstellungen",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/el-gr.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} εργασίες ολοκληρώθηκαν"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Επιλεγμένο",
"selectedItems": "Επιλέχθηκαν {{count}} αντικείμενα",
"selectedMessages": "Επιλέχθηκαν {{count}} μηνύματα",
"sessions": "[to be translated]:Sessions",
"settings": "Ρυθμίσεις",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/es-es.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} tareas completadas"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Seleccionado",
"selectedItems": "{{count}} elementos seleccionados",
"selectedMessages": "{{count}} mensajes seleccionados",
"sessions": "[to be translated]:Sessions",
"settings": "Configuración",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/fr-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} tâches terminées"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Sélectionné",
"selectedItems": "{{count}} éléments sélectionnés",
"selectedMessages": "{{count}} messages sélectionnés",
"sessions": "[to be translated]:Sessions",
"settings": "Paramètres",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/ja-jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} タスク完了"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "選択済み",
"selectedItems": "{{count}}件の項目を選択しました",
"selectedMessages": "{{count}}件のメッセージを選択しました",
"sessions": "[to be translated]:Sessions",
"settings": "設定",
"sort": {
"pinyin": {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/translate/pt-pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
}
}
},
"sidebar_title": "[to be translated]:Agents",
"todo": {
"panel": {
"title": "{{completed}}/{{total}} tarefas concluídas"
Expand Down Expand Up @@ -1385,6 +1386,7 @@
"selected": "Selecionado",
"selectedItems": "{{count}} itens selecionados",
"selectedMessages": "{{count}} mensagens selecionadas",
"sessions": "[to be translated]:Sessions",
"settings": "Configurações",
"sort": {
"pinyin": {
Expand Down
Loading
Loading