diff --git a/AGENTS.md b/AGENTS.md index 1ca7f33..f8558dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,114 +1,63 @@ # AGENTS.md - AI Coding Agent Guidelines -This document provides guidelines for AI coding agents working on the BillAI project. +Guidelines for AI coding agents working on BillAI - a microservices bill analysis system. -## Project Overview +## Architecture +- `web/` - SvelteKit 5 + TailwindCSS 4 + TypeScript +- `server/` - Go 1.21 + Gin + MongoDB +- `analyzer/` - Python 3.12 + FastAPI -BillAI is a microservices-based personal bill analysis system supporting WeChat and Alipay bill parsing, intelligent categorization, and visualization. - -**Architecture:** -- `web/` - Frontend (SvelteKit 5 + TailwindCSS 4.x + TypeScript) -- `server/` - Backend API (Go 1.21 + Gin + MongoDB) -- `analyzer/` - Python analysis service (Python 3.12 + FastAPI) - -## Build, Lint, and Test Commands +## Build/Lint/Test Commands ### Frontend (web/) - ```bash -# Development -npm run dev # Start dev server (Vite) - -# Build -npm run build # Production build -npm run preview # Preview production build - -# Type checking -npm run check # svelte-check with TypeScript -npm run check:watch # Watch mode - -# Linting and formatting -npm run lint # Prettier check + ESLint -npm run format # Format with Prettier - -# Testing -npm run test # Run all tests once -npm run test:unit # Run tests in watch mode -npx vitest run src/demo.spec.ts # Run single test file -npx vitest run -t "sum test" # Run tests matching name -npx vitest run src/routes/page.svelte.spec.ts # Run component test +npm run dev # Start dev server +npm run build # Production build +npm run check # TypeScript check +npm run lint # Prettier + ESLint +npm run format # Format code +npm run test # Run all tests +npx vitest run src/routes/+page.spec.ts # Single test file +npx vitest run -t "test name" # Test by name ``` ### Backend (server/) - ```bash -# Run -go run . # Start development server - -# Build -go build . # Build binary - -# Dependencies -go mod download # Install dependencies -go mod tidy # Clean up dependencies - -# Testing (if tests exist) -go test ./... # Run all tests -go test ./handler/... # Run tests in specific package -go test -run TestName # Run single test by name +go run . # Start server +go build . # Build binary +go mod tidy # Clean dependencies +go test ./... # All tests +go test ./handler/... # Package tests +go test -run TestName # Single test ``` ### Analyzer (analyzer/) - ```bash -# Setup -python -m venv venv -pip install -r requirements.txt - -# Run -python server.py # Start FastAPI server - -# Testing (if tests exist) -pytest # Run all tests -pytest test_file.py # Run single test file -pytest -k "test_name" # Run tests matching name +python server.py # Start FastAPI server +pytest # All tests +pytest test_file.py # Single file +pytest -k "test_name" # Test by pattern ``` ### Docker - ```bash -docker-compose up -d --build # Start all services -docker-compose ps # Check service status -docker-compose down # Stop all services -docker-compose logs -f web # Follow logs for specific service +docker-compose up -d --build # Start/rebuild all +docker-compose logs -f server # Follow service logs ``` -## Code Style Guidelines +## Code Style -### TypeScript/Svelte (Frontend) - -**Formatting (Prettier):** -- Use tabs for indentation -- Single quotes for strings -- No trailing commas -- Print width: 100 characters +### TypeScript/Svelte +**Prettier config:** Tabs, single quotes, no trailing commas, width 100 **Imports:** -- Use `$lib/` alias for imports from `src/lib/` -- Use `$app/` for SvelteKit internals -- Group imports: external packages, then internal modules - ```typescript -import { browser } from '$app/environment'; -import { auth } from '$lib/stores/auth'; +import { browser } from '$app/environment'; // SvelteKit +import { auth } from '$lib/stores/auth'; // Internal import type { UIBill } from '$lib/models/bill'; ``` **Types:** -- Define interfaces for API responses and requests -- Use `type` for unions and simple type aliases -- Export types from dedicated files in `$lib/types/` or alongside models - ```typescript export interface UploadResponse { result: boolean; @@ -117,55 +66,20 @@ export interface UploadResponse { } ``` -**Naming Conventions:** -- PascalCase: Components, interfaces, types -- camelCase: Functions, variables, properties -- Use descriptive names: `fetchBills`, `UIBill`, `checkHealth` - -**Error Handling:** -- Wrap API calls in try/catch -- Throw `Error` with HTTP status for API failures -- Handle 401 responses with logout redirect +**Naming:** PascalCase (types, components), camelCase (functions, variables) +**Error handling:** ```typescript if (!response.ok) { throw new Error(`HTTP ${response.status}`); } +// Handle 401 -> logout redirect ``` -### Go (Backend) - -**Project Structure:** -- `handler/` - HTTP request handlers -- `service/` - Business logic -- `repository/` - Data access layer -- `model/` - Data structures -- `adapter/` - External service integrations -- `config/` - Configuration management -- `middleware/` - Auth and other middleware - -**Naming Conventions:** -- PascalCase: Exported types, functions, constants -- camelCase: Unexported functions, variables -- Use descriptive names: `UpdateBillRequest`, `parseBillTime` - -**Error Handling:** -- Define sentinel errors in `repository/errors.go` -- Return errors up the call stack -- Use structured JSON responses for HTTP errors - -```go -if err == repository.ErrNotFound { - c.JSON(http.StatusNotFound, Response{Result: false, Message: "not found"}) - return -} -``` - -**JSON Tags:** -- Use snake_case for JSON field names -- Use `omitempty` for optional fields -- Match frontend API expectations +### Go Backend +**Structure:** `handler/` → `service/` → `repository/` → MongoDB +**JSON tags:** snake_case, omitempty for optional fields ```go type UpdateBillRequest struct { Category *string `json:"category,omitempty"` @@ -173,9 +87,7 @@ type UpdateBillRequest struct { } ``` -**Response Format:** -- All API responses use consistent structure: - +**Response format:** ```go type Response struct { Result bool `json:"result"` @@ -184,12 +96,16 @@ type Response struct { } ``` -### Python (Analyzer) +**Error handling:** +```go +if err == repository.ErrNotFound { + c.JSON(http.StatusNotFound, Response{Result: false, Message: "not found"}) + return +} +``` -**Style:** -- Follow PEP 8 -- Use type hints for function signatures -- Use Pydantic models for request/response validation +### Python Analyzer +**Style:** PEP 8, type hints, Pydantic models ```python def do_clean( @@ -199,56 +115,27 @@ def do_clean( ) -> tuple[bool, str, str]: ``` -**Error Handling:** -- Raise `HTTPException` for API errors -- Use try/except for file operations -- Return structured responses - +**Error handling:** ```python if not success: raise HTTPException(status_code=400, detail=message) ``` -## Testing Guidelines +## Key Patterns -**Frontend Tests:** -- Use Vitest with Playwright for browser testing -- Component tests: `*.svelte.spec.ts` -- Unit tests: `*.spec.ts` -- Tests require assertions: `expect.assertions()` or explicit expects +**API Flow:** Frontend (SvelteKit proxy) → Go API → MongoDB + Python analyzer -```typescript -import { describe, it, expect } from 'vitest'; -import { render } from 'vitest-browser-svelte'; +**Auth:** JWT tokens, Bearer header, 401 → logout redirect -describe('/+page.svelte', () => { - it('should render h1', async () => { - render(Page); - await expect.element(page.getByRole('heading')).toBeInTheDocument(); - }); -}); -``` +**File Processing:** ZIP → extract → convert (GBK→UTF-8, xlsx→csv) → clean → import -## Important Patterns +**Testing:** Vitest + Playwright for frontend, Go test for backend -**API Communication:** -- Frontend proxies API calls through SvelteKit to avoid CORS -- Backend uses Gin framework with JSON responses -- Analyzer communicates via HTTP (preferred) or subprocess - -**Data Flow:** -- Frontend (SvelteKit) -> Backend (Go/Gin) -> MongoDB -- Backend -> Analyzer (Python/FastAPI) for bill parsing - -**Authentication:** -- JWT tokens stored in frontend auth store -- Bearer token sent in Authorization header -- 401 responses trigger logout and redirect - -## File Locations - -- API types: `web/src/lib/api.ts` -- UI models: `web/src/lib/models/` -- Go handlers: `server/handler/` -- Go models: `server/model/` -- Python API: `analyzer/server.py` +## Important Files +- `web/src/lib/api.ts` - API client +- `web/src/lib/models/` - UI data models +- `server/handler/` - HTTP handlers +- `server/service/` - Business logic +- `server/model/` - Go data structures +- `analyzer/cleaners/` - Bill processing +- `mock_data/*.zip` - Test data (password: 123456) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500e208..5a2684c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,22 @@ - 新增 `DELETE /api/bills/:id` 接口 - `BillDetailDrawer` 组件新增 `allowDelete` 和 `onDelete` props +## [1.2.1] - 2026-01-23 + +### 优化 +- **智能复核快捷确认** - 在复核列表每行添加快捷确认按钮 + - 无需打开详情页面即可确认分类正确 + - 点击确认按钮立即清除复核标记并从列表移除 + - 自动更新统计数据(总数、高优先级、低优先级计数) + - 按钮支持加载状态显示,防止重复操作 + - 提升复核效率,支持快速批量确认 + +### 文档 +- **AGENTS.md 更新** - 精简为 150 行,专为 AI 编程助手设计 + - 核心构建/测试/lint 命令说明 + - TypeScript、Go、Python 代码风格指南 + - 关键架构模式和文件位置 + ## [1.1.0] - 2026-01-23 ### 新增 diff --git a/web/package.json b/web/package.json index bf63407..73b2b68 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { "name": "web", "private": true, - "version": "1.2.0", + "version": "1.2.1", "type": "module", "scripts": { "dev": "vite dev", diff --git a/web/src/routes/review/+page.svelte b/web/src/routes/review/+page.svelte index f62cc50..2244f23 100644 --- a/web/src/routes/review/+page.svelte +++ b/web/src/routes/review/+page.svelte @@ -7,12 +7,14 @@ import * as Table from '$lib/components/ui/table'; import BillDetailDrawer from '$lib/components/analysis/BillDetailDrawer.svelte'; import { cleanedBillToUIBill, type UIBill } from '$lib/models/bill'; + import { updateBill } from '$lib/api'; import Loader2 from '@lucide/svelte/icons/loader-2'; import AlertCircle from '@lucide/svelte/icons/alert-circle'; import AlertTriangle from '@lucide/svelte/icons/alert-triangle'; import Clock from '@lucide/svelte/icons/clock'; import PartyPopper from '@lucide/svelte/icons/party-popper'; import RefreshCw from '@lucide/svelte/icons/refresh-cw'; + import Check from '@lucide/svelte/icons/check'; let isLoading = $state(true); let errorMessage = $state(''); @@ -20,6 +22,9 @@ let allBills = $state([]); let filterLevel = $state<'all' | 'HIGH' | 'LOW'>('all'); + // 快捷确认按钮的加载状态 (记录ID -> 是否在加载) + let confirmingBills = $state>(new Map()); + onMount(() => { loadReviewData(); }); @@ -88,6 +93,48 @@ drawerOpen = true; } + // 快捷确认(仅清除 review_level,不修改其他字段) + async function quickConfirm(record: CleanedBill, event: Event) { + // 阻止事件冒泡,避免触发行点击 + event.stopPropagation(); + + if (confirmingBills.get(record.id)) return; + + // 设置加载状态 + confirmingBills.set(record.id, true); + confirmingBills = new Map(confirmingBills); + + try { + const resp = await updateBill(record.id, { review_level: '' }); + + if (resp.result) { + // 从列表中移除该记录 + const index = allBills.findIndex(r => r.id === record.id); + if (index !== -1) { + allBills.splice(index, 1); + allBills = [...allBills]; + } + + // 更新统计数据 + if (reviewStats) { + reviewStats = { + ...reviewStats, + total: Math.max(0, reviewStats.total - 1), + high: record.review_level === 'HIGH' ? Math.max(0, reviewStats.high - 1) : reviewStats.high, + low: record.review_level === 'LOW' ? Math.max(0, reviewStats.low - 1) : reviewStats.low + }; + } + } + } catch (err) { + console.error('快捷确认失败:', err); + // 这里可以添加错误提示 + } finally { + // 清除加载状态 + confirmingBills.delete(record.id); + confirmingBills = new Map(confirmingBills); + } + } + // 复核完成后从列表中移除该记录 function handleBillUpdate(updated: UIBill, original: UIBill) { // 更新后 review_level 已被清除,从列表中移除 @@ -254,6 +301,7 @@ 收/支 金额 优先级 + 操作 @@ -287,6 +335,23 @@ {record.review_level} + + + {/each}