mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
refactor(renderer): flatten src/renderer/src to src/renderer
Move all renderer source from src/renderer/src/* up one level to
src/renderer/*, removing the redundant nested src directory.
- Update path aliases (@renderer, @types, @logger, @data) and TanStack
Router paths in electron.vite.config.ts; update tsconfig.{json,web,node}
path mappings and include globs.
- Fix Vite root-relative script paths in the 8 renderer HTML entries.
- Update cross-process relative imports in main/preload (language,
apiServer models, preload index) to drop the /src segment.
- Switch renderer test imports of the logger mock to the @test-mocks alias.
- Update hardcoded renderer paths in scripts and their fixtures, lint
configs (eslint/oxlint/biome), CODEOWNERS, docs, and the data-classify tool.
- Convert deep (../../+) relative imports within the renderer to the
@renderer alias (69 files, 108 imports); keep single-level relatives.
- Fix doc links broken by the move and correct one pre-existing broken
link in naming-conventions.md.
This commit is contained in:
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -1,5 +1,5 @@
|
||||
/src/renderer/src/store/ @0xfullex @DeJeune
|
||||
/src/renderer/src/databases/ @0xfullex @DeJeune
|
||||
/src/renderer/store/ @0xfullex @DeJeune
|
||||
/src/renderer/databases/ @0xfullex @DeJeune
|
||||
/src/main/services/ConfigManager.ts @0xfullex @DeJeune
|
||||
/src/shared/IpcChannel.ts @0xfullex @DeJeune
|
||||
/src/main/ipc.ts @0xfullex @DeJeune
|
||||
@@ -8,8 +8,8 @@
|
||||
/src/main/core/ @0xfullex
|
||||
/src/shared/data/ @0xfullex
|
||||
/src/main/data/ @0xfullex
|
||||
/src/renderer/src/data/ @0xfullex
|
||||
/src/renderer/src/core/ @0xfullex
|
||||
/src/renderer/data/ @0xfullex
|
||||
/src/renderer/core/ @0xfullex
|
||||
/src/preload/ @0xfullex
|
||||
/v2-refactor-temp/ @0xfullex
|
||||
/docs/references/data/ @0xfullex
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"src/main/services/nutstore/sso/lib/**",
|
||||
"src/main/integration/cherryai/index.js",
|
||||
"src/main/services/nutstore/sso/lib/**",
|
||||
"src/renderer/src/ui/**",
|
||||
"src/renderer/ui/**",
|
||||
"packages/**/dist",
|
||||
"eslint.config.mjs",
|
||||
"v2-refactor-temp/**"
|
||||
|
||||
@@ -99,7 +99,7 @@ Use the `gh-create-issue` skill. Fallback: read `.agents/skills/gh-create-issue/
|
||||
|
||||
### TypeScript
|
||||
|
||||
- Place shared type definitions in `src/renderer/src/types/` or `src/shared/`.
|
||||
- Place shared type definitions in `src/renderer/types/` or `src/shared/`.
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
@@ -126,7 +126,7 @@ logger.error("message", error);
|
||||
|
||||
- All user-visible strings must use `i18next` — never hardcode UI strings
|
||||
- Run `pnpm i18n:check` to validate; `pnpm i18n:sync` to add missing keys
|
||||
- Locale files in `src/renderer/src/i18n/`
|
||||
- Locale files in `src/renderer/i18n/`
|
||||
|
||||
### UI Design
|
||||
|
||||
|
||||
10
DESIGN.md
10
DESIGN.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## 1. Visual Theme & Atmosphere
|
||||
|
||||
> **Source of truth:** token sources live in `packages/ui/src/styles/tokens/` and Tailwind-facing aliases are generated in `packages/ui/src/styles/theme.css`. Renderer-only bridge aliases live in `src/renderer/src/assets/styles/tailwind.css`. This document references public aliases only when they are actually exported; for actual values open the relevant token source or generated theme alias.
|
||||
> **Source of truth:** token sources live in `packages/ui/src/styles/tokens/` and Tailwind-facing aliases are generated in `packages/ui/src/styles/theme.css`. Renderer-only bridge aliases live in `src/renderer/assets/styles/tailwind.css`. This document references public aliases only when they are actually exported; for actual values open the relevant token source or generated theme alias.
|
||||
|
||||
Cherry Studio is a shadcn/ui-based design system built for an AI conversation application. The design language follows a neutral-first approach — a restrained, systematic palette rooted in pure neutral grays where the interface itself recedes to let content take center stage. The aesthetic is utilitarian-modern: clean surfaces, subtle borders, and restrained use of the exported primary color for true primary actions, creating a tool that feels professional, focused, and endlessly customizable through its robust light/dark mode support.
|
||||
|
||||
@@ -487,11 +487,11 @@ When a search field needs an inline trailing button (e.g. add provider in `Provi
|
||||
- Foreground: `var(--color-foreground)` at full opacity
|
||||
- Disabled: `pointer-events-none opacity-30`
|
||||
|
||||
Canonical implementation: `providerListClasses.searchInlineAddButton` in `src/renderer/src/pages/settings/ProviderSettings/primitives/classNames.ts`. The search wrap itself stays the standard input surface (`bg-background`, hairline border, `rounded-xl`).
|
||||
Canonical implementation: `providerListClasses.searchInlineAddButton` in `src/renderer/pages/settings/ProviderSettings/primitives/classNames.ts`. The search wrap itself stays the standard input surface (`bg-background`, hairline border, `rounded-xl`).
|
||||
|
||||
### Sidebar
|
||||
|
||||
Sidebar primitives currently live in `src/renderer/src/components/Sidebar`, not in `@cherrystudio/ui`. Treat this section as renderer sidebar guidance until a shared `@cherrystudio/ui` sidebar API exists.
|
||||
Sidebar primitives currently live in `src/renderer/components/Sidebar`, not in `@cherrystudio/ui`. Treat this section as renderer sidebar guidance until a shared `@cherrystudio/ui` sidebar API exists.
|
||||
|
||||
The page owns the outer wrapper (width / Scrollbar / padding). Reusable sidebar internals should own spacing, sizing, and active state so individual pages do not hand-roll divergent menus.
|
||||
|
||||
@@ -526,7 +526,7 @@ The page owns the outer wrapper (width / Scrollbar / padding). Reusable sidebar
|
||||
|
||||
> If a sidebar elsewhere needs different spacing, propose a shared renderer variant before hard-coding page-local overrides.
|
||||
>
|
||||
> **Target rule:** once the `SidebarHeader / SidebarSection / SidebarSectionTitle / SidebarMenuItem` family lands in `@cherrystudio/ui`, hand-rolled sidebar menus will not be allowed. Until that family ships, compose with `MenuList` + `MenuItem` + project-level className tokens (see `src/renderer/src/pages/settings/index.tsx` for the canonical token pattern: `settingsSubmenuItemClassName`, `settingsSubmenuItemLabelClassName`, `settingsSubmenuSectionTitleClassName`, `settingsSubmenuDividerClassName`).
|
||||
> **Target rule:** once the `SidebarHeader / SidebarSection / SidebarSectionTitle / SidebarMenuItem` family lands in `@cherrystudio/ui`, hand-rolled sidebar menus will not be allowed. Until that family ships, compose with `MenuList` + `MenuItem` + project-level className tokens (see `src/renderer/pages/settings/index.tsx` for the canonical token pattern: `settingsSubmenuItemClassName`, `settingsSubmenuItemLabelClassName`, `settingsSubmenuSectionTitleClassName`, `settingsSubmenuDividerClassName`).
|
||||
|
||||
### Page Header
|
||||
|
||||
@@ -612,7 +612,7 @@ Submenu composition rules:
|
||||
- Use `PageHeader` from `@cherrystudio/ui` at the top — do not hand-roll a header.
|
||||
- **Section-title-as-page-title exception**: when a page-level label is itself a *group name* that should match in-list group labels, keep using `PageHeader` and pass `titleClassName="font-normal text-foreground-muted text-xs leading-4"` so the heading swaps to section-title typography while preserving the same 16px line box. The PageHeader's `mt-3 + h-8 + mb-2` outer geometry is preserved, so the label baseline still aligns with the right column's PageHeader heading. See `page-header.stories.tsx` › `SectionTitleStyle` for the canonical example.
|
||||
- Wrap menu rows in `MenuList` with `gap-1`; group with `MenuDivider` + a section title `<div>` carrying `settingsSubmenuSectionTitleClassName`.
|
||||
- Each row is a `MenuItem` styled by the canonical settings token pair: `settingsSubmenuItemClassName` on `className` (height / hover / active surface) and `settingsSubmenuItemLabelClassName` on `labelClassName` (`group-data-[active=true]:font-medium` for the bold-on-active label). Both tokens live in `src/renderer/src/pages/settings/index.tsx`.
|
||||
- Each row is a `MenuItem` styled by the canonical settings token pair: `settingsSubmenuItemClassName` on `className` (height / hover / active surface) and `settingsSubmenuItemLabelClassName` on `labelClassName` (`group-data-[active=true]:font-medium` for the bold-on-active label). Both tokens live in `src/renderer/pages/settings/index.tsx`.
|
||||
- Provider-style nested lists (`ProviderList`) follow the same shape: `PageHeader` + search field with trailing action + scroll body. They use their own scoped tokens in `ProviderSettings/primitives/classNames.ts` but keep the 200px column convention.
|
||||
|
||||
### Spacing System
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"!src/main/services/nutstore/sso/lib/**",
|
||||
"!**/tailwind.css",
|
||||
"!**/package.json",
|
||||
"!src/renderer/src/routeTree.gen.ts",
|
||||
"!src/renderer/routeTree.gen.ts",
|
||||
"!.zed/**",
|
||||
"!v2-refactor-temp/**"
|
||||
],
|
||||
@@ -94,7 +94,7 @@
|
||||
"enabled": true,
|
||||
"includes": [
|
||||
"!**/tailwind.css",
|
||||
"!src/renderer/src/routeTree.gen.ts",
|
||||
"!src/renderer/routeTree.gen.ts",
|
||||
"!v2-refactor-temp/**",
|
||||
"src/renderer/**/*.{tsx,ts}"
|
||||
],
|
||||
|
||||
@@ -89,7 +89,7 @@ To avoid missing keys, all dynamically translated texts should first maintain a
|
||||
For example:
|
||||
|
||||
```ts
|
||||
// src/renderer/src/i18n/label.ts
|
||||
// src/renderer/i18n/label.ts
|
||||
const themeModeKeyMap = {
|
||||
dark: "settings.theme.dark",
|
||||
light: "settings.theme.light",
|
||||
|
||||
@@ -145,7 +145,7 @@ For example, if the chain is `[AuthMiddleware, CacheMiddleware, LoggingMiddlewar
|
||||
|
||||
### Registering Middleware
|
||||
|
||||
Middleware is registered in `src/renderer/src/providers/middleware/register.ts` (or a similar configuration file).
|
||||
Middleware is registered in `src/renderer/providers/middleware/register.ts` (or a similar configuration file).
|
||||
|
||||
```typescript
|
||||
// register.ts
|
||||
|
||||
@@ -39,7 +39,7 @@ Cherry Studio's AI calls follow a clear layered architecture:
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Service Layer │
|
||||
│ src/renderer/src/services/ │
|
||||
│ src/renderer/services/ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ ApiService.ts │ │
|
||||
│ │ - transformMessagesAndFetch() │ │
|
||||
@@ -51,7 +51,7 @@ Cherry Studio's AI calls follow a clear layered architecture:
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ AI Provider Layer │
|
||||
│ src/renderer/src/aiCore/ │
|
||||
│ src/renderer/aiCore/ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ AiProvider (AiProvider.ts) │ │
|
||||
│ │ - completions() │ │
|
||||
@@ -155,7 +155,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 2. ApiService.transformMessagesAndFetch() │
|
||||
│ Location: src/renderer/src/services/ApiService.ts:92 │
|
||||
│ Location: src/renderer/services/ApiService.ts:92 │
|
||||
│ │
|
||||
│ Step 2.1: ConversationService.prepareMessagesForModel() │
|
||||
│ ├─ Message format conversion (UI Message → Model Message) │
|
||||
@@ -174,7 +174,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 3. ApiService.fetchChatCompletion() │
|
||||
│ Location: src/renderer/src/services/ApiService.ts:139 │
|
||||
│ Location: src/renderer/services/ApiService.ts:139 │
|
||||
│ │
|
||||
│ Step 3.1: getProviderByModel() + API Key Rotation │
|
||||
│ ├─ Get provider configuration │
|
||||
@@ -199,7 +199,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 4. AiProvider.completions() │
|
||||
│ Location: src/renderer/src/aiCore/index_new.ts:116 │
|
||||
│ Location: src/renderer/aiCore/index_new.ts:116 │
|
||||
│ │
|
||||
│ Step 4.1: providerToAiSdkConfig() │
|
||||
│ ├─ Convert Cherry Provider → AI SDK Config │
|
||||
@@ -214,7 +214,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 5. AiProvider._completionsOrImageGeneration() │
|
||||
│ Location: src/renderer/src/aiCore/index_new.ts:167 │
|
||||
│ Location: src/renderer/aiCore/index_new.ts:167 │
|
||||
│ │
|
||||
│ Decision: │
|
||||
│ ├─ Image generation endpoint → legacyProvider.completions()│
|
||||
@@ -224,7 +224,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 6. AiProvider.modernCompletions() │
|
||||
│ Location: src/renderer/src/aiCore/index_new.ts:284 │
|
||||
│ Location: src/renderer/aiCore/index_new.ts:284 │
|
||||
│ │
|
||||
│ Step 6.1: buildPlugins(config) │
|
||||
│ └─ Build plugin array (Reasoning, ToolUse, WebSearch, etc.)│
|
||||
@@ -291,7 +291,7 @@ User Input (UI)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 10. Stream Data Processing │
|
||||
│ Location: src/renderer/src/aiCore/chunk/ │
|
||||
│ Location: src/renderer/aiCore/chunk/ │
|
||||
│ │
|
||||
│ Step 10.1: AiSdkToChunkAdapter.processStream() │
|
||||
│ ├─ Listen to AI SDK's textStream │
|
||||
@@ -377,7 +377,7 @@ plugins = [ReasoningPlugin, ToolUsePlugin, WebSearchPlugin]
|
||||
|
||||
#### File Location
|
||||
|
||||
`src/renderer/src/services/ApiService.ts`
|
||||
`src/renderer/services/ApiService.ts`
|
||||
|
||||
#### Core Responsibilities
|
||||
|
||||
@@ -556,7 +556,7 @@ function getRotatedApiKey(provider: Provider): string {
|
||||
|
||||
#### File Location
|
||||
|
||||
`src/renderer/src/aiCore/index_new.ts`
|
||||
`src/renderer/aiCore/index_new.ts`
|
||||
|
||||
#### Core Responsibilities
|
||||
|
||||
@@ -682,7 +682,7 @@ private async modernCompletions(
|
||||
|
||||
#### providerToAiSdkConfig() Details
|
||||
|
||||
**File**: `src/renderer/src/aiCore/provider/providerConfig.ts`
|
||||
**File**: `src/renderer/aiCore/provider/providerConfig.ts`
|
||||
|
||||
```typescript
|
||||
export function providerToAiSdkConfig(
|
||||
@@ -1132,7 +1132,7 @@ Other built-in plugins remain unchanged. See:
|
||||
|
||||
### 6.1 Message Conversion
|
||||
|
||||
**File**: `src/renderer/src/services/ConversationService.ts`
|
||||
**File**: `src/renderer/services/ConversationService.ts`
|
||||
|
||||
```typescript
|
||||
export class ConversationService {
|
||||
@@ -1190,7 +1190,7 @@ export class ConversationService {
|
||||
|
||||
### 6.2 Stream Data Adaptation
|
||||
|
||||
**File**: `src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts`
|
||||
**File**: `src/renderer/aiCore/chunk/AiSdkToChunkAdapter.ts`
|
||||
|
||||
```typescript
|
||||
export default class AiSdkToChunkAdapter {
|
||||
@@ -1411,7 +1411,7 @@ const executor = await createExecutor("custom-provider", {
|
||||
|
||||
#### Span Creation
|
||||
|
||||
**File**: `src/renderer/src/services/SpanManagerService.ts`
|
||||
**File**: `src/renderer/services/SpanManagerService.ts`
|
||||
|
||||
```typescript
|
||||
export function addSpan(params: StartSpanParams): Span | null {
|
||||
@@ -1671,16 +1671,16 @@ Current test coverage:
|
||||
|
||||
### Service Layer
|
||||
|
||||
- `src/renderer/src/services/ApiService.ts` - Main API service
|
||||
- `src/renderer/src/services/ConversationService.ts` - Message preparation
|
||||
- `src/renderer/src/services/SpanManagerService.ts` - Trace management
|
||||
- `src/renderer/services/ApiService.ts` - Main API service
|
||||
- `src/renderer/services/ConversationService.ts` - Message preparation
|
||||
- `src/renderer/services/SpanManagerService.ts` - Trace management
|
||||
|
||||
### AI Provider Layer
|
||||
|
||||
- `src/renderer/src/aiCore/index_new.ts` - AiProvider
|
||||
- `src/renderer/src/aiCore/provider/providerConfig.ts` - Provider configuration
|
||||
- `src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts` - Stream adaptation
|
||||
- `src/renderer/src/aiCore/plugins/PluginBuilder.ts` - Plugin building
|
||||
- `src/renderer/aiCore/index_new.ts` - AiProvider
|
||||
- `src/renderer/aiCore/provider/providerConfig.ts` - Provider configuration
|
||||
- `src/renderer/aiCore/chunk/AiSdkToChunkAdapter.ts` - Stream adaptation
|
||||
- `src/renderer/aiCore/plugins/PluginBuilder.ts` - Plugin building
|
||||
|
||||
### Core Package
|
||||
|
||||
@@ -1693,8 +1693,8 @@ Current test coverage:
|
||||
|
||||
### App-Level Extensions
|
||||
|
||||
- `src/renderer/src/aiCore/provider/extensions/index.ts` - App-level provider extensions
|
||||
- `src/renderer/src/aiCore/types/merged.ts` - Merged types (core + app extensions)
|
||||
- `src/renderer/aiCore/provider/extensions/index.ts` - App-level provider extensions
|
||||
- `src/renderer/aiCore/types/merged.ts` - Merged types (core + app extensions)
|
||||
|
||||
### Test Utilities
|
||||
|
||||
|
||||
@@ -179,10 +179,10 @@ cherry-studio
|
||||
|-----------|----------|---------------|
|
||||
| Service Lifecycle | `src/main/core/lifecycle/` | [Lifecycle Reference](./lifecycle/README.md) |
|
||||
| Data Layer | `src/main/data/` | [Data Reference](./data/README.md) |
|
||||
| AI Core | `src/renderer/src/aiCore/` | [AI Core Architecture](./ai-core-architecture.md) |
|
||||
| AI Core | `src/renderer/aiCore/` | [AI Core Architecture](./ai-core-architecture.md) |
|
||||
| MCP (Tool Use) | `src/main/services/mcp/` | — |
|
||||
| Knowledge (RAG) | `src/main/knowledge/` | [KnowledgeService](./knowledge/knowledge-service.md) |
|
||||
| Message System | `src/renderer/src/store/` | [Message System](./messaging/message-system.md) |
|
||||
| Message System | `src/renderer/store/` | [Message System](./messaging/message-system.md) |
|
||||
| CherryClaw (Agent) | `src/main/services/agents/` | [CherryClaw Overview](./cherryclaw/overview.md) |
|
||||
| API Server | `src/main/apiServer/` | [App Upgrade Config](./app-upgrade.md) |
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ The user-facing code execution component is [CodeBlockView][codeblock-view-link]
|
||||
- **State Management and Output Display**: Uses `executionResult` to manage all execution output; whenever there's any result (text or image), the [StatusBar][statusbar-link] component is rendered for unified display.
|
||||
|
||||
```typescript
|
||||
// src/renderer/src/components/CodeBlockView/view.tsx
|
||||
// src/renderer/components/CodeBlockView/view.tsx
|
||||
const [executionResult, setExecutionResult] = useState<{ text: string; image?: string } | null>(null)
|
||||
|
||||
const handleRunScript = useCallback(() => {
|
||||
@@ -119,7 +119,7 @@ The core Python execution happens inside the Web Worker defined in [pyodide.work
|
||||
<!-- Link Definitions -->
|
||||
|
||||
[pyodide-link]: https://pyodide.org/
|
||||
[codeblock-view-link]: /src/renderer/src/components/CodeBlockView/view.tsx
|
||||
[pyodide-service-link]: /src/renderer/src/services/PyodideService.ts
|
||||
[pyodide-worker-link]: /src/renderer/src/workers/pyodide.worker.ts
|
||||
[statusbar-link]: /src/renderer/src/components/CodeBlockView/StatusBar.tsx
|
||||
[codeblock-view-link]: /src/renderer/components/CodeBlockView/view.tsx
|
||||
[pyodide-service-link]: /src/renderer/services/PyodideService.ts
|
||||
[pyodide-worker-link]: /src/renderer/workers/pyodide.worker.ts
|
||||
[statusbar-link]: /src/renderer/components/CodeBlockView/StatusBar.tsx
|
||||
|
||||
@@ -268,8 +268,8 @@ See [App State Overview](./app-state-overview.md) for full rules and the key reg
|
||||
- `src/main/data/db/` - Database schemas
|
||||
|
||||
### Renderer Process Implementation
|
||||
- `src/renderer/src/data/DataApiService.ts` - API client
|
||||
- `src/renderer/src/data/CacheService.ts` - Cache service
|
||||
- `src/renderer/src/data/PreferenceService.ts` - Preference service
|
||||
- `src/renderer/src/data/hooks/` - React hooks
|
||||
- `src/renderer/data/DataApiService.ts` - API client
|
||||
- `src/renderer/data/CacheService.ts` - Cache service
|
||||
- `src/renderer/data/PreferenceService.ts` - Preference service
|
||||
- `src/renderer/data/hooks/` - React hooks
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ Create a custom hook that merges presets with user overrides:
|
||||
> **Note:** The hook below is a basic example. Your actual implementation should be tailored to your specific data structure and usage patterns. Consider factors like: which fields are user-editable, how merging should work for nested objects, whether you need filtering/sorting, etc.
|
||||
|
||||
```typescript
|
||||
// src/renderer/src/hooks/useProviders.ts
|
||||
// src/renderer/hooks/useProviders.ts
|
||||
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Non-obvious rules the code enforces; assume them when designing consumers.
|
||||
1. **Same-value write is a no-op.** Equality via `lodash.isEqual`. No broadcast, no subscriber fire, no hook re-render. (`src/main/data/CacheService.ts` `isEqual` guards before `broadcastSync` / notifier)
|
||||
2. **TTL-only refresh does not fire subscribers.** Updating `expireAt` on the same value is silent.
|
||||
3. **Subscribers fire only on explicit writes.** Lazy TTL cleanup, the 10-min GC sweep, and `onStop` do not fire.
|
||||
4. **Hooks + TTL is discouraged.** `useCache` / `useSharedCache` log a warn when the key has TTL (`src/renderer/src/data/hooks/useCache.ts:186-192,289-295`) — values can expire between renders.
|
||||
4. **Hooks + TTL is discouraged.** `useCache` / `useSharedCache` log a warn when the key has TTL (`src/renderer/data/hooks/useCache.ts:186-192,289-295`) — values can expire between renders.
|
||||
5. **Hooks pin cache entries.** `registerHook` / `unregisterHook` refcount keys; `delete` / `deleteShared` return `false` while any hook is active.
|
||||
6. **Persist has no delete.** Persist keys are fixed by schema; the API exposes only `getPersist` / `setPersist` / `hasPersist`.
|
||||
7. **TTL uses absolute `expireAt` (Unix ms).** Every process expires the same entry at the same instant, regardless of clock skew in IPC delivery.
|
||||
@@ -107,4 +107,4 @@ Non-obvious rules the code enforces; assume them when designing consumers.
|
||||
|
||||
- [Cache Usage](./cache-usage.md) — React hooks, direct API, patterns
|
||||
- [Cache Schema Guide](./cache-schema-guide.md) — Adding fixed and template keys
|
||||
- Source: `src/main/data/CacheService.ts`, `src/renderer/src/data/CacheService.ts`, `src/renderer/src/data/hooks/useCache.ts`, `src/shared/data/cache/`
|
||||
- Source: `src/main/data/CacheService.ts`, `src/renderer/data/CacheService.ts`, `src/renderer/data/hooks/useCache.ts`, `src/shared/data/cache/`
|
||||
|
||||
@@ -75,7 +75,7 @@ Migrators (Redux/Dexie → SQLite) use the pure-function counterparts `assignOrd
|
||||
|
||||
### 4. Renderer — `useReorder` hook
|
||||
|
||||
File: `src/renderer/src/data/hooks/useReorder.ts`. One hook on top of `useMutation`; drop its `applyReorderedList` straight into a drag-and-drop callback.
|
||||
File: `src/renderer/data/hooks/useReorder.ts`. One hook on top of `useMutation`; drop its `applyReorderedList` straight into a drag-and-drop callback.
|
||||
|
||||
```tsx
|
||||
import { useQuery } from '@data/hooks/useDataApi'
|
||||
|
||||
@@ -82,7 +82,7 @@ src/shared/file/types/tree.ts ← shared with renderer
|
||||
├── CreateTreeIpcResult — { treeId, snapshot }
|
||||
└── TreeMutationPushPayload — { treeId, event }
|
||||
|
||||
src/renderer/src/hooks/useDirectoryTree.ts ← renderer hook
|
||||
src/renderer/hooks/useDirectoryTree.ts ← renderer hook
|
||||
├── On mount → File_TreeCreate → rehydrate TreeNode class hierarchy
|
||||
├── On File_TreeMutation (filtered by treeId) → applyMutation in place
|
||||
├── Returns { root, isLoading, error, version, treeId, getNode }
|
||||
@@ -363,7 +363,7 @@ Three suites under `src/main/services/file/tree/__tests__/`:
|
||||
- **`TreeNode.test.ts`** — class invariants: rename cascade, identity preservation, JSON serialization shape.
|
||||
- **`search.test.ts`** — `listDirectory` happy path + error branches (ripgrep unavailable, EACCES on root).
|
||||
|
||||
Renderer-side: `src/renderer/src/hooks/__tests__/useDirectoryTree.test.tsx` covers mount/unmount, mutation application, mid-flight cancel, StrictMode remount, post-unmount rejection, and treeId mismatch filtering.
|
||||
Renderer-side: `src/renderer/hooks/__tests__/useDirectoryTree.test.tsx` covers mount/unmount, mutation application, mid-flight cancel, StrictMode remount, post-unmount rejection, and treeId mismatch filtering.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ The 90% case. See later sections for full rules and edge cases.
|
||||
| Bucket directory (categorical container) | lowercase **plural** noun | `services/`, `utils/`, `hooks/` |
|
||||
| Business / domain module directory | `camelCase` | `apiServer/`, `fileProcessing/` |
|
||||
| `packages/ui/` directory | `kebab-case` | `primitives/`, `button-group/` |
|
||||
| TanStack route file under `src/renderer/src/routes/` | `kebab-case.tsx` | `api-server.tsx`, `quick-assistant.tsx` |
|
||||
| TanStack route file under `src/renderer/routes/` | `kebab-case.tsx` | `api-server.tsx`, `quick-assistant.tsx` |
|
||||
|
||||
> Stateful classes use only `Service` (default) or `Manager` (instance pool) — see §5.2. Files placed inside any `utils/` directory drop the `Utils` suffix — the directory already declares the role; see §3.2.
|
||||
|
||||
@@ -64,8 +64,8 @@ Three rules trump any specific table below when in conflict:
|
||||
|
||||
| Location | Convention | Rationale |
|
||||
|---|---|---|
|
||||
| `src/renderer/src/components/**` | `PascalCase.tsx` | Filename mirrors the exported component name. |
|
||||
| `src/renderer/src/pages/**` | `PascalCase.tsx` | Filename mirrors the exported component name. |
|
||||
| `src/renderer/components/**` | `PascalCase.tsx` | Filename mirrors the exported component name. |
|
||||
| `src/renderer/pages/**` | `PascalCase.tsx` | Filename mirrors the exported component name. |
|
||||
| `packages/ui/**` (shadcn-derived) | `kebab-case.tsx` | Required by shadcn CLI for cross-OS file resolution. |
|
||||
|
||||
The component's **exported identifier** is always `PascalCase`, regardless of filename style:
|
||||
@@ -74,7 +74,7 @@ The component's **exported identifier** is always `PascalCase`, regardless of fi
|
||||
// packages/ui/src/components/primitives/button.tsx
|
||||
export function Button() { /* ... */ }
|
||||
|
||||
// src/renderer/src/components/Sidebar.tsx
|
||||
// src/renderer/components/Sidebar.tsx
|
||||
export function Sidebar() { /* ... */ }
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ utils/notesTree.ts ✅
|
||||
|
||||
A `*Utils` suffix is used only when the file lives outside any `utils/` directory.
|
||||
|
||||
**Hooks (`useXxx.ts`)** — live in `src/renderer/src/hooks/` (default, may group into sub-folders by feature) or co-located with the consuming feature.
|
||||
**Hooks (`useXxx.ts`)** — live in `src/renderer/hooks/` (default, may group into sub-folders by feature) or co-located with the consuming feature.
|
||||
|
||||
**Renderer wrappers around `window.api.*`** — the renderer does not use `*Api`, `*Client`, or any other IPC-wrapper suffix. Categorize wrappers by module shape per §5.2.
|
||||
|
||||
@@ -158,9 +158,9 @@ packages/SomePkg/ ❌ (PascalCase not allowed)
|
||||
When a directory **is** a component (i.e. contains `index.tsx` exporting the component, or groups files under one component name), use `PascalCase`.
|
||||
|
||||
```
|
||||
src/renderer/src/components/Sidebar/ ✅
|
||||
src/renderer/src/components/CodeEditor/ ✅
|
||||
src/renderer/src/components/MarkdownEditor/ ✅
|
||||
src/renderer/components/Sidebar/ ✅
|
||||
src/renderer/components/CodeEditor/ ✅
|
||||
src/renderer/components/MarkdownEditor/ ✅
|
||||
```
|
||||
|
||||
### 4.3 Bucket Directories — `lowercase plural noun`
|
||||
@@ -305,7 +305,7 @@ The `Service` suffix names a **role** (a stateful domain capability), not a **me
|
||||
| Lifecycle service | `@Injectable('XxxService')` + `extends BaseService`, accessed via `application.get('XxxService')` | The service owns long-lived resources OR registers persistent side effects |
|
||||
| Direct-import singleton service | `export const xxxService = new XxxService()` | No long-lived resources, no persistent side effects, but still has class-level state (e.g. cached SDK instances) |
|
||||
|
||||
The criteria for choosing between them are defined in [`docs/references/lifecycle/lifecycle-decision-guide.md`](../lifecycle/lifecycle-decision-guide.md).
|
||||
The criteria for choosing between them are defined in [`docs/references/lifecycle/lifecycle-decision-guide.md`](lifecycle/lifecycle-decision-guide.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -345,7 +345,7 @@ In `packages/*`, the directory name and `package.json#name` (after stripping sco
|
||||
|
||||
### 6.6 TanStack Router File-Based Routes
|
||||
|
||||
Files under `src/renderer/src/routes/` are **kebab-case** — TanStack Router maps filename directly to URL.
|
||||
Files under `src/renderer/routes/` are **kebab-case** — TanStack Router maps filename directly to URL.
|
||||
|
||||
Reserved tokens (TanStack-defined):
|
||||
|
||||
@@ -374,9 +374,9 @@ Any of these signals warrants a consolidation review.
|
||||
```
|
||||
Naming a new FILE
|
||||
├─ React component (.tsx)?
|
||||
│ ├─ Under src/renderer/src/routes/? → kebab-case.tsx (api-server.tsx)
|
||||
│ ├─ Under src/renderer/routes/? → kebab-case.tsx (api-server.tsx)
|
||||
│ ├─ Under packages/ui/? → kebab-case.tsx (button.tsx)
|
||||
│ └─ Under src/renderer/src/? → PascalCase.tsx (Sidebar.tsx)
|
||||
│ └─ Under src/renderer/? → PascalCase.tsx (Sidebar.tsx)
|
||||
├─ React hook? → useXxx.ts (useShortcuts.ts)
|
||||
├─ Primary export is a class? → PascalCase.ts (KnowledgeService.ts)
|
||||
├─ Primary export is function(s)? → camelCase.ts (markdownConverter.ts)
|
||||
|
||||
@@ -144,5 +144,5 @@ Behavioral injection goes through **`onWindowCreated`** (or its type-filtered co
|
||||
|
||||
### Renderer Integration
|
||||
|
||||
- `src/renderer/src/core/hooks/useWindowInitData.ts` — Canonical hook for init data consumption
|
||||
- `src/renderer/core/hooks/useWindowInitData.ts` — Canonical hook for init data consumption
|
||||
- `src/shared/IpcChannel.ts` — `WindowManager_*` IPC channel constants
|
||||
|
||||
@@ -226,7 +226,7 @@ openTopic(topicId: string): void {
|
||||
|
||||
## Renderer: `useWindowInitData` hook
|
||||
|
||||
`src/renderer/src/core/hooks/useWindowInitData.ts` provides the canonical way for any managed window to consume its init data across both creation paths:
|
||||
`src/renderer/core/hooks/useWindowInitData.ts` provides the canonical way for any managed window to consume its init data across both creation paths:
|
||||
|
||||
```typescript
|
||||
import { useWindowInitData } from '@renderer/core/hooks/useWindowInitData'
|
||||
|
||||
@@ -35,7 +35,7 @@ export default defineConfig({
|
||||
alias: {
|
||||
'@main': resolve('src/main'),
|
||||
'@application': resolve('src/main/core/application'),
|
||||
'@types': resolve('src/renderer/src/types'),
|
||||
'@types': resolve('src/renderer/types'),
|
||||
'@data': resolve('src/main/data'),
|
||||
'@shared': resolve('src/shared'),
|
||||
'@logger': resolve('src/main/core/logger/LoggerService'),
|
||||
@@ -101,8 +101,8 @@ export default defineConfig({
|
||||
tanstackRouter({
|
||||
target: 'react',
|
||||
autoCodeSplitting: true,
|
||||
routesDirectory: resolve('src/renderer/src/routes'),
|
||||
generatedRouteTree: resolve('src/renderer/src/routeTree.gen.ts')
|
||||
routesDirectory: resolve('src/renderer/routes'),
|
||||
generatedRouteTree: resolve('src/renderer/routeTree.gen.ts')
|
||||
}),
|
||||
(async () => (await import('@tailwindcss/vite')).default())(),
|
||||
react({
|
||||
@@ -113,11 +113,11 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
'@renderer': resolve('src/renderer'),
|
||||
'@shared': resolve('src/shared'),
|
||||
'@types': resolve('src/renderer/src/types'),
|
||||
'@logger': resolve('src/renderer/src/services/LoggerService'),
|
||||
'@data': resolve('src/renderer/src/data'),
|
||||
'@types': resolve('src/renderer/types'),
|
||||
'@logger': resolve('src/renderer/services/LoggerService'),
|
||||
'@data': resolve('src/renderer/data'),
|
||||
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
||||
'@mcp-trace/trace-web': resolve('packages/mcp-trace/trace-web'),
|
||||
'@cherrystudio/ai-core/provider': resolve('packages/aiCore/src/core/providers'),
|
||||
|
||||
@@ -125,8 +125,8 @@ export default defineConfig([
|
||||
'src/main/services/nutstore/sso/lib/**',
|
||||
'src/main/integration/cherryai/index.js',
|
||||
'src/main/services/nutstore/sso/lib/**',
|
||||
'src/renderer/src/ui/**',
|
||||
'src/renderer/src/routeTree.gen.ts',
|
||||
'src/renderer/ui/**',
|
||||
'src/renderer/routeTree.gen.ts',
|
||||
'packages/**/dist',
|
||||
'v2-refactor-temp/**'
|
||||
]
|
||||
@@ -316,11 +316,11 @@ export default defineConfig([
|
||||
},
|
||||
// renderer legacy css var migration warnings
|
||||
{
|
||||
files: ['src/renderer/src/**/*.{ts,tsx,js,jsx}'],
|
||||
files: ['src/renderer/**/*.{ts,tsx,js,jsx}'],
|
||||
ignores: [
|
||||
'src/renderer/src/**/*.test.*',
|
||||
'src/renderer/src/**/__tests__/**',
|
||||
'src/renderer/src/**/__mocks__/**'
|
||||
'src/renderer/**/*.test.*',
|
||||
'src/renderer/**/__tests__/**',
|
||||
'src/renderer/**/__mocks__/**'
|
||||
],
|
||||
plugins: {
|
||||
'renderer-styles': {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/draggable-list/index.tsx
|
||||
// Original path: src/renderer/components/draggable-list/index.tsx
|
||||
export { default as DraggableList } from './list'
|
||||
export { useDraggableReorder } from './use-draggable-reorder'
|
||||
export {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/draggable-list/list.tsx
|
||||
// Original path: src/renderer/components/draggable-list/list.tsx
|
||||
import type {
|
||||
DroppableProps,
|
||||
DropResult,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/draggable-list/useDraggableReorder.ts
|
||||
// Original path: src/renderer/components/draggable-list/useDraggableReorder.ts
|
||||
import type { DropResult } from '@hello-pangea/dnd'
|
||||
import type { Key } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/editable-number/index.tsx
|
||||
// Original path: src/renderer/components/editable-number/index.tsx
|
||||
import { cn } from '@cherrystudio/ui/lib/utils'
|
||||
import * as React from 'react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/ellipsis/index.tsx
|
||||
// Original: src/renderer/components/ellipsis/index.tsx
|
||||
import { cn } from '@cherrystudio/ui/lib/utils'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/horizontal-scroll-container/index.tsx
|
||||
// Original: src/renderer/components/horizontal-scroll-container/index.tsx
|
||||
import { cn } from '@cherrystudio/ui/lib/utils'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/Preview/ImageToolButton.tsx
|
||||
// Original path: src/renderer/components/Preview/ImageToolButton.tsx
|
||||
import { Button } from '@cherrystudio/ui/components/primitives/button'
|
||||
import { Tooltip } from '@cherrystudio/ui/components/primitives/tooltip'
|
||||
import { memo } from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/scrollbar/index.tsx
|
||||
// Original: src/renderer/components/scrollbar/index.tsx
|
||||
import { cn } from '@cherrystudio/ui/lib/utils'
|
||||
import { throttle } from 'lodash'
|
||||
import * as React from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/CopyButton.tsx
|
||||
// Original path: src/renderer/components/CopyButton.tsx
|
||||
import { Copy } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/Tags/CustomTag.tsx
|
||||
// Original path: src/renderer/components/Tags/CustomTag.tsx
|
||||
import { X } from 'lucide-react'
|
||||
import type { CSSProperties, FC, MouseEventHandler } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/DividerWithText.tsx
|
||||
// Original: src/renderer/components/DividerWithText.tsx
|
||||
import type { CSSProperties } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/EmojiIcon.tsx
|
||||
// Original path: src/renderer/components/EmojiIcon.tsx
|
||||
import type { FC } from 'react'
|
||||
|
||||
interface EmojiIconProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original path: src/renderer/src/components/ErrorBoundary.tsx
|
||||
// Original path: src/renderer/components/ErrorBoundary.tsx
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import type { ComponentType, ReactNode } from 'react'
|
||||
import type { FallbackProps } from 'react-error-boundary'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/IndicatorLight.tsx
|
||||
// Original: src/renderer/components/IndicatorLight.tsx
|
||||
import React from 'react'
|
||||
|
||||
interface IndicatorLightProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Original: src/renderer/src/components/Spinner.tsx
|
||||
// Original: src/renderer/components/Spinner.tsx
|
||||
import { motion } from 'framer-motion'
|
||||
import { Search } from 'lucide-react'
|
||||
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
* type UserWithName = RequireSome<User, 'name'>
|
||||
* // Result: { name: string; age?: number; }
|
||||
*/
|
||||
// The type is copied from src/renderer/src/types/index.ts.
|
||||
// The type is copied from src/renderer/types/index.ts.
|
||||
export type RequireSome<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
|
||||
|
||||
@@ -45,7 +45,7 @@ function findTemplateLiteral(project: Project, code: string): Node | undefined {
|
||||
vi.mock('fs')
|
||||
|
||||
describe('check-hardcoded-strings', () => {
|
||||
const mockSrcDir = '/mock/src/renderer/src'
|
||||
const mockSrcDir = '/mock/src/renderer'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('check-legacy-css-vars', () => {
|
||||
/* var(--color-text-2) */
|
||||
`
|
||||
|
||||
expect(findLegacyVarHitsInContent(content, 'src/renderer/src/example.css')).toEqual([])
|
||||
expect(findLegacyVarHitsInContent(content, 'src/renderer/example.css')).toEqual([])
|
||||
})
|
||||
|
||||
it('reports real legacy variable usages', () => {
|
||||
@@ -31,7 +31,7 @@ describe('check-legacy-css-vars', () => {
|
||||
const node = '<div class="text-[var(--color-text-2)]" />';
|
||||
`
|
||||
|
||||
const findings = findLegacyVarHitsInContent(content, 'src/renderer/src/example.tsx')
|
||||
const findings = findLegacyVarHitsInContent(content, 'src/renderer/example.tsx')
|
||||
|
||||
expect(findings).toHaveLength(2)
|
||||
expect(findings.map((finding) => finding.variable)).toEqual(['--color-text-1', '--color-text-2'])
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
describe('check-pr-style-reminders', () => {
|
||||
it('reports legacy vars only from added lines', () => {
|
||||
const diff = `
|
||||
diff --git a/src/renderer/src/example.tsx b/src/renderer/src/example.tsx
|
||||
diff --git a/src/renderer/example.tsx b/src/renderer/example.tsx
|
||||
index 1111111..2222222 100644
|
||||
--- a/src/renderer/src/example.tsx
|
||||
+++ b/src/renderer/src/example.tsx
|
||||
--- a/src/renderer/example.tsx
|
||||
+++ b/src/renderer/example.tsx
|
||||
@@ -10,0 +11,4 @@
|
||||
+const css = 'color: var(--color-text-1);'
|
||||
+// var(--color-text-2)
|
||||
@@ -20,17 +20,17 @@ index 1111111..2222222 100644
|
||||
-const removed = 'color: var(--color-text-3);'
|
||||
`
|
||||
|
||||
const findings = parseAddedLegacyVarFindingsFromDiff(diff, 'src/renderer/src/example.tsx')
|
||||
const findings = parseAddedLegacyVarFindingsFromDiff(diff, 'src/renderer/example.tsx')
|
||||
|
||||
expect(findings).toEqual([
|
||||
{
|
||||
file: 'src/renderer/src/example.tsx',
|
||||
file: 'src/renderer/example.tsx',
|
||||
line: 11,
|
||||
variable: '--color-text-1',
|
||||
lineText: "const css = 'color: var(--color-text-1);'"
|
||||
},
|
||||
{
|
||||
file: 'src/renderer/src/example.tsx',
|
||||
file: 'src/renderer/example.tsx',
|
||||
line: 13,
|
||||
variable: '--color-background-soft',
|
||||
lineText: "const next = 'background: var(--color-background-soft);'"
|
||||
@@ -40,10 +40,10 @@ index 1111111..2222222 100644
|
||||
|
||||
it('tracks added line numbers from a unified diff', () => {
|
||||
const diff = `
|
||||
diff --git a/src/renderer/src/example.tsx b/src/renderer/src/example.tsx
|
||||
diff --git a/src/renderer/example.tsx b/src/renderer/example.tsx
|
||||
index 1111111..2222222 100644
|
||||
--- a/src/renderer/src/example.tsx
|
||||
+++ b/src/renderer/src/example.tsx
|
||||
--- a/src/renderer/example.tsx
|
||||
+++ b/src/renderer/example.tsx
|
||||
@@ -10,2 +10,4 @@
|
||||
const old = true
|
||||
+const added = 'w-[420px]'
|
||||
@@ -59,7 +59,7 @@ index 1111111..2222222 100644
|
||||
const body = buildPullRequestStyleRemindersComment(
|
||||
[
|
||||
{
|
||||
file: 'src/renderer/src/example.tsx',
|
||||
file: 'src/renderer/example.tsx',
|
||||
line: 11,
|
||||
variable: '--color-text-1',
|
||||
lineText: "const css = 'color: var(--color-text-1);'"
|
||||
@@ -67,7 +67,7 @@ index 1111111..2222222 100644
|
||||
],
|
||||
[
|
||||
{
|
||||
file: 'src/renderer/src/example.tsx',
|
||||
file: 'src/renderer/example.tsx',
|
||||
line: 12,
|
||||
original: 'w-[420px]',
|
||||
canonical: 'w-105',
|
||||
|
||||
@@ -260,11 +260,11 @@ const countTranslatableStrings = (obj: I18N): number =>
|
||||
const main = async () => {
|
||||
validateConfig()
|
||||
|
||||
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
const translateDir = path.join(__dirname, '../src/renderer/src/i18n/translate')
|
||||
const localesDir = path.join(__dirname, '../src/renderer/i18n/locales')
|
||||
const translateDir = path.join(__dirname, '../src/renderer/i18n/translate')
|
||||
const baseLocale = process.env.TRANSLATION_BASE_LOCALE ?? 'en-us'
|
||||
const baseFileName = `${baseLocale}.json`
|
||||
const baseLocalePath = path.join(__dirname, '../src/renderer/src/i18n/locales', baseFileName)
|
||||
const baseLocalePath = path.join(__dirname, '../src/renderer/i18n/locales', baseFileName)
|
||||
if (!fs.existsSync(baseLocalePath)) {
|
||||
throw new Error(`${baseLocalePath} not found.`)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as path from 'path'
|
||||
import type { SourceFile } from 'ts-morph'
|
||||
import { Node, Project } from 'ts-morph'
|
||||
|
||||
const RENDERER_DIR = path.join(__dirname, '../src/renderer/src')
|
||||
const RENDERER_DIR = path.join(__dirname, '../src/renderer')
|
||||
const MAIN_DIR = path.join(__dirname, '../src/main')
|
||||
const EXTENSIONS = ['.tsx', '.ts']
|
||||
const IGNORED_DIRS = ['__tests__', 'node_modules', 'i18n', 'locales', 'types', 'assets']
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from 'path'
|
||||
|
||||
import { sortedObjectByKeys } from './sort'
|
||||
|
||||
const translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
const translationsDir = path.join(__dirname, '../src/renderer/i18n/locales')
|
||||
const baseLocale = process.env.BASE_LOCALE ?? 'zh-cn'
|
||||
const baseFileName = `${baseLocale}.json`
|
||||
const baseFilePath = path.join(translationsDir, baseFileName)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const RENDERER_DIR = path.join(__dirname, '../src/renderer/src')
|
||||
const RENDERER_DIR = path.join(__dirname, '../src/renderer')
|
||||
const CHECK_EXTENSIONS = new Set(['.css', '.ts', '.tsx'])
|
||||
const IGNORED_DIR_NAMES = new Set(['node_modules', 'dist', 'out'])
|
||||
const IGNORED_FILE_PATTERNS = [/\.test\.(ts|tsx)$/, /\.spec\.(ts|tsx)$/, /\.snap$/]
|
||||
|
||||
@@ -71,19 +71,19 @@ function parseArgs(): { baseRef: string; headRef: string; markdownOutput?: strin
|
||||
}
|
||||
|
||||
function isTrackedRendererFile(filePath: string): boolean {
|
||||
if (!filePath.startsWith('src/renderer/src/')) return false
|
||||
if (!filePath.startsWith('src/renderer/')) return false
|
||||
if (!LEGACY_CHECK_EXTENSIONS.has(path.extname(filePath))) return false
|
||||
return !shouldIgnoreFile(path.join(REPO_ROOT, filePath))
|
||||
}
|
||||
|
||||
function isCanonicalClassCheckFile(filePath: string): boolean {
|
||||
if (!filePath.startsWith('src/renderer/src/')) return false
|
||||
if (!filePath.startsWith('src/renderer/')) return false
|
||||
if (!CANONICAL_CLASS_CHECK_EXTENSIONS.has(path.extname(filePath))) return false
|
||||
return !shouldIgnoreFile(path.join(REPO_ROOT, filePath))
|
||||
}
|
||||
|
||||
function getChangedRendererFiles(baseRef: string, headRef: string): string[] {
|
||||
const output = runGit(['diff', '--name-only', '--diff-filter=ACMR', baseRef, headRef, '--', 'src/renderer/src'])
|
||||
const output = runGit(['diff', '--name-only', '--diff-filter=ACMR', baseRef, headRef, '--', 'src/renderer'])
|
||||
|
||||
return output
|
||||
.split(/\r?\n/)
|
||||
@@ -93,7 +93,7 @@ function getChangedRendererFiles(baseRef: string, headRef: string): string[] {
|
||||
}
|
||||
|
||||
function getChangedCanonicalClassFiles(baseRef: string, headRef: string): string[] {
|
||||
const output = runGit(['diff', '--name-only', '--diff-filter=ACMR', baseRef, headRef, '--', 'src/renderer/src'])
|
||||
const output = runGit(['diff', '--name-only', '--diff-filter=ACMR', baseRef, headRef, '--', 'src/renderer'])
|
||||
|
||||
return output
|
||||
.split(/\r?\n/)
|
||||
|
||||
@@ -278,7 +278,7 @@ async function resolveStylesheet(id: string, base: string, cwd: string): Promise
|
||||
}
|
||||
|
||||
export async function loadTailwindDesignSystem(cwd = process.cwd()): Promise<DesignSystem> {
|
||||
const entryPath = path.join(cwd, 'src/renderer/src/assets/styles/tailwind.css')
|
||||
const entryPath = path.join(cwd, 'src/renderer/assets/styles/tailwind.css')
|
||||
const css = await fs.readFile(entryPath, 'utf8')
|
||||
|
||||
return __unstable__loadDesignSystem(css, {
|
||||
|
||||
@@ -3,8 +3,8 @@ import * as path from 'path'
|
||||
|
||||
import { sortedObjectByKeys } from './sort'
|
||||
|
||||
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
const translateDir = path.join(__dirname, '../src/renderer/src/i18n/translate')
|
||||
const localesDir = path.join(__dirname, '../src/renderer/i18n/locales')
|
||||
const translateDir = path.join(__dirname, '../src/renderer/i18n/translate')
|
||||
const baseLocale = process.env.TRANSLATION_BASE_LOCALE ?? 'en-us'
|
||||
const baseFileName = `${baseLocale}.json`
|
||||
const baseFilePath = path.join(localesDir, baseFileName)
|
||||
|
||||
@@ -23,7 +23,7 @@ const INDEX = [
|
||||
{ name: 'Greek', code: 'el-gr', model: MODEL }
|
||||
]
|
||||
|
||||
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as I18N
|
||||
const zh = JSON.parse(fs.readFileSync('src/renderer/i18n/locales/zh-cn.json', 'utf8')) as I18N
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: API_KEY,
|
||||
@@ -134,11 +134,11 @@ void (async () => {
|
||||
const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic)
|
||||
bar.start(INDEX.length, 0)
|
||||
for (const { name, code, model } of INDEX) {
|
||||
const obj = fs.existsSync(`src/renderer/src/i18n/translate/${code}.json`)
|
||||
? (JSON.parse(fs.readFileSync(`src/renderer/src/i18n/translate/${code}.json`, 'utf8')) as I18N)
|
||||
const obj = fs.existsSync(`src/renderer/i18n/translate/${code}.json`)
|
||||
? (JSON.parse(fs.readFileSync(`src/renderer/i18n/translate/${code}.json`, 'utf8')) as I18N)
|
||||
: {}
|
||||
await translate(zh, obj, name, model, () => {
|
||||
fs.writeFileSync(`src/renderer/src/i18n/translate/${code}.json`, JSON.stringify(obj, null, 2), 'utf8')
|
||||
fs.writeFileSync(`src/renderer/i18n/translate/${code}.json`, JSON.stringify(obj, null, 2), 'utf8')
|
||||
})
|
||||
count += 1
|
||||
bar.update(count)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { formatApiHost, withoutTrailingSlash } from '@shared/utils'
|
||||
import { trim } from 'lodash'
|
||||
|
||||
// NOTE: Since #13194, it's re-written with reduxService
|
||||
// See: renderer/src/utils/api.ts: formatVertexApiHost
|
||||
// See: renderer/utils/api.ts: formatVertexApiHost
|
||||
export async function formatVertexApiHost(host: string): Promise<string> {
|
||||
const { projectId: project, location } = await reduxService.select('llm.settings.vertexai')
|
||||
const trimmedHost = withoutTrailingSlash(trim(host))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
import type { ApiModel, ApiModelsFilter, ApiModelsResponse } from '../../../renderer/src/types/apiModels'
|
||||
import type { ApiModel, ApiModelsFilter, ApiModelsResponse } from '../../../renderer/types/apiModels'
|
||||
import {
|
||||
getAvailableProviders,
|
||||
getProviderAnthropicModelChecker,
|
||||
|
||||
@@ -65,7 +65,7 @@ export class MiniAppMigrator extends BaseMigrator {
|
||||
this.originalSourceCount = groups.reduce((total, group) => total + group.data.length, 0)
|
||||
|
||||
// v1 strips `logo` to undefined before persisting custom apps to Redux state
|
||||
// (see v1 src/renderer/src/store/minapps.ts reducers). The full custom-app
|
||||
// (see v1 src/renderer/store/minapps.ts reducers). The full custom-app
|
||||
// record — including logo — lives in `customMiniAppsFile` (resolved by
|
||||
// MigrationPaths from {userData}/Data/Files/custom-minapps.json) and is
|
||||
// reattached at runtime. Re-read it here so logos survive migration.
|
||||
|
||||
@@ -31,7 +31,7 @@ import { legacyModelToUniqueId } from '../transformers/ModelTransformers'
|
||||
|
||||
/**
|
||||
* Old Model type from Redux state
|
||||
* Source: src/renderer/src/types/index.ts
|
||||
* Source: src/renderer/types/index.ts
|
||||
*/
|
||||
/**
|
||||
* Legacy data may have incomplete model objects (e.g. missing provider or group).
|
||||
@@ -46,7 +46,7 @@ export interface OldModel {
|
||||
|
||||
/**
|
||||
* Old AssistantSettings from Redux state
|
||||
* Source: src/renderer/src/types/index.ts
|
||||
* Source: src/renderer/types/index.ts
|
||||
*/
|
||||
export interface OldAssistantSettings {
|
||||
maxTokens?: number
|
||||
@@ -84,7 +84,7 @@ export interface OldMcpServer {
|
||||
|
||||
/**
|
||||
* Old Assistant type from Redux state.
|
||||
* Source: src/renderer/src/types/index.ts
|
||||
* Source: src/renderer/types/index.ts
|
||||
*
|
||||
* Fields use nullable unions (`| null`) because legacy Redux data
|
||||
* may store explicit nulls. All fields except `id` are optional
|
||||
|
||||
@@ -69,7 +69,7 @@ import { legacyModelToUniqueId } from '../transformers/ModelTransformers'
|
||||
|
||||
/**
|
||||
* Old Topic type from Redux assistants slice
|
||||
* Source: src/renderer/src/types/index.ts
|
||||
* Source: src/renderer/types/index.ts
|
||||
*/
|
||||
export interface OldTopic {
|
||||
id: string
|
||||
@@ -125,7 +125,7 @@ export interface OldModel {
|
||||
|
||||
/**
|
||||
* Old Message type from Dexie topics table
|
||||
* Source: src/renderer/src/types/newMessage.ts
|
||||
* Source: src/renderer/types/newMessage.ts
|
||||
*/
|
||||
export interface OldMessage {
|
||||
id: string
|
||||
|
||||
@@ -49,7 +49,7 @@ export function transformMiniApp(
|
||||
): Omit<MiniAppInsert, 'orderKey'> {
|
||||
const appId = toRequired<string>(source.id, '')
|
||||
// v1 stamps `type: 'Custom'` on apps loaded from custom-minapps.json
|
||||
// (see v1 src/renderer/src/config/minapps.ts:loadCustomMiniApp). Preset rows
|
||||
// (see v1 src/renderer/config/minapps.ts:loadCustomMiniApp). Preset rows
|
||||
// in v1 leave `type` unset. Honor that explicit signal first so a user-created
|
||||
// app whose id collides with a v2-only preset isn't misclassified as preset.
|
||||
const isExplicitCustom = source.type === 'Custom'
|
||||
|
||||
@@ -416,7 +416,7 @@ export async function registerIpc() {
|
||||
// (read/write/delete/move on <userData>/Data/Files/<v1uuid>.<ext>, plus
|
||||
// notes/watcher/directory utilities) backed by the FileStorage singleton.
|
||||
// The Dexie `db.files` metadata + refcount layer lives entirely in the
|
||||
// renderer (`src/renderer/src/services/FileManager.ts`); main never
|
||||
// renderer (`src/renderer/services/FileManager.ts`); main never
|
||||
// touches IndexedDB. Both sides stay live until Batch A-E migrates the
|
||||
// renderer callers to the v2 surface (createInternalEntry /
|
||||
// ensureExternalEntry / getPhysicalPath / permanentDelete / runSweep)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* The former `ipcMain.handle(IpcChannel.ReduxStoreReady, ...)` handshake has
|
||||
* been removed on both sides: the stub does not read from the renderer, so
|
||||
* there is no readiness state to track, and the renderer's invoke of the
|
||||
* channel (renderer/src/store/index.ts) has been commented out with the
|
||||
* channel (renderer/store/index.ts) has been commented out with the
|
||||
* existing `// [v2] Removed:` convention.
|
||||
*
|
||||
* Migrate each caller to the v2 data layer (Preference / DataApi / direct
|
||||
|
||||
@@ -3,19 +3,19 @@ import { defaultLanguage } from '@shared/config/constant'
|
||||
import type { LanguageVarious } from '@shared/data/preference/preferenceTypes'
|
||||
import { app } from 'electron'
|
||||
|
||||
import EnUs from '../../renderer/src/i18n/locales/en-us.json'
|
||||
import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
|
||||
import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
|
||||
import EnUs from '../../renderer/i18n/locales/en-us.json'
|
||||
import ZhCn from '../../renderer/i18n/locales/zh-cn.json'
|
||||
import ZhTw from '../../renderer/i18n/locales/zh-tw.json'
|
||||
// Machine translation
|
||||
import deDE from '../../renderer/src/i18n/translate/de-de.json'
|
||||
import elGR from '../../renderer/src/i18n/translate/el-gr.json'
|
||||
import esES from '../../renderer/src/i18n/translate/es-es.json'
|
||||
import frFR from '../../renderer/src/i18n/translate/fr-fr.json'
|
||||
import JaJP from '../../renderer/src/i18n/translate/ja-jp.json'
|
||||
import ptPT from '../../renderer/src/i18n/translate/pt-pt.json'
|
||||
import roRO from '../../renderer/src/i18n/translate/ro-ro.json'
|
||||
import RuRu from '../../renderer/src/i18n/translate/ru-ru.json'
|
||||
import viVN from '../../renderer/src/i18n/translate/vi-vn.json'
|
||||
import deDE from '../../renderer/i18n/translate/de-de.json'
|
||||
import elGR from '../../renderer/i18n/translate/el-gr.json'
|
||||
import esES from '../../renderer/i18n/translate/es-es.json'
|
||||
import frFR from '../../renderer/i18n/translate/fr-fr.json'
|
||||
import JaJP from '../../renderer/i18n/translate/ja-jp.json'
|
||||
import ptPT from '../../renderer/i18n/translate/pt-pt.json'
|
||||
import roRO from '../../renderer/i18n/translate/ro-ro.json'
|
||||
import RuRu from '../../renderer/i18n/translate/ru-ru.json'
|
||||
import viVN from '../../renderer/i18n/translate/vi-vn.json'
|
||||
|
||||
export const locales = Object.fromEntries(
|
||||
[
|
||||
|
||||
@@ -89,7 +89,7 @@ import type {
|
||||
SkillInstallOptions,
|
||||
SkillResult,
|
||||
SkillToggleOptions
|
||||
} from '../renderer/src/types/skill'
|
||||
} from '../renderer/types/skill'
|
||||
|
||||
// OpenClaw types
|
||||
type OpenClawGatewayStatus = 'stopped' | 'starting' | 'running' | 'error'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
- **职责**:作为特定 AI Provider SDK 的纯粹适配层。
|
||||
- **参数适配**:将应用内部统一的 `CoreRequest` 对象 (见下文) 转换为特定 SDK 所需的请求参数格式。
|
||||
- **基础响应转换**:将 SDK 返回的原始数据块 (`RawSdkChunk`,例如 `OpenAI.Chat.Completions.ChatCompletionChunk`) 转换为一组最基础、最直接的应用层 `Chunk` 对象 (定义于 `src/renderer/src/types/chunk.ts`)。
|
||||
- **基础响应转换**:将 SDK 返回的原始数据块 (`RawSdkChunk`,例如 `OpenAI.Chat.Completions.ChatCompletionChunk`) 转换为一组最基础、最直接的应用层 `Chunk` 对象 (定义于 `src/renderer/types/chunk.ts`)。
|
||||
- 例如:SDK 的 `delta.content` -> `TextDeltaChunk`;SDK 的 `delta.reasoning_content` -> `ThinkingDeltaChunk`;SDK 的 `delta.tool_calls` -> `RawToolCallChunk` (包含原始工具调用数据)。
|
||||
- **关键**:`XxxApiClient` **不处理**耦合在文本内容中的复杂结构,如 `<think>` 或 `<tool_use>` 标签。
|
||||
- **特点**:极度轻量化,代码量少,易于实现和维护新的 Provider 适配。
|
||||
@@ -155,7 +155,7 @@
|
||||
## 4. 建议的文件/目录结构
|
||||
|
||||
```
|
||||
src/renderer/src/
|
||||
src/renderer/
|
||||
└── aiCore/
|
||||
├── clients/
|
||||
│ ├── openai/
|
||||
@@ -207,7 +207,7 @@ src/renderer/src/
|
||||
|
||||
1. **定义核心数据结构 (TypeScript 类型):**
|
||||
- `CoreCompletionsRequest` (Type):定义应用内部统一的对话请求结构。
|
||||
- `Chunk` (Type - 检查并按需扩展现有 `src/renderer/src/types/chunk.ts`):定义所有可能的通用Chunk类型。
|
||||
- `Chunk` (Type - 检查并按需扩展现有 `src/renderer/types/chunk.ts`):定义所有可能的通用Chunk类型。
|
||||
- 为其他API(翻译、总结)定义类似的 `CoreXxxRequest` (Type)。
|
||||
2. **定义 `ApiClient` 接口:** 明确 `getRequestTransformer`, `getResponseChunkTransformer`, `getSdkInstance` 等核心方法。
|
||||
3. **调整 `AiProviderMiddlewareContext`:**
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user