mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-09-05 15:46:48 +08:00
fix: 修复 AILogo 噪音、设置页滚动与供应商批量伪内置脏数据
- AILogo: `custom` 名称为合法兜底场景,不再以 console.error 上报;其余未匹配名称降级为 console.warn - SettingPage/Model: 双栏加 `min-h-0 overflow-y-auto`,让供应商列表与右侧表单各自可滚动 - ProviderService.add_provider: API 创建一律落到 `type='custom'`,并对同名供应商抛 ValueError,避免再产生伪内置行 - CLAUDE.md: 补充 v2.0.0 子系统(RAG/Chat、可选 Nacos+RabbitMQ、i18n、cookie/transcriber 管理器) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2cd43770eb
commit
108ad270bf
@@ -8,9 +8,11 @@ interface AILogoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AILogo = ({ name, style = 'Color', size = 24 }: AILogoProps) => {
|
const AILogo = ({ name, style = 'Color', size = 24 }: AILogoProps) => {
|
||||||
const Icon = Icons[name as keyof typeof Icons]
|
const Icon = name ? Icons[name as keyof typeof Icons] : undefined
|
||||||
if (!Icon) {
|
if (!Icon) {
|
||||||
console.error(`❌ 图标组件不存在: ${name}`)
|
if (name && name !== 'custom') {
|
||||||
|
console.warn(`AILogo: 未匹配到图标,使用自定义占位: ${name}`)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<span style={{ fontSize: size }}>
|
<span style={{ fontSize: size }}>
|
||||||
<img src={CustomLogo} alt="CustomLogo" style={{ width: size, height: size }} />
|
<img src={CustomLogo} alt="CustomLogo" style={{ width: size, height: size }} />
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ interface AILogoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AILogo = ({ name, style = 'Color', size = 24 }: AILogoProps) => {
|
const AILogo = ({ name, style = 'Color', size = 24 }: AILogoProps) => {
|
||||||
const Icon = Icons[name as keyof typeof Icons];
|
const Icon = name ? Icons[name as keyof typeof Icons] : undefined;
|
||||||
if (!Icon) {
|
if (!Icon) {
|
||||||
console.error(`❌ 图标组件不存在: ${name}`);
|
if (name && name !== 'custom') {
|
||||||
|
console.warn(`AILogo: 未匹配到图标,使用占位: ${name}`);
|
||||||
|
}
|
||||||
return <span style={{ fontSize: size }}>🚫</span>;
|
return <span style={{ fontSize: size }}>🚫</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { Outlet } from 'react-router-dom'
|
|||||||
|
|
||||||
const Model = () => {
|
const Model = () => {
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-full bg-white'}>
|
<div className={'flex h-full min-h-0 bg-white'}>
|
||||||
<div className={'flex-1/5 border-r border-neutral-200 p-2'}>
|
<div className={'flex-1/5 min-h-0 overflow-y-auto border-r border-neutral-200 p-2'}>
|
||||||
<Provider></Provider>
|
<Provider></Provider>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-4/5'}>
|
<div className={'flex-4/5 min-h-0 overflow-y-auto'}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,22 +39,31 @@ cd BillNote_frontend && pnpm tauri build
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
**Backend** (`backend/`) — FastAPI app, entry point `main.py`:
|
**Backend** (`backend/`) — FastAPI app, entry point `main.py`:
|
||||||
- `app/routers/` — API routes: `note.py` (generation), `provider.py`, `model.py`, `config.py`
|
- `app/routers/` — API routes: `note.py` (generation), `provider.py`, `model.py`, `config.py`, `chat.py` (RAG Q&A on generated notes)
|
||||||
- `app/services/` — Business logic: `note.py` (NoteGenerator orchestrates the full pipeline), `task_serial_executor.py` (task queue)
|
- `app/services/` — Business logic:
|
||||||
|
- `note.py` — `NoteGenerator` orchestrates the full pipeline (download → transcribe → LLM → notes)
|
||||||
|
- `task_serial_executor.py` — task queue
|
||||||
|
- `chat_service.py` + `chat_tools.py` + `vector_store.py` — RAG-based AI Q&A with Function Calling, indexing transcripts and video metadata
|
||||||
|
- `cookie_manager.py` — per-platform cookie storage; injected into yt-dlp by downloaders (e.g. Bilibili)
|
||||||
|
- `transcriber_config_manager.py` — persisted transcriber settings
|
||||||
|
- `worker_registry.py` — **optional** Nacos registration + heartbeat for distributed worker mode (no-op when `NACOS_SERVER_ADDR` unset)
|
||||||
|
- `app/messaging/` — **optional** RabbitMQ producer/consumer publishing task progress/results to `bilinote.task.feedback` exchange. Silently degrades when `RABBITMQ_URL` is unset; always import-safe.
|
||||||
- `app/downloaders/` — Platform adapters (bilibili, youtube, douyin, kuaishou, local) with shared `base.py` interface
|
- `app/downloaders/` — Platform adapters (bilibili, youtube, douyin, kuaishou, local) with shared `base.py` interface
|
||||||
- `app/transcriber/` — Speech-to-text engines (fast-whisper, groq, bcut, kuaishou, mlx-whisper) with factory in `transcriber_provider.py`
|
- `app/transcriber/` — Speech-to-text engines (fast-whisper, groq, bcut, kuaishou, mlx-whisper) with factory in `transcriber_provider.py`. YouTube path prefers existing subtitles and skips audio download when available.
|
||||||
- `app/gpt/` — LLM integration with factory pattern (`gpt_factory.py`), prompt templates (`prompt.py`, `prompt_builder.py`), and `request_chunker.py` for long transcripts
|
- `app/gpt/` — LLM integration with factory pattern (`gpt_factory.py`), prompt templates (`prompt.py`, `prompt_builder.py`), and `request_chunker.py` for long transcripts
|
||||||
- `app/db/` — SQLite + SQLAlchemy: DAO pattern (`provider_dao.py`, `model_dao.py`, `video_task_dao.py`), models in `models/`
|
- `app/db/` — SQLite + SQLAlchemy: DAO pattern (`provider_dao.py`, `model_dao.py`, `video_task_dao.py`), models in `models/`
|
||||||
- `app/utils/` — `response.py` (ResponseWrapper for consistent JSON), `video_helper.py` (screenshots via FFmpeg), `export.py` (PDF/DOCX)
|
- `app/utils/` — `response.py` (ResponseWrapper for consistent JSON), `video_helper.py` (screenshots via FFmpeg), `export.py` (PDF/DOCX), `ppt_generator.py`, `minio_client.py`
|
||||||
|
- `app/i18n/` — backend localization
|
||||||
- `events/` (root level) — Blinker signal system for post-processing (e.g., temp file cleanup after transcription)
|
- `events/` (root level) — Blinker signal system for post-processing (e.g., temp file cleanup after transcription)
|
||||||
|
|
||||||
**Frontend** (`BillNote_frontend/src/`) — React 19 + Vite + Tailwind + shadcn/ui:
|
**Frontend** (`BillNote_frontend/src/`) — React 19 + Vite + Tailwind + shadcn/ui:
|
||||||
- `pages/HomePage/` — Main note generation UI: `NoteForm.tsx` (input), `MarkdownViewer.tsx` (preview), `MarkmapComponent.tsx` (mind map)
|
- `pages/HomePage/` — Main note generation UI: `NoteForm.tsx` (input), `MarkdownViewer.tsx` (preview), `MarkmapComponent.tsx` (mind map)
|
||||||
- `pages/SettingPage/` — LLM provider management, system monitoring, transcriber config
|
- `pages/SettingPage/` — LLM provider management, system monitoring, transcriber config
|
||||||
- `store/` — Zustand stores: `taskStore`, `modelStore`, `configStore`, `providerStore`
|
- `store/` — Zustand stores: `taskStore`, `modelStore`, `configStore`, `providerStore`. Persists to IndexedDB.
|
||||||
- `services/` — Axios API clients matching backend routes
|
- `services/` — Axios API clients matching backend routes
|
||||||
- `hooks/useTaskPolling.ts` — Polls task status every 3 seconds
|
- `hooks/useTaskPolling.ts` — Polls task status every 3 seconds
|
||||||
- `components/ui/` — shadcn/ui (Radix-based) components
|
- `components/ui/` — shadcn/ui (Radix-based) components
|
||||||
|
- `i18n/` — `react-i18next` setup with locale JSON in `i18n/locales/`; toggled via `components/LanguageSwitcher.tsx`
|
||||||
- Path alias: `@` → `./src`
|
- Path alias: `@` → `./src`
|
||||||
|
|
||||||
**Core Workflow**: User submits URL → task queued → download video → extract audio (FFmpeg) → transcribe (Whisper/Groq/etc) → generate notes (LLM) → frontend polls for completion → display Markdown + mind map.
|
**Core Workflow**: User submits URL → task queued → download video → extract audio (FFmpeg) → transcribe (Whisper/Groq/etc) → generate notes (LLM) → frontend polls for completion → display Markdown + mind map.
|
||||||
@@ -66,6 +75,7 @@ cd BillNote_frontend && pnpm tauri build
|
|||||||
- **Database**: SQLite at `backend/app/db/bili_note.db`, auto-initialized on first run
|
- **Database**: SQLite at `backend/app/db/bili_note.db`, auto-initialized on first run
|
||||||
- **FFmpeg**: Required system dependency for video/audio processing
|
- **FFmpeg**: Required system dependency for video/audio processing
|
||||||
- **Vite proxy**: Dev server proxies `/api` and `/static` to backend (configured in `vite.config.ts`, reads env from parent dir)
|
- **Vite proxy**: Dev server proxies `/api` and `/static` to backend (configured in `vite.config.ts`, reads env from parent dir)
|
||||||
|
- **Distributed mode (optional)**: Setting `NACOS_SERVER_ADDR` enables Nacos worker registration; setting `RABBITMQ_URL` enables MQ feedback. Both are no-ops when unset — single-node deployment works without either. Other knobs: `WORKER_ID`, `WORKER_SELF_URL`, `WORKER_MAX_CONCURRENT`, `TASK_MAX_WORKERS`.
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,19 @@ class ProviderService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def add_provider( name: str, api_key: str, base_url: str, logo: str, type_: str, enabled: int = 1):
|
def add_provider( name: str, api_key: str, base_url: str, logo: str, type_: str, enabled: int = 1):
|
||||||
try:
|
try:
|
||||||
|
# 内置供应商(type='built-in')只能由 seed 流程写入;API 创建一律落到 'custom',
|
||||||
|
# 否则历史上出现过批量伪内置脏数据
|
||||||
|
if type_ != 'custom':
|
||||||
|
type_ = 'custom'
|
||||||
|
existing = get_provider_by_name(name)
|
||||||
|
if existing is not None:
|
||||||
|
raise ValueError(f'供应商名称已存在: {name}')
|
||||||
id = uuid().lower()
|
id = uuid().lower()
|
||||||
logo = 'custom'
|
logo = 'custom'
|
||||||
return insert_provider(id, name, api_key, base_url, logo, type_, enabled)
|
return insert_provider(id, name, api_key, base_url, logo, type_, enabled)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('创建模式失败',e)
|
print('创建模式失败',e)
|
||||||
|
raise
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def provider_to_dict(p: Provider):
|
def provider_to_dict(p: Provider):
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user