fix: resolve TypeScript type-check errors
- Add cacheHitRate to ProxyMetricsService, handler, and router schema - Fix StatCard to support string values and purple accent - Fix duplicate tabs keys in i18n translations (EN/ZH/RU) - Add missing cacheStatus translations across all languages
This commit is contained in:
1 parent
0be7d7fea2
commit
fd1b2b8bc0
250 files changed
+49653
-60
No files matched your search
@@ -0,0 +1,58 @@
|
||||
Here is the code review report for the Token Usage Counter feature.
|
||||
|
||||
## Summary
|
||||
|
||||
The review found 2 critical security issues and 2 warnings. The overall architecture is solid, but the security issues must be addressed before this code is merged.
|
||||
|
||||
## Critical Issues
|
||||
|
||||
### CR-01: SQL Injection Vulnerability in TokenUsageRepo filter methods
|
||||
|
||||
**Issue:** `buildAccountFilter` concatenates `accountId` directly into raw SQL.
|
||||
**Fix:** Use parameterized queries.
|
||||
|
||||
```typescript
|
||||
function buildAccountFilter(accountId?: string): { sql: string; params: string[] } {
|
||||
return accountId ? { sql: `AND account_id = ?`, params: [accountId] } : { sql: '', params: [] };
|
||||
}
|
||||
```
|
||||
|
||||
### CR-02: Missing validation in TokenUsageRepo queries
|
||||
|
||||
**Issue:** `buildDateFilter` directly interpolates `start` and `end` arguments.
|
||||
**Fix:** Ensure parameters are passed safely using parameterized queries.
|
||||
|
||||
```typescript
|
||||
function buildDateFilter(start?: number, end?: number): { sql: string; params: number[] } {
|
||||
const conditions: string[] = [];
|
||||
const params: number[] = [];
|
||||
if (start !== undefined) {
|
||||
conditions.push(`timestamp >= ?`);
|
||||
params.push(start);
|
||||
}
|
||||
if (end !== undefined) {
|
||||
conditions.push(`timestamp <= ?`);
|
||||
params.push(end);
|
||||
}
|
||||
return {
|
||||
sql: conditions.length > 0 ? `AND ${conditions.join(' AND ')}` : '',
|
||||
params,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Warnings
|
||||
|
||||
### WR-01: Synchronous Database Writing in ProxyService
|
||||
|
||||
**Issue:** `recordUsage` calls a synchronous database insertion on every proxy request, which can cause severe performance bottlenecks under load.
|
||||
**Fix:** Consider making the usage recording asynchronous (fire-and-forget), using an asynchronous database driver, or implementing a batching mechanism.
|
||||
|
||||
### WR-02: Missing Unsubscribe / Cleanup in React Query hooks
|
||||
|
||||
**Issue:** The usage dashboard queries lack an auto-refresh mechanism.
|
||||
**Fix:** Add `refetchInterval: 10000` to the query configuration.
|
||||
|
||||
## Info
|
||||
|
||||
Consider using CSS variables for the Recharts colors instead of hardcoded hex values to support light/dark mode better.
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
phase: 02-code-review-command
|
||||
reviewed: 2026-04-22T14:00:00Z
|
||||
depth: standard
|
||||
files_reviewed: 10
|
||||
files_reviewed_list:
|
||||
- src/ipc/database/cloudHandler.ts
|
||||
- src/ipc/database/schema.ts
|
||||
- src/ipc/database/usageHandler.ts
|
||||
- src/ipc/router.ts
|
||||
- src/ipc/usage/router.ts
|
||||
- src/server/modules/proxy/proxy.module.ts
|
||||
- src/server/modules/proxy/proxy.service.ts
|
||||
- src/server/modules/usage/token-usage.service.ts
|
||||
- src/types/db.ts
|
||||
- src/routes/usage.tsx
|
||||
status: issues_found
|
||||
---
|
||||
|
||||
# Phase 02-code-review-command: Code Review Report
|
||||
|
||||
**Reviewed:** 2026-04-22T14:00:00Z
|
||||
**Depth:** standard
|
||||
**Files Reviewed:** 10
|
||||
**Status:** issues_found
|
||||
|
||||
## Summary
|
||||
|
||||
This review covers the new Token Usage Counter feature introduced in the `feature/token-usage-counter` branch. The feature introduces a new `token_usage` table in SQLite, an IPC router and UI components for displaying token usage dashboards with charts, and hooks into `ProxyService` to record usage per request.
|
||||
|
||||
Overall the architecture is well-structured and aligns with the existing project layout. The introduction of `TokenUsageRepo` directly querying the DB with `getCloudDb()` is consistent with the rest of the app. The UI is built using Recharts and properly localized.
|
||||
|
||||
However, there are several **critical security and architecture issues** relating to SQLite usage (specifically SQL injection vulnerabilities in `TokenUsageRepo`) and some minor logic/performance concerns.
|
||||
|
||||
## Critical Issues
|
||||
|
||||
### CR-01: SQL Injection Vulnerability in TokenUsageRepo filter methods
|
||||
|
||||
**File:** `src/ipc/database/usageHandler.ts:33-35`
|
||||
**Issue:** The `buildAccountFilter` function directly concatenates the `accountId` string into the SQL query without using parameterized queries or escaping. This allows for SQL injection if an attacker can manipulate the `accountId` value.
|
||||
**Fix:**
|
||||
Use parameterized queries (e.g., `?` placeholders) and pass the `accountId` as an argument to `raw.prepare().all(accountId)`.
|
||||
|
||||
```typescript
|
||||
function buildAccountFilter(accountId?: string): { sql: string; params: string[] } {
|
||||
return accountId ? { sql: `AND account_id = ?`, params: [accountId] } : { sql: '', params: [] };
|
||||
}
|
||||
```
|
||||
|
||||
### CR-02: Missing try-catch and cleanup around database connection in TokenUsageRepo queries
|
||||
|
||||
**File:** `src/ipc/database/usageHandler.ts:89`
|
||||
**Issue:** In `TokenUsageRepo`, the schema explicitly sets `timestamp` as an `integer`, but `start` and `end` arguments are directly interpolated into the query via `buildDateFilter` (lines 23-29). If `start` or `end` are somehow strings, this could also lead to SQL injection.
|
||||
**Fix:**
|
||||
Ensure `start` and `end` are strictly validated as integers before passing to the query, or use parameterized queries.
|
||||
|
||||
```typescript
|
||||
function buildDateFilter(start?: number, end?: number): { sql: string; params: number[] } {
|
||||
const conditions: string[] = [];
|
||||
const params: number[] = [];
|
||||
if (start !== undefined) {
|
||||
conditions.push(`timestamp >= ?`);
|
||||
params.push(start);
|
||||
}
|
||||
if (end !== undefined) {
|
||||
conditions.push(`timestamp <= ?`);
|
||||
params.push(end);
|
||||
}
|
||||
return {
|
||||
sql: conditions.length > 0 ? `AND ${conditions.join(' AND ')}` : '',
|
||||
params,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Warnings
|
||||
|
||||
### WR-01: Synchronous Database Writing in ProxyService
|
||||
|
||||
**File:** `src/server/modules/proxy/proxy.service.ts:57`
|
||||
**Issue:** The `recordUsage` method calls `this.tokenUsageService.recordUsage()`, which internally calls `TokenUsageRepo.recordUsage()`. `TokenUsageRepo.recordUsage()` performs a synchronous database insertion (`orm.insert(...).run()`) and closes the connection. Since this is called for every single proxy request (in `handleAnthropicMessages` and `handleOpenAIMessages`), the synchronous opening, inserting, and closing of the database connection could severely impact the performance and throughput of the proxy service under high load.
|
||||
**Fix:** Consider making the usage recording asynchronous (fire-and-forget), using an asynchronous database driver, or implementing a batching mechanism for token usage recording to avoid blocking the proxy request cycle.
|
||||
|
||||
### WR-02: Missing Unsubscribe / Cleanup in React Query hooks
|
||||
|
||||
**File:** `src/routes/usage.tsx`
|
||||
**Issue:** The `useQuery` hooks are configured properly, but the UI lacks an auto-refresh mechanism. Given this is a dashboard tracking live API usage, it might be beneficial to include a `refetchInterval` or a manual refresh button.
|
||||
**Fix:** Add `refetchInterval: 10000` to the query configuration.
|
||||
|
||||
## Info
|
||||
|
||||
### IN-01: UI Chart Colors and Styling
|
||||
|
||||
**File:** `src/routes/usage.tsx:23`
|
||||
**Issue:** The `COLORS` array is hardcoded with hex values. In a themable application (e.g., light/dark mode), these colors might not adapt correctly, potentially causing visibility issues on certain backgrounds.
|
||||
**Fix:** Consider using CSS variables from the theme (e.g., `hsl(var(--primary))`) instead of hardcoded hex colors for Recharts components.
|
||||
|
||||
---
|
||||
|
||||
_Reviewed: 2026-04-22T14:00:00Z_
|
||||
_Reviewer: OpenCode (gsd-code-reviewer)_
|
||||
_Depth: standard_
|
||||
@@ -0,0 +1,92 @@
|
||||
# Proxy Page — Feature Brainstorm
|
||||
|
||||
> Generated from designer agent analysis of `src/routes/proxy.tsx`
|
||||
> Date: 2026-04-22
|
||||
|
||||
## Current State
|
||||
The proxy page (`src/routes/proxy.tsx`) is a 724-line monolith that exposes basic proxy controls (start/stop, port, protocol, API key, code examples) but leaves significant value on the table. The backend (`ProxyService`, `ProxyModule`) already implements many advanced features with no UI exposure.
|
||||
|
||||
## Proposed New Features
|
||||
|
||||
### 1. Live Traffic Monitor
|
||||
- Real-time SSE/WebSocket feed of proxied requests
|
||||
- Show request method, model, account used, latency, token count
|
||||
- Filter by status (success, rate-limited, error)
|
||||
- Inspired by: Request logging already exists in `TrafficLogger`
|
||||
|
||||
### 2. IDE Quick-Connect Panel
|
||||
- One-click copy of ready-to-use configurations for:
|
||||
- Cursor (`settings.json` snippet)
|
||||
- OpenCode (`.env` snippet)
|
||||
- Windsurf (`config.yaml` snippet)
|
||||
- Claude Code / CLI tools
|
||||
- Include the local IP, port, and API key pre-filled
|
||||
- QR code for mobile/quick access
|
||||
|
||||
### 3. API Playground
|
||||
- Built-in Swagger/OpenAPI UI or custom test panel
|
||||
- Pre-loaded with all supported endpoints:
|
||||
- `/v1/chat/completions` (OpenAI-compatible)
|
||||
- `/v1/messages` (Anthropic-compatible)
|
||||
- `/v1beta/models/...generateContent` (Gemini-native)
|
||||
- Allow sending test requests and viewing formatted responses
|
||||
- Show exact curl equivalent for every request
|
||||
|
||||
### 4. Health Dashboard
|
||||
- Real-time metrics cards:
|
||||
- Requests/minute
|
||||
- Average latency (p50, p95, p99)
|
||||
- Error rate %
|
||||
- Active connections
|
||||
- Circuit breaker status (open/closed/half-open)
|
||||
- Sparkline charts for the last 5 minutes
|
||||
- Color-coded status indicators
|
||||
|
||||
### 5. Model Availability Status
|
||||
- Live grid showing which models are currently available
|
||||
- Per-model indicators:
|
||||
- Green: available
|
||||
- Yellow: rate-limited (retry after)
|
||||
- Red: unavailable / quota exhausted
|
||||
- Show last-used time and remaining quota (if known)
|
||||
- Collapse/expand by provider (Gemini, Anthropic)
|
||||
|
||||
### 6. Error Feed
|
||||
- Scrollable log of recent errors
|
||||
- Columns: timestamp, endpoint, model, account, error code, message
|
||||
- Click to expand full request/response
|
||||
- Filter by error type (rate limit, timeout, auth, network)
|
||||
- Export to JSON/CSV
|
||||
|
||||
### 7. Connection Visualizer
|
||||
- Diagram showing: IDE → Proxy → Account Pool → Upstream API
|
||||
- Animated flow dots representing live requests
|
||||
- Highlight currently active account
|
||||
- Show queue depth / waiting requests
|
||||
- Simple but effective for understanding routing
|
||||
|
||||
### 8. One-Click Diagnostics
|
||||
- "Run Diagnostics" button that performs:
|
||||
- Check if proxy port is accessible
|
||||
- Test each configured account
|
||||
- Verify model mapping resolution
|
||||
- Check upstream API reachability
|
||||
- Validate API key format
|
||||
- Output: green checkmarks or detailed error report with fix suggestions
|
||||
|
||||
### 9. Session Stats
|
||||
- Summary of current session since proxy started:
|
||||
- Total requests handled
|
||||
- Total tokens proxied
|
||||
- Unique accounts used
|
||||
- Top 3 models used
|
||||
- Uptime counter
|
||||
- Resettable per-session (not persisted)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
- Many of these features require new IPC endpoints in `src/ipc/proxy/router.ts`
|
||||
- Health metrics likely already exist in `ProxyService` but need exposure
|
||||
- Traffic logging exists via `TrafficLogger` — just needs UI
|
||||
- Model availability data is already fetched for the account pool page
|
||||
@@ -0,0 +1,81 @@
|
||||
# Proxy Page — UI/UX Improvements
|
||||
|
||||
> Generated from designer agent analysis of `src/routes/proxy.tsx`
|
||||
> Date: 2026-04-22
|
||||
|
||||
## Current Pain Points
|
||||
1. **Visual noise**: Gradient backgrounds (`bg-gradient-to-br`) feel dated and clash with the dark, technical aesthetic
|
||||
2. **Information overload**: All content is visible at once — no progressive disclosure
|
||||
3. **Code examples dominate**: Large, always-visible code blocks push important controls below the fold
|
||||
4. **Inconsistent density**: Too much padding in some areas, cramped in others
|
||||
5. **Security concern**: API key is displayed in plain text with only a basic copy button
|
||||
6. **No status context**: User can't see which account/model is being used for requests
|
||||
|
||||
## Proposed Improvements
|
||||
|
||||
### 1. Remove Gradient Backgrounds
|
||||
- Replace `bg-gradient-to-br from-slate-800 to-slate-900` with solid `bg-card` or subtle borders
|
||||
- Use elevation (borders, subtle shadows) instead of gradients to create hierarchy
|
||||
- Align with the rest of the app which uses flat, technical styling
|
||||
|
||||
### 2. Tabs-Based Layout
|
||||
Organize the page into tabs:
|
||||
- **Status** (default): Service state, quick actions, health metrics
|
||||
- **Routing**: Model mapping, account scheduling, protocol settings
|
||||
- **API Docs**: Code examples, endpoint documentation, IDE configs
|
||||
- **Logs**: Live traffic feed, error history
|
||||
|
||||
### 3. Compact Protocol Selector
|
||||
- Current: large buttons with icons
|
||||
- Proposed: `Select` dropdown or compact segmented control
|
||||
- Save horizontal space for more important controls
|
||||
|
||||
### 4. Collapsible Code Examples
|
||||
- Wrap each code block in a `Collapsible` or `Accordion`
|
||||
- Show only the title + language icon by default
|
||||
- Expand on click
|
||||
- Add nested tabs for different languages (cURL, Python, JavaScript, TypeScript)
|
||||
|
||||
### 5. Security-Focused API Key Display
|
||||
- Mask the API key by default (show `sk-••••••••`)
|
||||
- Reveal on explicit "Show" toggle
|
||||
- Add "Regenerate" button with confirmation dialog
|
||||
- Show "Last used" timestamp
|
||||
- Add warning if key is the default/weak key
|
||||
|
||||
### 6. Configurable Model Mapping Table
|
||||
- Instead of a static list, show an editable table:
|
||||
- Requested model | Resolved model | Provider | Status
|
||||
- Allow users to add custom mappings
|
||||
- Show which mappings are active/inactive
|
||||
- Validate that resolved models exist in the account pool
|
||||
|
||||
### 7. Account Quota Context
|
||||
- Show the currently selected "primary" account with its quota bar
|
||||
- If auto-switching is enabled, show "Pool Mode: Active (N accounts)"
|
||||
- Link to the account pool page for detailed management
|
||||
|
||||
### 8. Toast Copy Feedback
|
||||
- When user clicks "Copy", show a `Toast` or `Sonner` notification
|
||||
- Current: no feedback — user doesn't know if copy succeeded
|
||||
- Message: "Copied to clipboard" or "Failed to copy"
|
||||
|
||||
### 9. Health Metrics Stub
|
||||
- Even if backend metrics aren't wired yet, create the UI skeleton
|
||||
- Show placeholder charts with "Coming soon" badge
|
||||
- This sets expectations and makes the page feel more complete
|
||||
|
||||
### 10. Empty States
|
||||
- When proxy is stopped, show a clear empty state:
|
||||
- Icon: `ServerOff` from lucide
|
||||
- Message: "Proxy is not running"
|
||||
- Action: "Start Proxy" button prominently displayed
|
||||
- When no accounts are configured, show a CTA to add accounts
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
- **Density over whitespace**: This is a developer tool — show more information per pixel
|
||||
- **Progressive disclosure**: Hide advanced options behind tabs/accordions
|
||||
- **Immediate feedback**: Every action should have a visible result (toast, spinner, state change)
|
||||
- **Security first**: API keys and sensitive data should never be displayed casually
|
||||
@@ -0,0 +1,125 @@
|
||||
# Proxy Page — Advanced Features Hidden in Backend
|
||||
|
||||
> Generated from oracle agent analysis of `src/server/modules/proxy/`
|
||||
> Date: 2026-04-22
|
||||
|
||||
## Discovery
|
||||
The backend (`ProxyService`, `ProxyController`, `ProxyModule`) implements many sophisticated features that are **completely invisible** in the current UI. The proxy page only shows: port, protocol, API key, and basic start/stop.
|
||||
|
||||
## Hidden Backend Features
|
||||
|
||||
### 1. Advanced Routing & Scheduling Controls
|
||||
**Backend location**: `proxy.service.ts`, `proxy-scheduler.service.ts`
|
||||
**What's implemented**:
|
||||
- `scheduler.maxRequestsPerAccount`: Limit requests per account before switching
|
||||
- `scheduler.maxWaitTime`: Max time to wait for an available account
|
||||
- `scheduler.retryAttempts`: Number of retries per request
|
||||
- `scheduler.backoffStrategy`: `exponential` | `linear` | `fixed`
|
||||
- `scheduler.priorityAccounts`: Array of account IDs to prioritize
|
||||
|
||||
**UI exposure**: None. These are hardcoded or set via config files only.
|
||||
|
||||
### 2. Live Traffic Inspector
|
||||
**Backend location**: `traffic-logger.service.ts`
|
||||
**What's implemented**:
|
||||
- `TrafficLogger` captures every request/response with metadata
|
||||
- Fields: timestamp, requestId, accountId, model, promptTokens, completionTokens, latency, status, error
|
||||
- Log rotation and cleanup policies
|
||||
- Export to JSON/CSV
|
||||
|
||||
**UI exposure**: None. Logs are only written to files.
|
||||
|
||||
### 3. Circuit Breaker Dashboard
|
||||
**Backend location**: `proxy-circuit-breaker.service.ts`
|
||||
**What's implemented**:
|
||||
- Per-account circuit breaker state: `CLOSED`, `OPEN`, `HALF_OPEN`
|
||||
- Failure threshold and recovery timeout
|
||||
- When open: requests fail fast without hitting the API
|
||||
- Metrics: failure rate, last failure time, consecutive successes
|
||||
|
||||
**UI exposure**: None. Circuit breaker state is internal only.
|
||||
|
||||
### 4. Parity Testing Sandbox
|
||||
**Backend location**: `proxy-parity.service.ts`
|
||||
**What's implemented**:
|
||||
- Send identical requests to multiple models simultaneously
|
||||
- Compare responses for consistency
|
||||
- Detect model drift or API changes
|
||||
- Configurable test suites
|
||||
|
||||
**UI exposure**: None. Only accessible via manual API calls or scripts.
|
||||
|
||||
### 5. Custom Model Mapping Table
|
||||
**Backend location**: `proxy-model-mapper.service.ts`
|
||||
**What's implemented**:
|
||||
- Dynamic model resolution: `claude-opus-4` → `gemini-3-flash`
|
||||
- User-defined mappings stored in database
|
||||
- Regex-based model matching
|
||||
- Fallback chain: if mapped model unavailable, try next
|
||||
|
||||
**UI exposure**: A static, read-only list is shown. No editing capability.
|
||||
|
||||
### 6. Upstream Proxy Configuration
|
||||
**Backend location**: `proxy-upstream.service.ts`
|
||||
**What's implemented**:
|
||||
- `upstreamProxy.url`: Route all traffic through another proxy
|
||||
- `upstreamProxy.auth`: Basic auth or bearer token for upstream
|
||||
- `upstreamProxy.bypassList`: Domains to bypass
|
||||
|
||||
**UI exposure**: None. Set via environment variables only.
|
||||
|
||||
### 7. IDE Integration Quick-Setup
|
||||
**Backend location**: `proxy-config.service.ts` — `generateIdeConfig()`
|
||||
**What's implemented**:
|
||||
- Generates ready-to-paste configs for:
|
||||
- Cursor (`settings.json`)
|
||||
- VS Code + Cline/Roo Code
|
||||
- OpenCode (`.env`)
|
||||
- Claude Code (`claude_desktop_config.json`)
|
||||
- Generic OpenAI client
|
||||
- Includes local IP detection, port, and API key
|
||||
|
||||
**UI exposure**: Code examples are generic curl snippets. No IDE-specific configs.
|
||||
|
||||
### 8. Health & Performance Metrics
|
||||
**Backend location**: `proxy-metrics.service.ts`
|
||||
**What's implemented**:
|
||||
- Request latency histogram (p50, p95, p99)
|
||||
- Requests per minute counter
|
||||
- Error rate by status code
|
||||
- Active connection gauge
|
||||
- Token throughput (tokens/minute)
|
||||
- Account utilization percentage
|
||||
|
||||
**UI exposure**: None. Metrics are only exposed via a `/metrics` endpoint for Prometheus.
|
||||
|
||||
### 9. Request Replay & Debug Tool
|
||||
**Backend location**: `proxy-replay.service.ts`
|
||||
**What's implemented**:
|
||||
- Store recent requests (last 100 by default)
|
||||
- Replay any request with one click
|
||||
- Modify headers/body before replay
|
||||
- Compare original vs. replayed response
|
||||
- Export request as curl command
|
||||
|
||||
**UI exposure**: None.
|
||||
|
||||
### 10. Global Timeout & Max Wait Controls
|
||||
**Backend location**: `proxy-config.service.ts`
|
||||
**What's implemented**:
|
||||
- `requestTimeout`: Global timeout for all upstream requests
|
||||
- `connectTimeout`: TCP connection timeout
|
||||
- `maxWaitTime`: Max time to wait for an available account
|
||||
- `idleTimeout`: Close idle connections after N seconds
|
||||
|
||||
**UI exposure**: None. Hardcoded defaults.
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
Create a new "Advanced" tab on the proxy page that exposes these features progressively:
|
||||
1. **Phase 1**: Read-only views (circuit breaker status, health metrics, traffic logs)
|
||||
2. **Phase 2**: Interactive controls (model mapping editor, scheduler settings)
|
||||
3. **Phase 3**: Power-user tools (replay, parity testing, upstream proxy config)
|
||||
|
||||
This would transform the proxy page from a simple on/off switch into a comprehensive API gateway dashboard.
|
||||
@@ -0,0 +1,156 @@
|
||||
# Proxy Page — Code Organization Refactoring Plan
|
||||
|
||||
> Generated from oracle agent analysis of `src/routes/proxy.tsx`
|
||||
> Date: 2026-04-22
|
||||
|
||||
## Current State
|
||||
`src/routes/proxy.tsx` is a **724-line monolith** containing:
|
||||
- Route definition
|
||||
- State management (useState, useEffect, useMemo)
|
||||
- Data fetching (useQuery equivalents for proxy config, local IPs)
|
||||
- Derived state (code examples, URLs)
|
||||
- UI rendering (service card, model mapping, code examples, local access banner)
|
||||
- Event handlers (start/stop, copy, protocol change)
|
||||
- Constants and helper functions
|
||||
|
||||
## Target Architecture
|
||||
Split into **7+ files** following the project's conventions:
|
||||
|
||||
```
|
||||
src/
|
||||
├── routes/
|
||||
│ └── proxy.tsx # ~50 lines — route definition + composition
|
||||
├── components/
|
||||
│ └── proxy/
|
||||
│ ├── ProxyServiceCard.tsx
|
||||
│ ├── ProxyModelMappingCard.tsx
|
||||
│ ├── ProxyUsageExamplesCard.tsx
|
||||
│ └── ProxyLocalAccessBanner.tsx
|
||||
├── hooks/
|
||||
│ ├── useProxyConfig.ts
|
||||
│ ├── useLocalIps.ts
|
||||
│ └── useProxyExamples.ts
|
||||
├── constants/
|
||||
│ └── proxy.ts # API endpoints, default ports, protocols
|
||||
└── utils/
|
||||
└── proxy.ts # URL builders, formatters, parsers
|
||||
```
|
||||
|
||||
## File Breakdown
|
||||
|
||||
### 1. `src/hooks/useProxyConfig.ts`
|
||||
**Responsibility**: Fetch and mutate proxy configuration
|
||||
```typescript
|
||||
export function useProxyConfig() {
|
||||
// Fetches from ipc.client.proxy.getConfig()
|
||||
// Provides: config, isLoading, error, updateConfig, startProxy, stopProxy
|
||||
// Handles optimistic updates for start/stop toggling
|
||||
}
|
||||
```
|
||||
|
||||
### 2. `src/hooks/useLocalIps.ts`
|
||||
**Responsibility**: Get local IP addresses for the access banner
|
||||
```typescript
|
||||
export function useLocalIps() {
|
||||
// Fetches from ipc.client.system.getLocalIps()
|
||||
// Returns: ips[], isLoading, error
|
||||
}
|
||||
```
|
||||
|
||||
### 3. `src/hooks/useProxyExamples.ts`
|
||||
**Responsibility**: Generate code examples based on current config
|
||||
```typescript
|
||||
export function useProxyExamples(config: ProxyConfig, localIps: string[]) {
|
||||
// Returns memoized examples for curl, Python, Node.js
|
||||
// Updates when port, protocol, or API key changes
|
||||
}
|
||||
```
|
||||
|
||||
### 4. `src/components/proxy/ProxyServiceCard.tsx`
|
||||
**Responsibility**: The main service control card
|
||||
- Start/stop toggle
|
||||
- Port input
|
||||
- Protocol selector
|
||||
- API key display (with mask/reveal)
|
||||
- Status indicator
|
||||
|
||||
### 5. `src/components/proxy/ProxyModelMappingCard.tsx`
|
||||
**Responsibility**: Model mapping display/editor
|
||||
- Read-only list (current state)
|
||||
- Future: editable table with add/remove
|
||||
- Show provider badges
|
||||
|
||||
### 6. `src/components/proxy/ProxyUsageExamplesCard.tsx`
|
||||
**Responsibility**: Code examples section
|
||||
- Collapsible/accordion layout
|
||||
- Tabs for different languages
|
||||
- Copy-to-clipboard buttons
|
||||
- Syntax highlighting (if available)
|
||||
|
||||
### 7. `src/components/proxy/ProxyLocalAccessBanner.tsx`
|
||||
**Responsibility**: "Access your proxy locally" banner
|
||||
- Show local IPs
|
||||
- QR code (future)
|
||||
- Copy URL buttons
|
||||
|
||||
### 8. `src/constants/proxy.ts`
|
||||
**Responsibility**: Constants
|
||||
```typescript
|
||||
export const DEFAULT_PROXY_PORT = 3000;
|
||||
export const PROTOCOLS = ['http', 'https'] as const;
|
||||
export const EXAMPLE_MODELS = ['gpt-4o', 'claude-3-opus', 'gemini-pro'];
|
||||
```
|
||||
|
||||
### 9. `src/utils/proxy.ts`
|
||||
**Responsibility**: Pure helper functions
|
||||
```typescript
|
||||
export function buildProxyUrl(protocol: string, ip: string, port: number): string;
|
||||
export function maskApiKey(key: string): string;
|
||||
export function formatCurlExample(url: string, model: string, apiKey: string): string;
|
||||
```
|
||||
|
||||
## Route File After Refactoring
|
||||
|
||||
```typescript
|
||||
// src/routes/proxy.tsx — ~50 lines
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useProxyConfig } from '@/hooks/useProxyConfig';
|
||||
import { useLocalIps } from '@/hooks/useLocalIps';
|
||||
import { ProxyServiceCard } from '@/components/proxy/ProxyServiceCard';
|
||||
import { ProxyModelMappingCard } from '@/components/proxy/ProxyModelMappingCard';
|
||||
import { ProxyUsageExamplesCard } from '@/components/proxy/ProxyUsageExamplesCard';
|
||||
import { ProxyLocalAccessBanner } from '@/components/proxy/ProxyLocalAccessBanner';
|
||||
|
||||
function ProxyPage() {
|
||||
const { config, isLoading, startProxy, stopProxy } = useProxyConfig();
|
||||
const { ips } = useLocalIps();
|
||||
|
||||
if (isLoading) return <ProxySkeleton />;
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<ProxyServiceCard config={config} onStart={startProxy} onStop={stopProxy} />
|
||||
<ProxyModelMappingCard mappings={config.modelMappings} />
|
||||
<ProxyUsageExamplesCard config={config} ips={ips} />
|
||||
<ProxyLocalAccessBanner ips={ips} port={config.port} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/proxy')({ component: ProxyPage });
|
||||
```
|
||||
|
||||
## Migration Steps
|
||||
1. Create new files (hooks, components, constants, utils)
|
||||
2. Move code from `proxy.tsx` to appropriate files
|
||||
3. Update imports in `proxy.tsx`
|
||||
4. Extract and test each hook independently
|
||||
5. Add Storybook stories for each component (if Storybook is used)
|
||||
6. Verify no regression in functionality
|
||||
|
||||
## Benefits
|
||||
- **Testability**: Each hook and component can be unit tested in isolation
|
||||
- **Reusability**: `useLocalIps` can be used in other pages (e.g., settings)
|
||||
- **Maintainability**: 50-line route file vs. 724-line monolith
|
||||
- **Code review**: Smaller files = faster, more focused PRs
|
||||
- **Onboarding**: New developers can understand the page in minutes
|
||||
@@ -0,0 +1,132 @@
|
||||
# Proxy Page — Code Quality Issues
|
||||
|
||||
> Generated from fixer agent analysis of `src/routes/proxy.tsx`
|
||||
> Date: 2026-04-22
|
||||
|
||||
## Severity Legend
|
||||
- 🔴 **Critical**: Bugs or crashes
|
||||
- 🟡 **High**: Performance or reliability issues
|
||||
- 🟢 **Medium**: Maintainability or best practice violations
|
||||
|
||||
## Issues Found
|
||||
|
||||
### 1. 🔴 State Duplication Between Proxy Config and Local State
|
||||
**Location**: Lines ~120-180
|
||||
**Issue**: The component maintains both `proxyConfig` (from IPC) and local `useState` copies for `port`, `protocol`, `apiKey`. These can drift out of sync if the backend config changes externally.
|
||||
**Fix**: Use a single source of truth. Either:
|
||||
- Derive local state from `proxyConfig` with `useEffect` sync
|
||||
- Or use `useQuery` with `staleTime: 0` and no local cache
|
||||
|
||||
### 2. 🟡 Race Condition in Start/Stop Toggle
|
||||
**Location**: Lines ~200-250
|
||||
**Issue**: If user clicks start/stop rapidly, multiple IPC calls fire in parallel. The UI shows intermediate states that may not reflect the actual backend state.
|
||||
**Fix**: Disable the toggle button while a transition is in progress. Add an `isTransitioning` flag.
|
||||
|
||||
### 3. 🟢 Clipboard Error Handling
|
||||
**Location**: Lines ~300-320
|
||||
**Issue**: `navigator.clipboard.writeText()` can fail (permission denied, not in secure context). The code has no try/catch.
|
||||
**Fix**:
|
||||
```typescript
|
||||
async function copyToClipboard(text: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast.success('Copied!');
|
||||
} catch {
|
||||
toast.error('Failed to copy');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 🟢 Unstable Callbacks Causing Re-renders
|
||||
**Location**: Throughout
|
||||
**Issue**: Event handlers are defined inline in JSX, causing child components to re-render unnecessarily.
|
||||
**Fix**: Wrap handlers in `useCallback`:
|
||||
```typescript
|
||||
const handleStart = useCallback(() => startProxy(), [startProxy]);
|
||||
const handleStop = useCallback(() => stopProxy(), [stopProxy]);
|
||||
```
|
||||
|
||||
### 5. 🟡 Memory Leak in setTimeout
|
||||
**Location**: Lines ~400-420
|
||||
**Issue**: A `setTimeout` is used for a "copy feedback" animation but is never cleared on unmount.
|
||||
**Fix**: Use `useEffect` cleanup:
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setCopied(false), 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [copied]);
|
||||
```
|
||||
|
||||
### 6. 🟢 Missing Loading States for Mutations
|
||||
**Location**: Lines ~200-250
|
||||
**Issue**: When starting/stopping the proxy, there's no loading spinner or disabled state on the button.
|
||||
**Fix**: Add `isPending` state to the mutation hook and disable the button + show spinner while pending.
|
||||
|
||||
### 7. 🟢 Hardcoded i18n Strings
|
||||
**Location**: Throughout
|
||||
**Issue**: Many UI strings are hardcoded in English instead of using `useTranslation()`:
|
||||
- "Proxy Service"
|
||||
- "Start Proxy"
|
||||
- "Stop Proxy"
|
||||
- "Copy"
|
||||
- "Copied!"
|
||||
**Fix**: Extract all strings to `src/localization/i18n.ts` and use `t('proxy.startProxy')`, etc.
|
||||
|
||||
### 8. 🟢 Magic Numbers
|
||||
**Location**: Lines ~450-500
|
||||
**Issue**: Port validation uses hardcoded `3000`, `65535` without constants.
|
||||
**Fix**:
|
||||
```typescript
|
||||
const MIN_PORT = 1024;
|
||||
const MAX_PORT = 65535;
|
||||
const DEFAULT_PORT = 3000;
|
||||
```
|
||||
|
||||
### 9. 🟢 Unsafe Type Cast
|
||||
**Location**: Lines ~550-600
|
||||
**Issue**: `event.target.value` is cast to `Protocol` without validation.
|
||||
**Fix**:
|
||||
```typescript
|
||||
const protocol = event.target.value;
|
||||
if (!PROTOCOLS.includes(protocol)) return;
|
||||
setProtocol(protocol as Protocol);
|
||||
```
|
||||
|
||||
### 10. 🟡 Dangerous Parsing of Local IPs
|
||||
**Location**: Lines ~650-700
|
||||
**Issue**: `localIps` is parsed from a shell command output using string splitting that could break on unexpected formats.
|
||||
**Fix**: Use a robust parser or, better, expose `os.networkInterfaces()` via a safe IPC endpoint.
|
||||
|
||||
### 11. 🟢 Unnecessary Re-renders from Inline Objects
|
||||
**Location**: Lines ~350-380
|
||||
**Issue**: Inline style objects and arrays are recreated on every render, breaking `React.memo` optimizations in child components.
|
||||
**Fix**: Extract static styles outside the component or memoize:
|
||||
```typescript
|
||||
const cardStyle = useMemo(() => ({ padding: '1.5rem' }), []);
|
||||
```
|
||||
|
||||
### 12. 🟢 No Validation on Port Input
|
||||
**Location**: Lines ~150-170
|
||||
**Issue**: User can type any value into the port field, including non-numbers, negatives, or out-of-range values.
|
||||
**Fix**: Add input validation:
|
||||
```typescript
|
||||
<input
|
||||
type="number"
|
||||
min={1024}
|
||||
max={65535}
|
||||
value={port}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
if (value >= 1024 && value <= 65535) setPort(value);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Recommendations
|
||||
- Add unit tests for `useProxyConfig` hook (mock IPC client)
|
||||
- Test race conditions: rapid start/stop clicks
|
||||
- Test clipboard failure path
|
||||
- Test port validation edge cases (0, 80, 65536, NaN)
|
||||
- Test with `react-testing-library` + `user-event`
|
||||
@@ -0,0 +1,141 @@
|
||||
# Proxy Page — Architecture Overview
|
||||
|
||||
> Generated from explorer agent mapping of proxy-related files
|
||||
> Date: 2026-04-22
|
||||
|
||||
## File Map
|
||||
|
||||
### Frontend
|
||||
| File | Lines | Role |
|
||||
|------|-------|------|
|
||||
| `src/routes/proxy.tsx` | 724 | Main proxy page (monolith) |
|
||||
| `src/components/ModelVisibilitySettings.tsx` | ~200 | Model visibility toggle grid |
|
||||
| `src/components/AccountProxySettings.tsx` | ~180 | Per-account proxy configuration |
|
||||
| `src/hooks/useCloudAccounts.ts` | ~50 | Fetches cloud accounts for proxy routing |
|
||||
|
||||
### Backend (NestJS)
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/server/modules/proxy/proxy.module.ts` | NestJS module definition |
|
||||
| `src/server/modules/proxy/proxy.service.ts` | 1883 lines — core proxy logic, all handlers |
|
||||
| `src/server/modules/proxy/proxy.controller.ts` | HTTP endpoints for proxy control |
|
||||
| `src/server/modules/proxy/proxy-scheduler.service.ts` | Account rotation & scheduling |
|
||||
| `src/server/modules/proxy/proxy-circuit-breaker.service.ts` | Per-account circuit breaker |
|
||||
| `src/server/modules/proxy/traffic-logger.service.ts` | Request/response logging |
|
||||
| `src/server/modules/proxy/proxy-metrics.service.ts` | Prometheus metrics |
|
||||
| `src/server/modules/proxy/proxy-model-mapper.service.ts` | Model name resolution |
|
||||
| `src/server/modules/proxy/proxy-parity.service.ts` | Parity testing |
|
||||
| `src/server/modules/proxy/proxy-replay.service.ts` | Request replay |
|
||||
| `src/server/modules/proxy/proxy-config.service.ts` | Config management |
|
||||
| `src/server/modules/proxy/proxy-upstream.service.ts` | Upstream proxy support |
|
||||
|
||||
### IPC Layer
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/ipc/router.ts` | Main IPC router composition |
|
||||
| `src/ipc/proxy/router.ts` | Proxy-specific IPC endpoints |
|
||||
| `src/ipc/manager.ts` | IPC client initialization |
|
||||
|
||||
### Types & Schema
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/types/config.ts` | `ProxyConfig` Zod schema — reveals hidden fields |
|
||||
| `src/types/proxy.ts` | Request/response types |
|
||||
|
||||
## Hidden ProxyConfig Fields
|
||||
|
||||
The `ProxyConfig` schema in `src/types/config.ts` contains many fields never shown in the UI:
|
||||
|
||||
```typescript
|
||||
interface ProxyConfig {
|
||||
// Shown in UI
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
protocol: 'http' | 'https';
|
||||
apiKey: string;
|
||||
|
||||
// Hidden from UI
|
||||
scheduler: {
|
||||
maxRequestsPerAccount: number;
|
||||
maxWaitTime: number;
|
||||
retryAttempts: number;
|
||||
backoffStrategy: 'exponential' | 'linear' | 'fixed';
|
||||
priorityAccounts: string[];
|
||||
};
|
||||
upstreamProxy?: {
|
||||
url: string;
|
||||
auth?: { username: string; password: string };
|
||||
bypassList: string[];
|
||||
};
|
||||
circuitBreaker: {
|
||||
failureThreshold: number;
|
||||
recoveryTimeout: number;
|
||||
halfOpenMaxCalls: number;
|
||||
};
|
||||
logging: {
|
||||
enabled: boolean;
|
||||
maxEntries: number;
|
||||
retentionDays: number;
|
||||
};
|
||||
metrics: {
|
||||
enabled: boolean;
|
||||
histogramBuckets: number[];
|
||||
};
|
||||
modelMappings: Array<{
|
||||
from: string;
|
||||
to: string;
|
||||
provider: string;
|
||||
enabled: boolean;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐ IPC ┌──────────────────┐
|
||||
│ proxy.tsx │ ◄──────────► │ proxy/router.ts │
|
||||
│ (React UI) │ │ (IPC handlers) │
|
||||
└─────────────────┘ └────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ ProxyController │
|
||||
│ (HTTP endpoints) │
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ ProxyService │
|
||||
│ (Core orchestrator) │
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌───────────────┐ ┌──────────────┐
|
||||
│ Scheduler │ │ CircuitBreaker│ │Model Mapper │
|
||||
└─────────────┘ └───────────────┘ └──────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌───────────────┐ ┌──────────────┐
|
||||
│TrafficLogger│ │MetricsService │ │ParityService │
|
||||
└─────────────┘ └───────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## Key Observations
|
||||
1. **Backend is significantly more capable than the UI suggests** — 12 services vs. 1 page
|
||||
2. **IPC layer is thin** — most backend services are not exposed via IPC, only HTTP
|
||||
3. **ProxyConfig schema is the source of truth** — it documents everything the backend can do
|
||||
4. **No real-time communication** — UI polls for status; no WebSocket or SSE for live updates
|
||||
5. **Separation of concerns is good on backend**, terrible on frontend (724-line monolith)
|
||||
|
||||
## Recommended Architecture Changes
|
||||
1. Extend `src/ipc/proxy/router.ts` to expose all `ProxyConfig` fields
|
||||
2. Add IPC endpoints for:
|
||||
- `proxy.getTrafficLogs()`
|
||||
- `proxy.getCircuitBreakerStatus()`
|
||||
- `proxy.getMetrics()`
|
||||
- `proxy.replayRequest()`
|
||||
3. Create a WebSocket or SSE channel for live traffic updates
|
||||
4. Refactor frontend into the 7-file structure proposed in `04-code-organization.md`
|
||||
Reference in new issue
Block a user