Архитектура процесса рендеринга
Процесс рендеринга отвечает за весь UI и взаимодействие с пользователем; построен на React 18 + TypeScript. Точка входа — packages/renderer/src/app/App.tsx.
Иерархия провайдеров (Context Provider)
Компонент App задаёт строгий порядок вложенности провайдеров. Внешние провайдеры доступны вложенным провайдерам и компонентам:
graph TB
LP[LocaleProvider — i18n internationalization]
TP[ThemeProvider — theme/wallpaper]
CDP[ConfirmDialogProvider — global confirm dialog]
AP[AuthProvider — auth state]
SDP[SettingsDataProvider — settings data]
EP[ElfiProvider — Elfi assistant]
WSP[WebSearchProvider — search]
ChatDP[ChatDataProvider — chat data cache]
CBP[ChatBackendProvider — WebSocket backend]
UCP[UnifiedChatProvider — unified chat API]
HPP[HtmlPreviewProvider — HTML preview]
DPP[DevPreviewProvider — dev preview]
PR[ProtectedRoute — route guard]
HR[HashRouter — frontend routing]
CTP[ChatTabsProvider — multi-tab management]
AR[AppRoutes — route definition]
AC[AppContent — main content]
LP --> TP --> CDP --> AP --> SDP --> EP --> WSP --> ChatDP --> CBP --> UCP --> HPP --> DPP --> PR --> HR --> CTP --> AR --> AC
// Фактическая структура вложенности в App.tsx
function App() {
return (
<LocaleProvider>
<ThemeProvider>
<ConfirmDialogProvider>
<AuthProvider>
<SettingsDataProvider>
<ElfiProvider>
<WebSearchProvider>
<ChatDataProvider>
<ChatBackendProvider>
<UnifiedChatProvider>
<HtmlPreviewProvider>
<DevPreviewProvider>
<ProtectedRoute>
<HashRouter>
<ChatTabsProvider>
<AppRoutes>
<AppContent />
</AppRoutes>
</ChatTabsProvider>
</HashRouter>
</ProtectedRoute>
</DevPreviewProvider>
</HtmlPreviewProvider>
</UnifiedChatProvider>
</ChatBackendProvider>
</ChatDataProvider>
</WebSearchProvider>
</ElfiProvider>
</SettingsDataProvider>
</AuthProvider>
</ConfirmDialogProvider>
</ThemeProvider>
</LocaleProvider>
);
}
Zustand Store
Часто обновляемое состояние чата управляется через Zustand, чтобы избежать лишних ре-рендеров из-за Context.
chatStore
Основное хранилище состояния чата; использует middleware subscribeWithSelector для точечных подписок:
// Упрощённый интерфейс состояния chatStore
interface ChatState {
// Сессии
sessions: Session[];
sessionsLoading: boolean;
sessionsError: Error | null;
// Ассистенты
assistants: ChatAssistant[];
assistantsLoading: boolean;
// Провайдеры
providers: LLMProvider[];
providersLoading: boolean;
// Кэш сообщений (индексирован по sessionId)
messageCache: Map<string, Message[]>;
// Состояние стриминга
streamingState: StreamingState;
// Информация о ветках
branchInfo: Map<string, BranchInfo>;
// Действия
fetchSessions(): Promise<void>;
fetchAssistants(): Promise<void>;
fetchProviders(): Promise<void>;
loadMessages(sessionId: string): Promise<void>;
setStreamingState(state: Partial<StreamingState>): void;
switchBranch(messageId: string, index: number): void;
}
// Используйте селектор для точечной подписки, чтобы избежать лишних ре-рендеров
const sessions = useChatStore(state => state.sessions);
const isStreaming = useChatStore(state => state.streamingState.isStreaming);
| Store | Файл | Ответственность |
|---|---|---|
chatStore | shared/state/chatStore.ts | Сессии, сообщения, состояние стриминга, ветки |
settingsStore | shared/state/settingsStore.ts | Состояние настроек |
useChatStoreSync | shared/state/useChatStoreSync.ts | Синхронизация ChatDataContext → chatStore |
Детали React Context
23+ контекстов, каждый с независимой зоной ответственности:
| Context | Файл | Ответственность | Ключевые состояния/методы |
|---|---|---|---|
LocaleContext | shared/state/LocaleContext.tsx | i18n (en/zh/ja) | useTranslation(), setLocale() |
themeStore | shared/state/themeStore.ts (хост app/ThemeHost.tsx) | Режим темы, обои, пользовательский CSS | mode, resolvedMode, userTheme, customCss |
ConfirmDialogHost | shared/state/ConfirmDialogHost.tsx | Глобальный диалог подтверждения | confirm(options) |
authStore | shared/state/authStore.ts | Состояние аутентификации | user, isAuthenticated, login(), logout() |
SettingsDataHost | app/SettingsDataHost.tsx | Чтение/запись настроек | settings, updateSetting() |
ElfiContext | features/elfi/state/ElfiContext.tsx | Умный ассистент Elfi | isOpen, messages, sendMessage() |
webSearchStore | shared/state/webSearchStore.ts (хост app/WebSearchHost.tsx) | Веб-поиск | search(), results |
chatStore | shared/state/chatStore.ts | Кэш данных чата + единый API чата (бывшие ChatDataContext / UnifiedChatContext перенесены сюда) | sessions, messages, sendMessage(), regenerate(), editMessage() |
htmlPreviewStore | shared/state/htmlPreviewStore.ts (хост app/HtmlPreviewHost.tsx) | Предварительный просмотр HTML-контента | showPreview(), previewContent |
devPreviewStore | shared/state/devPreviewStore.ts (хост app/DevPreviewHost.tsx) | Предварительный просмотр в dev-режиме | isDevMode, devData |
ChatTabsContext | features/chat/state/ChatTabsContext.tsx | Управление вкладками чата | tabs, activeTabId, createTab(), closeTab() |
AgentContext | features/agents/state/AgentContext.tsx | Состояние агентов | agents, selectedAgent |
subagentStore | shared/state/subagentStore.ts (хост shared/state/SubagentHost.tsx) | Состояние субагентов | subagents, status |
MessageSelectionContext | features/chat/state/MessageSelectionContext.tsx | Множественный выбор сообщений | selectedIds, toggleSelect() |
sessionOrganizerStore | shared/state/sessionOrganizerStore.ts (хост shared/state/sessionOrganizerStore/SessionOrganizerHost.tsx) | Организация/категоризация сессий | folders, moveSession() |
worksStore | shared/state/worksStore.ts | Управление работами | works, createWork() |
musicWorksStore | shared/state/musicWorksStore.ts | Музыкальные работы | tracks, playTrack() |
musicTemplateStore | shared/state/musicTemplateStore.ts | Музыкальные шаблоны | templates |
VideoWorksContext | features/media/state/VideoWorksContext.tsx | Видеоработы | videos |
videoTemplateStore | shared/state/videoTemplateStore.ts | Видеошаблоны | templates |
TemplateContext | features/marketplace/state/TemplateContext.tsx | Шаблоны изображений | templates |
useWorldInfoHighlight | shared/hooks/characters/useWorldInfoHighlight.ts | Подсветка ключевых слов WI | highlightedKeywords |
Пользовательские хуки
60+ пользовательских хуков, организованных по функциональным доменам:
Связанные с чатом
| Хук | Файл | Назначение |
|---|---|---|
useAppState | shared/hooks/useAppState.ts | Централизованное управление флагами представлений, активной вкладкой, пользовательскими настройками |
useRouteSync | shared/hooks/useRouteSync.ts | Синхронизация URL-пути с состоянием представления |
useSessionProtection | features/chat/hooks/useSessionProtection.ts | Защита активной сессии (предотвращает сброс при обновлении) |
useDraft | features/chat/hooks/useDraft.ts | Сохранение/восстановление черновика ввода |
useAutoUpdate | shared/hooks/useAutoUpdate.ts | Автоматическое обновление приложения |
useAudioRecorder | features/media/hooks/useAudioRecorder.ts | Запись аудио |
Директории хуков по функциям
| Директория | Назначение | Примеры хуков |
|---|---|---|
features/chat/hooks/ | Операции с чатом | useMessageStream, useBranchNavigation |
features/marketplace/hooks/channel/ | Плагины каналов | useChannelPlugins |
shared/hooks/characters/character/ | Карточки персонажей | useCharacterCards |
shared/hooks/characters/worldinfo/ | World info | useWorldInfo |
shared/hooks/characters/ | Подсветка WI / теги | useWorldInfoHighlight, useTagsData |
features/characters/hooks/sprites/ | Спрайты выражений | useSprites |
features/settings/components/tabs/tools-tab/hooks/ | MCP-серверы | useMcpManagement |
features/kb/hooks/ | Система заметок | useNotesData |
features/todo/hooks/ | Задачи | useTodoData |
features/tasks/hooks/ | Управление задачами | useTasksData |
features/cron/hooks/ | Запланированные задания | useCronJobs |
features/marketplace/hooks/skills/ | Управление навыками | useSkillHub |
Архитектура компонентов
Структура макета
graph TB
Root["div.fixed.inset-0.flex.bg-background"]
WS[WorkspaceShell]
WR[WorkspaceRail — left navigation rail]
TB[TitleBar — tab bar + window control]
MC[Main Content — routed content area]
Root --> WS
WS --> WR
WS --> TB
WS --> MC
MC --> ChatPage
MC --> AgentPage
MC --> SettingsPage
MC --> RoleplayPage
MC --> NotesPage
MC --> CoworkPage
- WorkspaceRail: Левая фиксированная навигационная панель с точками входа: Главная, Агенты, Настройки
- WorkspaceShell: Основной контейнер макета; управляет Rail, панелью вкладок и областью контента
- TitleBar: Панель с вкладками + кнопки управления окном (развернуть/свернуть/закрыть)
- Main Content: Динамически отображает компоненты страниц в соответствии с маршрутом
Расширение рабочего пространства (Workspace Registry)
«Панель рабочего пространства» между страницами Chat/Agent (редактор / git diff / HTML-предпросмотр / canvas / область design studio справа от дерева файлов) расширяется через реестр WorkspaceDefinition. WorkspaceArea.tsx как хост-оболочка выполняет только макет + диспетчеризацию реестра — без каких-либо бизнес-ветвлений вида activeView === 'X'.
graph TB
WA["WorkspaceArea<br/>(layout + dispatch)"]
Reg["registry.ts<br/>WORKSPACE_REGISTRY[]"]
Slot["WorkspaceToolbarProvider<br/>(slot context)"]
Aff["host-affordances<br/>(cross-view coordination)"]
Defs["definitions/"]
WA -->|findActiveWorkspace| Reg
WA -->|wraps| Slot
WA -->|consumes| Aff
Reg -->|imports| Defs
Defs --> EW[EditorWorkspace]
Defs --> GW[GitWorkspace]
Defs --> HW[HtmlPreviewWorkspace]
Defs --> AW[A2UIWorkspace]
Defs --> DW[DevPreviewWorkspace]
Defs --> DS[DesignStudioWorkspace]
Defs --> CW[CanvasWorkspace]
Каждый WorkspaceDefinition самостоятельно управляет:
- Идентификатором
view+ предикатомisAvailable(ctx) - Собственным состоянием режима представления (split/code/preview и др. — внутренний useState, не вытекает в хост)
- Публикацией конфигурации тулбара через
useWorkspaceToolbar(config)в слот;<WorkspaceToolbar>хоста читает его черезuseWorkspaceToolbarConfig() - Необязательным хуком
onClose(ctx, cb)(срабатывает по кнопке закрытия вне дерева React; канал функции закрытия используетWorkspaceCallbacks)
Добавление нового типа рабочего пространства = изменение 2 файлов: добавить запись в массив registry.ts + создать definitions/<Name>Workspace.tsx. Файл WorkspaceArea.tsx изменять не нужно. Подробный рецепт — в разделе «🧩 Add New Workspace Type» файла packages/renderer/CLAUDE.md; спецификация контракта — в openspec/specs/workspace-registry/spec.md.
Межпредставленческая координация (например, кнопка «Preview» в редакторе, переключающая на htmlPreview, или скрытие всей панели рабочего пространства при максимизации HTML-предпросмотра) централизована в host-affordances.ts — единственное место, где допускается условие activeView === 'X'.
| Файл | Ответственность |
|---|---|
features/chat/components/content/WorkspaceArea.tsx | Хост-оболочка (макет + чтение слота + диспетчеризация реестра, без бизнес-ветвлений) |
features/chat/components/content/workspaces/types.ts | Контракт WorkspaceDefinition / WorkspaceHostContext / WorkspaceCallbacks |
features/chat/components/content/workspaces/registry.ts | Единая точка регистрации WORKSPACE_REGISTRY + запрос findActiveWorkspace |
features/chat/components/content/workspaces/host-affordances.ts | Межпредставленческая координация (переключение и скрытие при максимизации) |
features/chat/components/content/workspaces/workspace-toolbar-slot{,-internals,-hooks}.{tsx,ts} | Трио слота тулбара (Provider / contexts / hooks) |
features/chat/components/content/workspaces/definitions/<Name>Workspace.tsx | 7 определений рабочих пространств (editor / git / htmlPreview / a2ui / devPreview / designStudio / canvas) |
features/chat/components/content/workspaces/inline-file-viewers.tsx | InlineMarkdownViewer + InlineBinaryViewer (изображение/аудио/видео/pdf + заглушка «не удаётся просмотреть») |
features/chat/components/content/workspaces/inline-file-viewers-types.ts | Определение типов файлов: getBinaryFileType / isMarkdownFile / isNonTextFile |
features/chat/components/content/unified-page/hooks/useWorkspaceState.ts | Хук состояния рабочего пространства (маршрутизация .html → openHtmlPreview() + маршрутизация бинарных файлов в handleFileSelect) |
Система маршрутизации
Используется HashRouter (Electron не поддерживает BrowserRouter) с хуком useRouteSync для синхронизации URL с состоянием представления:
// Синхронизация маршрута с состоянием представления
function useRouteSync(pathname: string, state: AppState) {
useEffect(() => {
if (pathname.startsWith('/chat')) {
state.setIsChatView(true);
state.setIsHomeView(false);
} else if (pathname.startsWith('/agents')) {
state.setIsAgentView(true);
} else if (pathname.startsWith('/settings')) {
state.setIsSettingsView(true);
}
}, [pathname]);
}
Интернационализация (i18n)
Поддерживается 3 языка; используется хук useTranslation():
| Язык | Директория | Файлы |
|---|---|---|
| Английский | locales/en/ | 15+ JSON-файлов |
| Китайский (упрощённый) | locales/zh/ | 15+ JSON-файлов |
| Японский | locales/ja/ | 15+ JSON-файлов |
Файлы переводов разделены по функциональным доменам: common.json, chat.json, settings.json, characters.json, elfi.json и др.
Хук AppState
useAppState централизованно управляет глобальным состоянием UI:
// Упрощённый интерфейс AppState
interface AppState {
// Флаги представлений
isHomeView: boolean;
isChatView: boolean;
isAgentView: boolean;
isSettingsView: boolean;
// Активная сессия
selectedSession: string | null;
activeSessions: Set<string>;
processingSessions: Set<string>;
// Вкладки
activeTab: string;
settingsInitialTab: string;
// Пользовательские настройки
autoExpandTools: boolean;
showRawParameters: boolean;
showThinking: boolean;
autoScrollToBottom: boolean;
sendByCtrlEnter: boolean;
// Модальные окна
showVersionModal: boolean;
}
Конфигурация сборки
| Параметр | Значение |
|---|---|
| Инструмент сборки | Vite 7.0 |
| Стилизация | Tailwind CSS 3.4 + CSS-переменные (семантические токены) |
| Разделение кода | Ленивая загрузка на уровне маршрутов (директория lazy/, 6 файлов + 1 barrel по назначению) |
| Tree shaking | Именованные импорты |
| Ограничение главного бандла | 500 KB |
Связанные файлы
| Файл | Описание |
|---|---|
packages/renderer/src/app/App.tsx | Точка входа приложения, иерархия провайдеров и макет |
packages/renderer/src/shared/state/chatStore.ts | Zustand Store состояния чата |
packages/renderer/src/shared/state/settingsStore.ts | Zustand Store состояния настроек |
packages/renderer/src/shared/state/chatStore.ts | Единый API чата (бывший UnifiedChatContext перенесён в chatStore) |
packages/renderer/src/shared/state/themeStore.ts | Store темы (бывший ThemeContext) |
packages/renderer/src/shared/hooks/useAppState.ts | Централизованное управление состоянием UI |
packages/renderer/src/shared/hooks/useRouteSync.ts | Синхронизация маршрутов |
packages/renderer/src/app/layout/WorkspaceShell.tsx | Основной контейнер макета |
packages/renderer/src/app/layout/WorkspaceRail.tsx | Левая навигационная панель |
packages/renderer/src/app/title-bar/ | Компоненты панели вкладок |
packages/renderer/src/app/lazy/ | Директория компонентов с ленивой загрузкой (pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx, barrel в lazy/index.ts) |
packages/renderer/src/features/chat/components/content/WorkspaceArea.tsx | Центральная хост-оболочка рабочего пространства (макет + диспетчеризация реестра, без бизнес-ветвлений) |
packages/renderer/src/features/chat/components/content/workspaces/ | Реестр рабочих пространств (контракт WorkspaceDefinition + 7 файлов определений + слот тулбара + host-affordances) |
packages/renderer/src/app/railItems.ts | Конфигурация навигационной панели |
packages/renderer/src/app/mainContentRouter.tsx | Разбор маршрутизатора основного контента |
packages/renderer/CLAUDE.md | Стандарты разработки UI фронтенда |