23 |
24 |
30 |
37 |
38 |
48 |
49 | )
50 | }
--------------------------------------------------------------------------------
/backend/ReAct/tools_server.py:
--------------------------------------------------------------------------------
1 | """本地工具 MCP 服务器
2 |
3 | 将本地工具函数暴露为 MCP (Model Context Protocol) 工具服务。
4 | """
5 |
6 | import sys
7 | import os
8 |
9 | # 添加 backend 目录到路径
10 | backend_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11 | print("backend_path:", backend_path)
12 | sys.path.insert(0, backend_path)
13 | print("sys.path:", sys.path[:3]) # 只打印前3个
14 |
15 | try:
16 | import backend
17 | print("backend imported successfully")
18 | except ImportError as e:
19 | print("import backend failed:", e)
20 |
21 | from fastmcp import FastMCP
22 |
23 | # 导入本地工具
24 | # from tools.calculator import add_numbers # 示例,已移除
25 | from backend.tools.retrieval_tool import retrieval_tool
26 |
27 | # 创建 MCP 服务器
28 | mcp = FastMCP("LocalTools")
29 |
30 |
31 | @mcp.tool()
32 | async def knowledge_retrieval(question: str, transcript_ids) -> str:
33 | """知识库检索工具,从指定转录中检索相关内容。
34 |
35 | 参数:
36 | question: 用户问题,字符串类型,用于描述需要检索的具体内容
37 | transcript_ids: 转录ID,可以是以下格式:
38 | - 单个整数:如 123,表示从单个视频文件中检索
39 | - 整数列表:如 [123, 456, 789],表示从多个视频文件中同时检索
40 | - 字符串格式的列表:如 "[123, 456]",会被自动解析为列表
41 |
42 | 返回:
43 | 压缩后的关键信息字符串,包含检索到的相关内容、时间戳和文件名信息
44 |
45 | 示例:
46 | knowledge_retrieval("什么是机器学习?", [1, 2, 3])
47 | knowledge_retrieval("插件功能介绍", 5)
48 | """
49 | result = await retrieval_tool.retrieve_knowledge(question, transcript_ids)
50 | return result
51 |
52 |
53 | if __name__ == "__main__":
54 | # 启动 MCP 服务器,使用 HTTP 传输
55 | mcp.run(transport="http", port=8004)
--------------------------------------------------------------------------------
/backend/queues/tasks/knowledge_base_stage.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """知识库处理阶段"""
3 |
4 | import logging
5 | from typing import List, Dict, Any, Tuple
6 |
7 | from backend.services.knowledge_base_service import knowledge_base
8 | from backend.schemas import Segment
9 |
10 | logger = logging.getLogger(__name__)
11 |
12 |
13 | def handle_knowledge_base_stage(
14 | job_id: int,
15 | transcript_id: int,
16 | segments: List[Dict[str, Any]]
17 | ) -> None:
18 | """处理知识库添加阶段"""
19 | try:
20 | # 知识库只存 minimal metadata:transcript_id 与 chunk_index(自动添加)
21 | metadata = {"transcript_id": transcript_id}
22 |
23 | knowledge_base.add_transcript(
24 | segments=segments,
25 | metadata=metadata
26 | )
27 | logger.info(f"转写句子段已添加到知识库: job_id={job_id}")
28 | except Exception as e:
29 | logger.error(f"添加转写句子段到知识库失败: {str(e)}")
30 | raise
31 |
32 |
33 | def handle_knowledge_retrieval_stage(
34 | question: str,
35 | transcript_id: int
36 | ) -> Tuple[List[Segment], str]:
37 | """处理知识库检索阶段"""
38 | try:
39 | from backend.services.chat_knowledge_service import ChatKnowledgeService
40 | service = ChatKnowledgeService()
41 | relevant_segments, filename = service._perform_knowledge_retrieval(question, transcript_id)
42 | logger.info(f"知识库检索完成: transcript_id={transcript_id}, 检索到 {len(relevant_segments)} 个片段")
43 | return relevant_segments, filename
44 | except Exception as e:
45 | logger.error(f"知识库检索失败: {str(e)}")
46 | raise
--------------------------------------------------------------------------------
/ASRBackend/Dockerfile.local:
--------------------------------------------------------------------------------
1 | # 本地版本 Dockerfile
2 | # 包含完整的本地模型支持、torch 等大型依赖
3 | # 构建大小:约 3-5GB(根据 torch 版本)
4 |
5 | FROM python:3.10-slim
6 |
7 | WORKDIR /app
8 |
9 | # 设置运行模式为本地
10 | ENV ASR_MODE=local
11 | ENV PYTHONPATH=/app
12 |
13 | # 配置 pip 使用清华源
14 | RUN pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
15 |
16 | # 复制项目文件
17 | COPY . .
18 |
19 | # 配置清华源加速 apt 安装
20 | RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources
21 |
22 | # 安装系统依赖
23 | RUN apt-get update && apt-get install -y --no-install-recommends \
24 | build-essential \
25 | git \
26 | curl \
27 | ffmpeg \
28 | ca-certificates \
29 | && rm -rf /var/lib/apt/lists/*
30 |
31 | # 安装 Python 依赖
32 | RUN pip3 install --upgrade pip
33 |
34 | # 先安装 requirements(使用清华源),再安装大型 PyTorch wheel(指定 PyTorch 官方索引)
35 | RUN pip3 install -r requirements-local.txt
36 |
37 | # 安装带 CUDA 支持的 PyTorch 和 torchaudio,增加超时并使用 no-cache-dir,添加 trusted-host
38 | # 建议在非 GPU 环境下使用 "+cpu" 版本以减小下载体积。如需 GPU,保留 cu118,并可考虑 pin 具体版本。
39 | RUN pip3 install --no-cache-dir --prefer-binary \
40 | torch torchvision torchaudio \
41 | --index-url https://download.pytorch.org/whl/cu118 \
42 | --trusted-host download.pytorch.org
43 |
44 | # 创建必要的目录
45 | RUN mkdir -p cache
46 |
47 | # 暴露端口
48 | EXPOSE 8003
49 |
50 | # 健康检查
51 | HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
52 | CMD curl -f http://localhost:8003/health || exit 1
53 |
54 | # 启动应用
55 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8003"]
--------------------------------------------------------------------------------
/backend/routers/chat/summarize_router.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """总结相关的路由"""
3 |
4 | import os
5 | from fastapi import APIRouter, HTTPException, Request
6 |
7 | from backend.text_process.summarize import summarize_segments
8 | from .models import SummarizeRequest, SummarizeResponse
9 |
10 |
11 | router = APIRouter()
12 |
13 |
14 | @router.post("/summarize")
15 | def api_summarize(payload: SummarizeRequest, request: Request) -> SummarizeResponse:
16 | """基于句级片段一次性生成总结。
17 |
18 | 请求 body 字段:
19 | - segments: List[Segment] (必需)
20 | - api_key/base_url/model: 可选(若未提供则从 config 或环境变量读取)
21 |
22 | 返回:{"summaries": List[SummaryItem]}
23 | """
24 | segments = payload.get("segments")
25 | if not segments or not isinstance(segments, list):
26 | raise HTTPException(status_code=400, detail="segments (list) is required")
27 |
28 | # 从配置或环境读取 CHAT_MAX_WINDOWS(优先级:环境变量 -> 默认 1000000)
29 | chat_max = int(
30 | os.environ.get("CHAT_MAX_WINDOWS") or 1000000
31 | )
32 |
33 | try:
34 | summaries = summarize_segments(
35 | segments=segments,
36 | chat_max_windows=chat_max,
37 | max_tokens=4096,
38 | )
39 | except ValueError as e:
40 | # 例如 token 超限等可预期的错误,返回 400
41 | raise HTTPException(status_code=400, detail=str(e))
42 | except Exception as e:
43 | # 其他未知错误返回 500
44 | raise HTTPException(status_code=500, detail=f"summarization failed: {e}")
45 |
46 | # 返回直接的 list[SummaryItem] 以简化前端处理
47 | return {"summaries": summaries}
--------------------------------------------------------------------------------
/backend/services/embedding_litellm_example.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """litellm embedding 示例测试"""
3 |
4 | import sys
5 | import os
6 | import asyncio
7 |
8 | # 添加项目根目录到 sys.path
9 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
10 |
11 | # 加载环境变量
12 | from dotenv import load_dotenv
13 | load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env'))
14 |
15 | import litellm
16 | from litellm import Router
17 | from backend.config import settings
18 | from backend.startup import get_embedding_router
19 |
20 | async def test_litellm_embedding():
21 | """测试 litellm embedding 调用"""
22 |
23 | router = get_embedding_router()
24 |
25 | input_text = "请总结这个视频的内容"
26 |
27 | print("=== 测试 litellm Router embedding 调用 ===")
28 | print(f"模型: {settings.embedding_model}")
29 | print(f"base_url: {settings.embedding_provider_base_url}")
30 |
31 | try:
32 | response = await router.aembedding(
33 | model=settings.embedding_model,
34 | input=input_text
35 | )
36 |
37 | print("embedding 响应成功:")
38 | print(f"response type: {type(response)}")
39 | print(f"data type: {type(response.data)}")
40 | print(f"data[0] type: {type(response.data[0])}")
41 | embedding_vector = response.data[0]['embedding']
42 | print(f"向量维度: {len(embedding_vector)}")
43 | print(f"前5个值: {embedding_vector[:5]}")
44 |
45 | except Exception as e:
46 | print(f"调用失败: {e}")
47 |
48 | if __name__ == "__main__":
49 | asyncio.run(test_litellm_embedding())
50 |
--------------------------------------------------------------------------------
/backend/text_process/docs/ChatGPT翻译技巧说明.md:
--------------------------------------------------------------------------------
1 | # ChatGPT翻译技巧说明
2 |
3 | ## 概述
4 |
5 | 本文会介绍一种使用ChatGPT进行高质量翻译的方法,特别适用于长文翻译,以避免信息丢失和不自然的问题。核心是“直译 + 意译”的两步翻译法,借鉴了“Step-by-Step”提示词理念。
6 |
7 | ## 基本理念
8 |
9 | 这个方法类似于让ChatGPT一步步思考,通过分步处理大幅改善翻译结果。研究显示,在提示词中加入“让我们一步一步的思考”能提升生成质量。
10 |
11 | ## 两步翻译法(适用于GPT-4)
12 |
13 | ### 提示词示例
14 |
15 | 你是一位精通简体中文的专业翻译,曾参与《纽约时报》和《经济学人》中文版的翻译工作,因此对于新闻和时事文章的翻译有深入的理解。我希望你能帮我将以下英文新闻段落翻译成中文,风格与上述杂志的中文版相似。
16 |
17 | 规则:
18 |
19 | - 翻译时要准确传达新闻事实和背景。
20 | - 保留特定的英文术语或名字,并在其前后加上空格,例如:"中 UN 文"。
21 | - 分成两次翻译,并且打印每一次结果:
22 |
23 | 1. 根据新闻内容直译,不要遗漏任何信息
24 | 2. 根据第一次直译的结果重新意译,遵守原意的前提下让内容更通俗易懂,符合中文表达习惯
25 |
26 | 本条消息只需要回复OK,接下来的消息我将会给你发送完整内容,收到后请按照上面的规则打印两次翻译结果。
27 |
28 | ### 效果对比
29 |
30 | 优化前:直接翻译,可能丢失信息或不自然。
31 | 优化后:通过直译+意译,翻译更准确、自然。
32 |
33 | ## 两步翻译法(适用于GPT-3.5)
34 |
35 | 由于GPT-3.5能力较弱,需拆分成两步执行。
36 |
37 | ### 第一步:直译提示词
38 |
39 | 你是一位精通简体中文的专业翻译,曾参与《纽约时报》和《经济学人》中文版的翻译工作,因此对于新闻和时事文章的翻译有深入的理解。我希望你能帮我将以下英文新闻段落翻译成中文,风格与上述杂志的中文版相似。
40 |
41 | 规则:
42 |
43 | - 翻译时要准确传达新闻事实和背景。
44 | - 保留特定的英文术语或名字,并在其前后加上空格,例如:"中 UN 文"。
45 | - 根据新闻内容直译,不要遗漏任何信息。
46 |
47 | 英文原文:
48 | { 英文原文 }
49 |
50 | 直译结果:
51 |
52 | ### 第二步:意译提示词
53 |
54 | 你是一位专业中文翻译,擅长对翻译结果进行二次修改和润色成通俗易懂的中文,我希望你能帮我将以下英文视频的中文翻译结果重新意译和润色。
55 |
56 | 规则:
57 |
58 | - 这些字幕包含机器学习或AI等专业知识相关,注意翻译时术语的准确性
59 | - 保留特定的英文术语、数字或名字,并在其前后加上空格,例如:"生成式 AI 产品","不超过 10 秒"。
60 | - 基于直译结果重新意译,意译时务必对照原始英文,不要添加也不要遗漏内容,并以让翻译结果通俗易懂,符合中文表达习惯
61 |
62 | 英文原文:
63 | { 英文原文 }
64 |
65 | 直译结果:
66 | { 第一直译的结果 }
67 |
68 | 意译和润色后:
69 |
70 | ## 总结
71 |
72 | 这种方法能有效提升翻译质量,尤其在处理复杂内容时。通过分步提示词,我们能让模型更可靠地输出结果。虽然GPT-3.5需额外步骤,但效果依然不错。
73 |
--------------------------------------------------------------------------------
/backend/tests/test_summarize.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 最小可运行示例:
4 | - 构造少量句级段落(不含 spk_id),调用 summarize_once 并打印结果;
5 | - 读取 config.yaml 中的 chat.{api_key, base_url, model} 作为调用所需配置;
6 | - 若缺少必要配置,则直接打印提示并退出(不做 try/except)。
7 |
8 | 遵循项目约定:
9 | - 交流与注释中文;
10 | - 直接调用,避免 argparse/unittest;
11 | - 最小测试原则。
12 | """
13 | from __future__ import annotations
14 |
15 | import json
16 | import os
17 | import sys
18 | from typing import List, Dict, Any
19 |
20 | # 兼容在 backend/tests 目录下直接运行:将项目根目录加入 sys.path
21 | _THIS_DIR = os.path.dirname(__file__)
22 | _PROJECT_ROOT = os.path.abspath(os.path.join(_THIS_DIR, "..", ".."))
23 | if _PROJECT_ROOT not in sys.path:
24 | sys.path.insert(0, _PROJECT_ROOT)
25 |
26 | from backend.services.summarize_service import summarize_once # noqa: E402
27 |
28 |
29 | def run() -> None:
30 | # 构造最小段落列表
31 | segments: List[Dict[str, Any]] = [
32 | {"index": 1, "sentence": "你好,世界!这是一次最小总结测试。", "start_time": 0.0, "end_time": 1.2},
33 | {"index": 2, "sentence": "我们希望输出主题与简要总结。", "start_time": 1.2, "end_time": 2.8},
34 | ]
35 |
36 | # 执行一次总结
37 | items = summarize_once(segments)
38 |
39 | # 输出与保存
40 | print("summarize result:")
41 | print(json.dumps(items, ensure_ascii=False, indent=2))
42 |
43 | out_dir = os.path.join(_THIS_DIR, "results")
44 | os.makedirs(out_dir, exist_ok=True)
45 | out_path = os.path.join(out_dir, "summarize_result.json")
46 | with open(out_path, "w", encoding="utf-8") as f:
47 | json.dump(items, f, ensure_ascii=False, indent=2)
48 | print("保存路径:", out_path)
49 |
50 |
51 | if __name__ == "__main__":
52 | run()
53 |
--------------------------------------------------------------------------------
/backend/media_processing/video/download/youtube/example.py:
--------------------------------------------------------------------------------
1 | """
2 | YouTube视频下载示例脚本
3 | 使用youtube_downloader模块下载指定的YouTube视频
4 | """
5 |
6 | import os
7 | import sys
8 |
9 | # 添加backend到路径,确保绝对导入正常工作
10 | backend_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
11 | project_root = os.path.dirname(backend_dir)
12 | if project_root not in sys.path:
13 | sys.path.insert(0, project_root)
14 |
15 | try:
16 | from backend.media_processing.video.download.youtube.youtube_downloader import download_youtube_video
17 | except ImportError:
18 | try:
19 | from .youtube_downloader import download_youtube_video
20 | except ImportError:
21 | raise ImportError("无法导入必要的模块,请检查项目结构")
22 |
23 | def progress_callback(progress_info):
24 | """
25 | 进度回调函数
26 | """
27 | status = progress_info.get('status', 'unknown')
28 | progress_percent = progress_info.get('progress_percent', 0)
29 | print(f"下载状态: {status}, 进度: {progress_percent:.2f}%")
30 |
31 | if __name__ == "__main__":
32 | # 测试URL
33 | test_url = "https://www.youtube.com/watch?v=A6ZgS0vGsl8"
34 |
35 | print("开始下载YouTube视频...")
36 | result = download_youtube_video(test_url, progress_callback=progress_callback)
37 |
38 | if result.success:
39 | print("下载成功!")
40 | print(f"标题: {result.title}")
41 | print(f"视频路径: {result.video_path}")
42 | print(f"音频路径: {result.audio_path}")
43 | print(f"时长: {result.duration}秒")
44 | print(f"媒体类型: {result.media_type}")
45 | else:
46 | print(f"下载失败: {result.error_message}")
47 |
--------------------------------------------------------------------------------
/frontend/src/components/RightPanel/Chat/MessageInput.tsx:
--------------------------------------------------------------------------------
1 | import type { KeyboardEvent } from "react"
2 | import { Button } from "@/components/ui/button"
3 | import { Textarea } from "@/components/ui/textarea"
4 | import { Send, Loader2 } from "lucide-react"
5 |
6 | interface MessageInputProps {
7 | readonly inputValue: string
8 | readonly loading: boolean
9 | readonly disabled: boolean
10 | readonly onInputChange: (value: string) => void
11 | readonly onSend: () => void
12 | }
13 |
14 | export default function MessageInput({
15 | inputValue,
16 | loading,
17 | disabled,
18 | onInputChange,
19 | onSend,
20 | }: MessageInputProps) {
21 | const handleKeyPress = (e: KeyboardEvent