mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-06-15 20:50:43 +08:00
feat(extension): 浏览器插件 P1 MVP
新建 BillNote_extension/ 工作空间(基于 vitesse-webext 骨架,Vue 3 + Vite + UnoCSS + MV3)。 P1 MVP 范围: - popup:自动读当前 tab URL,识别 Bilibili / YouTube / 抖音 / 快手;提交 /generate_note 后轮询 /task_status;展示 markdown,复制 + 下载 .md - options:后端地址输入与连通性测试;从 /get_all_providers + /get_models_by_provider 拉供应商/模型列表;默认画质、截图/跳转、笔记风格 - chrome.storage.local 持久化设置与最近 30 个任务,popup 重开恢复进行中任务 - markdown 里的 /static/screenshots 路径在渲染前重写为绝对地址 后端:CORS 改用 regex,新增允许 chrome-extension:// 与 moz-extension:// 源(同时保留 localhost / 127.0.0.1 / tauri.localhost)。无新增 backend endpoint。 P2-P4(content script 悬浮按钮、cookie 直通、side panel、思维导图、RAG 问答)保留 stub 文件,不在本次范围。 去掉 vitesse-webext 自带的 simple-git-hooks postinstall 配置——它会在仓库根装 pre-commit 钩子去跑 pnpm lint-staged,但仓库根没有 package.json,会破坏所有提交流。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
BillNote_extension/src/logic/api.ts
Normal file
69
BillNote_extension/src/logic/api.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { GenerateRequest, Model, Provider, TaskStatusResponse } from './types'
|
||||
import { settings } from './storage'
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
code: number
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
|
||||
function backendUrl(): string {
|
||||
return (settings.value?.backendUrl || 'http://localhost:8483').replace(/\/$/, '')
|
||||
}
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${backendUrl()}${path}`, {
|
||||
headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) },
|
||||
...init,
|
||||
})
|
||||
if (!res.ok)
|
||||
throw new Error(`HTTP ${res.status}: ${await res.text()}`)
|
||||
const body = (await res.json()) as ApiEnvelope<T> | T
|
||||
// 后端 ResponseWrapper 包了 {code, msg, data};非 0 视为业务错
|
||||
if (body && typeof body === 'object' && 'code' in body) {
|
||||
const env = body as ApiEnvelope<T>
|
||||
if (env.code !== 0)
|
||||
throw new Error(env.msg || '后端返回失败')
|
||||
return env.data
|
||||
}
|
||||
return body as T
|
||||
}
|
||||
|
||||
export async function getProviders(): Promise<Provider[]> {
|
||||
return request<Provider[]>('/api/get_all_providers')
|
||||
}
|
||||
|
||||
export async function getModelsByProvider(providerId: string): Promise<Model[]> {
|
||||
return request<Model[]>(`/api/get_models_by_provider/${providerId}`)
|
||||
}
|
||||
|
||||
export async function generateNote(payload: GenerateRequest): Promise<{ task_id: string }> {
|
||||
return request<{ task_id: string }>('/api/generate_note', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTaskStatus(taskId: string): Promise<TaskStatusResponse> {
|
||||
// /task_status/{id} 返回的是裸对象(非 ResponseWrapper 包装),见 routers/note.py
|
||||
const res = await fetch(`${backendUrl()}/api/task_status/${taskId}`)
|
||||
if (!res.ok)
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
return (await res.json()) as TaskStatusResponse
|
||||
}
|
||||
|
||||
export async function ping(): Promise<boolean> {
|
||||
try {
|
||||
await getProviders()
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// markdown 里的 /static/screenshots/xxx 是相对路径,extension 渲染时需要拼绝对地址
|
||||
export function absolutizeMarkdownImages(md: string): string {
|
||||
const base = backendUrl()
|
||||
return md.replace(/!\[([^\]]*)\]\((\/static\/[^)]+)\)/g, (_, alt, path) => ``)
|
||||
}
|
||||
Reference in New Issue
Block a user