134 lines
5.0 KiB
Markdown
134 lines
5.0 KiB
Markdown
# Zhiju AI Assistant — Developer Notes
|
|
|
|
## Project Overview
|
|
|
|
Electron desktop app (Electron Forge + Vite + Vue 3) that wraps a locally-spawned `opencode` binary (located in `resources/`). The renderer is a Vue 3 SPA; the main process manages the `opencode` child process and exposes IPC to the renderer via a preload script.
|
|
|
|
---
|
|
|
|
## Build & Configuration
|
|
|
|
### Prerequisites
|
|
- Node.js ≥ 18
|
|
- The `opencode` binary must exist at `resources/windows/x64/opencode.exe` (Windows) before packaging. It is **not** committed to the repo — obtain it separately.
|
|
|
|
### Install dependencies
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
### Run in development
|
|
```bash
|
|
npm start
|
|
```
|
|
This uses `electron-forge start`, which runs Vite for the renderer and launches Electron. Hot-reload is active for the renderer; changes to `src/main/index.js` or `src/preload/index.js` require a manual restart.
|
|
|
|
### Package / distribute
|
|
```bash
|
|
npm run make # builds installers for the current platform
|
|
npm run package # produces an unpackaged app directory
|
|
```
|
|
Packaged output lands in `out/`. The `opencode.exe` binary is bundled via `extraResource` in `forge.config.js` and unpacked from ASAR at runtime.
|
|
|
|
### Vite configs
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `vite.main.config.mjs` | Main process bundle |
|
|
| `vite.preload.config.mjs` | Preload script bundle |
|
|
| `vite.renderer.config.mjs` | Renderer (Vue SPA), alias `@` → `src/renderer` |
|
|
|
|
---
|
|
|
|
## Architecture Notes
|
|
|
|
- **Main process** (`src/main/index.js`): spawns `opencode` binary, resolves a free port starting at `4096`, waits for TCP readiness, then injects `window.__opencodeBaseUrl` into the renderer via `executeJavaScript`. Also runs Bonjour service discovery.
|
|
- **Preload** (`src/preload/index.js`): bridges IPC between main and renderer using `contextBridge`.
|
|
- **Renderer** (`src/renderer/`): Vue 3 + Pinia + Vue Router. HTTP calls go through `src/renderer/http/` (axios-based). SSE streaming is handled in `src/renderer/http/sse.js`.
|
|
- **URL constants** (`src/renderer/http/url.js`): single source of truth for all API endpoint paths. `getBaseUrl()` reads `window.__opencodeBaseUrl` (injected by main) with fallback to `http://127.0.0.1:4096`.
|
|
- **Crypto** (`src/renderer/utils/crypto.js`): passwords are Base64-encoded then RSA-encrypted (public key hardcoded) before being sent to the login API.
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Framework
|
|
[Vitest](https://vitest.dev/) — chosen for native Vite/ESM compatibility, zero extra config needed.
|
|
|
|
### Run all tests
|
|
```bash
|
|
npm test # vitest run (single pass, CI-friendly)
|
|
npm run test:watch # vitest watch mode (development)
|
|
```
|
|
|
|
Or directly:
|
|
```bash
|
|
npx vitest run
|
|
```
|
|
|
|
### Writing tests
|
|
- Place test files alongside the source file as `*.test.js` (Vitest picks them up automatically).
|
|
- For pure utility/logic modules (e.g. `url.js`, `crypto.js`) no additional setup is needed.
|
|
- Modules that depend on `window`, `electron`, or Pinia require mocking. Use `vi.stubGlobal` for `window` properties and `vi.mock` for module mocks.
|
|
|
|
### Example: testing `url.js`
|
|
```js
|
|
// src/renderer/http/url.test.js
|
|
import { describe, it, expect } from 'vitest';
|
|
import url from './url.js';
|
|
|
|
describe('url constants', () => {
|
|
it('session.detail returns correct path', () => {
|
|
expect(url.session.detail('abc123')).toBe('/session/abc123');
|
|
});
|
|
|
|
it('message.send returns correct path', () => {
|
|
expect(url.message.send('sess1')).toBe('/session/sess1/message');
|
|
});
|
|
});
|
|
```
|
|
|
|
Run with:
|
|
```bash
|
|
npx vitest run src/renderer/http/url.test.js
|
|
```
|
|
|
|
### Testing modules that use `window.__opencodeBaseUrl`
|
|
```js
|
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal('__opencodeBaseUrl', 'http://127.0.0.1:5000');
|
|
});
|
|
|
|
// import and test getBaseUrl() etc.
|
|
```
|
|
|
|
---
|
|
|
|
## Code Style
|
|
|
|
- **Formatter**: Prettier (config in `package.json` defaults). Run `npm run format` to auto-fix, `npm run format:check` for CI check.
|
|
- **Commit messages**: enforced by commitlint (`@commitlint/config-conventional`). Use conventional commits: `feat:`, `fix:`, `chore:`, etc.
|
|
- **Pre-commit hook**: husky + lint-staged runs Prettier on staged `*.js` and `*.vue` files automatically.
|
|
- **No ESLint** is configured — only Prettier for formatting.
|
|
- Vue components use the Composition API (`<script setup>` style in newer components, options-style `setup()` returning refs in stores).
|
|
- Pinia stores use the Setup Store pattern (function returning refs/actions), not the Options Store pattern.
|
|
- Chinese comments are common throughout the codebase — maintain them in Chinese when editing existing files.
|
|
|
|
---
|
|
|
|
## Key Dependencies
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `electron` v41 | Desktop shell |
|
|
| `electron-forge` v7 | Build/package toolchain |
|
|
| `vite` v5 + `@vitejs/plugin-vue` | Renderer bundler |
|
|
| `vue` v3 + `pinia` + `vue-router` | UI framework |
|
|
| `axios` | HTTP client (renderer) |
|
|
| `element-plus` | UI component library |
|
|
| `bonjour-service` | LAN service discovery |
|
|
| `jsencrypt` + `js-base64` | Password encryption before login |
|
|
| `unified` / `remark` / `rehype` | Markdown rendering pipeline |
|
|
| `katex` | Math formula rendering in chat |
|