diff --git a/.gitignore b/.gitignore index c013eeb..48f8d93 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ env/ # IDE directories .kilocode/ +.kilo/ .codex/ \ No newline at end of file diff --git a/backend/llm.py b/backend/llm.py index 3176261..ab25379 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -63,10 +63,10 @@ def _extract_message(response) -> tuple[str, str]: async def call_ollama( prompt: str, *, - system_prompt: str = None, + system_prompt: str | None = None, tag: str = "default", temperature: float = 0.7, - thinking: str = None, + thinking: str | None = None, ) -> dict: """ 调用 Ollama API 并返回 content 和 thinking。 diff --git a/backend/main.py b/backend/main.py index f3bded9..3235d6e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,6 +1,5 @@ import asyncio import base64 -import json import logging import os import re @@ -12,7 +11,7 @@ from typing import Optional from fastapi import FastAPI, HTTPException, Request, Security, File, UploadFile from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, StreamingResponse, Response +from fastapi.responses import JSONResponse, Response from fastapi.security import APIKeyHeader from pydantic import BaseModel @@ -125,10 +124,6 @@ def _sanitize_converted_markdown(text: str) -> str: return value.strip() -def _sse_payload(payload: dict) -> str: - return f"data: {json.dumps(payload)}\n\n" - - def get_client_ip(request: Request) -> str: if request.client: return request.headers.get("X-Client-IP") or request.client.host @@ -186,11 +181,10 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s ) ) - async with ACTIVE_COMPLETIONS_LOCK: - existing = ACTIVE_COMPLETIONS.get(request_id) - if existing and not existing.done(): - existing.cancel() - ACTIVE_COMPLETIONS[request_id] = inference_task + existing = ACTIVE_COMPLETIONS.get(request_id) + if existing and not existing.done(): + existing.cancel() + ACTIVE_COMPLETIONS[request_id] = inference_task result = await inference_task content = result["content"] or "" @@ -204,26 +198,17 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s _preview(content, 120), ) - async def generate(): - yield _sse_payload({"content": content}) - yield _sse_payload({"done": True}) - - return StreamingResponse(generate(), media_type="text/event-stream") + return JSONResponse(content={"content": content, "request_id": request_id}) except asyncio.CancelledError: logger.info("[%s] /v1/completions cancelled request_id=%s", request_tag, request_id) - - async def cancelled(): - yield _sse_payload({"cancelled": True, "request_id": request_id, "done": True}) - - return StreamingResponse(cancelled(), media_type="text/event-stream") + return JSONResponse(content={"cancelled": True, "request_id": request_id}, status_code=499) except Exception as e: logger.exception("[%s] /v1/completions failed request_id=%s: %s", request_tag, request_id, e) return JSONResponse(content={"error": str(e)}, status_code=500) finally: - async with ACTIVE_COMPLETIONS_LOCK: - active = ACTIVE_COMPLETIONS.get(request_id) - if active is not None and active is inference_task: - ACTIVE_COMPLETIONS.pop(request_id, None) + active = ACTIVE_COMPLETIONS.get(request_id) + if active is not None and active is inference_task: + ACTIVE_COMPLETIONS.pop(request_id, None) @app.post("/v1/completions/cancel") @@ -399,7 +384,10 @@ if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8001) -# TTS and ASR routes -from tts_asr import register_tts_asr_routes -register_tts_asr_routes(app) +# TTS and ASR routes (lazy loaded to avoid heavy import on startup) +def _register_tts_asr_routes(): + from tts_asr import register_tts_asr_routes + register_tts_asr_routes(app) + +_register_tts_asr_routes() diff --git a/backend/prompt.py b/backend/prompt.py index fb8b0fa..ac56694 100644 --- a/backend/prompt.py +++ b/backend/prompt.py @@ -1,6 +1,13 @@ from datetime import datetime, timedelta, timezone import re -from typing import Tuple +from typing import Protocol, Tuple, runtime_checkable + + +@runtime_checkable +class UserPreferences(Protocol): + language: str + currency: str + timezone: str def _get_current_datetime(timezone_pref: str = "auto") -> str: @@ -214,113 +221,107 @@ def _canonical_language_id(language_id: str) -> str: return LANGUAGE_SYNONYMS.get(safe, safe) -def _language_guidance(language_id: str) -> str: - canonical = _canonical_language_id(language_id) - if canonical == "markdown": - return "" - if canonical == "mermaid": - return """ +_JS_LANGS = {"javascript", "typescript"} +_CODE_LANGS = {"python", "go", "rust", "java", "kotlin", "swift", "ruby", "php", "lua", "c", "cpp", "csharp", "r", "matlab", "dart"} + +_LANG_GUIDANCE = { + "mermaid": """ Language-specific guidance (mermaid): - Output valid Mermaid syntax only. - Prefer concise, syntactically correct diagram statements. -- Avoid prose unless the user prompt explicitly requires it.""" - if canonical == "latex": - return """ +- Avoid prose unless the user prompt explicitly requires it.""", + "latex": """ Language-specific guidance (latex): - Output LaTeX math content only when completing LaTeX. - If CURSOR_IN_FENCED_CODE_BLOCK=true and CURSOR_FENCE_LANGUAGE is latex/tex/katex: - Output raw LaTeX lines only. - - Do not wrap with $ or $$.""" - if canonical == "json": - return """ + - Do not wrap with $ or $$.""", + "json": """ Language-specific guidance (json): - Output strict JSON only (no comments, no trailing commas). -- Ensure valid quotes and braces.""" - if canonical == "yaml": - return """ +- Ensure valid quotes and braces.""", + "yaml": """ Language-specific guidance (yaml): - Output valid YAML only. -- Use consistent indentation and avoid tabs.""" - if canonical == "toml": - return """ +- Use consistent indentation and avoid tabs.""", + "toml": """ Language-specific guidance (toml): - Output valid TOML only. -- Keep key types consistent.""" - if canonical == "ini": - return """ +- Keep key types consistent.""", + "ini": """ Language-specific guidance (ini): - Output valid INI only. -- Keep section headers and key=value pairs consistent.""" - if canonical == "sql": - return """ +- Keep section headers and key=value pairs consistent.""", + "sql": """ Language-specific guidance (sql): - Output a single, valid SQL statement unless context requires multiple. -- Prefer ANSI SQL when dialect is unclear.""" - if canonical == "bash": - return """ +- Prefer ANSI SQL when dialect is unclear.""", + "bash": """ Language-specific guidance (bash): - Output POSIX-compatible shell when possible. -- Avoid interactive prompts or destructive commands unless requested.""" - if canonical == "powershell": - return """ +- Avoid interactive prompts or destructive commands unless requested.""", + "powershell": """ Language-specific guidance (powershell): - Output valid PowerShell commands. -- Avoid destructive commands unless explicitly requested.""" - if canonical == "html": - return """ +- Avoid destructive commands unless explicitly requested.""", + "html": """ Language-specific guidance (html): - Output valid HTML only. -- Keep markup minimal and well-formed.""" - if canonical == "css": - return """ +- Keep markup minimal and well-formed.""", + "css": """ Language-specific guidance (css): - Output valid CSS only. -- Use concise, readable selectors.""" - if canonical == "diff": - return """ +- Use concise, readable selectors.""", + "diff": """ Language-specific guidance (diff): - Output a unified diff only. -- Ensure @@ hunk headers and +/- lines are consistent.""" - if canonical == "regex": - return """ +- Ensure @@ hunk headers and +/- lines are consistent.""", + "regex": """ Language-specific guidance (regex): - Output the regex pattern only. -- Avoid delimiters unless explicitly requested.""" - if canonical in {"javascript", "typescript"}: - return f""" -Language-specific guidance ({canonical}): -- Output valid {canonical} code. -- Prefer modern syntax and avoid prose unless comments are needed.""" - if canonical in {"python", "go", "rust", "java", "kotlin", "swift", "ruby", "php", "lua", "c", "cpp", "csharp", "r", "matlab", "dart"}: - return f""" -Language-specific guidance ({canonical}): -- Output valid {canonical} code. -- Avoid prose unless context clearly expects comments or docstrings.""" - if canonical == "text": - return """ +- Avoid delimiters unless explicitly requested.""", + "text": """ Language-specific guidance (text): - Output plain text only. -- Avoid markdown formatting unless explicitly asked.""" - if canonical == "xml": - return """ +- Avoid markdown formatting unless explicitly asked.""", + "xml": """ Language-specific guidance (xml): - Output well-formed XML only. -- Ensure matching tags and proper escaping.""" - if canonical == "dockerfile": - return """ +- Ensure matching tags and proper escaping.""", + "dockerfile": """ Language-specific guidance (dockerfile): - Output valid Dockerfile instructions only. -- Keep layers minimal and ordered logically.""" - if canonical == "makefile": - return """ +- Keep layers minimal and ordered logically.""", + "makefile": """ Language-specific guidance (makefile): - Output valid Makefile syntax only. -- Use tabs for recipe lines.""" - return f""" -Language-specific guidance ({canonical}): -- Output valid {canonical} code. +- Use tabs for recipe lines.""", +} + +_GENERIC_CODE = """ +Language-specific guidance ({lang}): +- Output valid {lang} code. - Avoid prose unless context clearly expects comments or docstrings.""" +_JS_CODE = """ +Language-specific guidance ({lang}): +- Output valid {lang} code. +- Prefer modern syntax and avoid prose unless comments are needed.""" + + +def _language_guidance(language_id: str) -> str: + canonical = _canonical_language_id(language_id) + if canonical == "markdown": + return "" + guidance = _LANG_GUIDANCE.get(canonical) + if guidance: + return guidance + if canonical in _JS_LANGS: + return _JS_CODE.format(lang=canonical) + if canonical in _CODE_LANGS: + return _GENERIC_CODE.format(lang=canonical) + return _GENERIC_CODE.format(lang=canonical) + def build_inline_system_prompt(language_id: str = "markdown") -> str: safe_language_id = _canonical_language_id(language_id) @@ -520,7 +521,7 @@ def build_completion_prompts( language_id: str = "markdown", location: str = "", thinking_level: str = "low", - preferences: object = None, + preferences: UserPreferences | None = None, ) -> Tuple[str, str]: safe_language_id = _canonical_language_id(language_id) recent_prefix, recent_suffix = _prepare_context(prefix, suffix) @@ -601,7 +602,7 @@ def build_prompt( language_id: str = "markdown", location: str = "", thinking_level: str = "low", - preferences: object = None, + preferences: UserPreferences | None = None, ) -> str: """ Backward-compatible helper. Returns only the user prompt body. diff --git a/backend/tts_asr.py b/backend/tts_asr.py index 0acc06f..714e9fc 100644 --- a/backend/tts_asr.py +++ b/backend/tts_asr.py @@ -197,8 +197,9 @@ class ASRResponse(BaseModel): def get_api_key(api_key: str): - from backend.main import API_KEY + import main + API_KEY = main.API_KEY if api_key != API_KEY: raise HTTPException(status_code=403, detail="API Key 无效") return api_key diff --git a/src/components/DocBlockCrepe.vue b/src/components/DocBlockCrepe.vue index e1e7622..9075c43 100644 --- a/src/components/DocBlockCrepe.vue +++ b/src/components/DocBlockCrepe.vue @@ -1,38 +1,13 @@ + diff --git a/src/components/DocumentBlock.vue b/src/components/DocumentBlock.vue index 501eaa4..0f48e1f 100644 --- a/src/components/DocumentBlock.vue +++ b/src/components/DocumentBlock.vue @@ -107,78 +107,76 @@ const downloadDoc = () => {