feat: LLM 应用网页开发及内联建议功能实现

This commit is contained in:
2026-04-05 13:42:29 +08:00
parent 9904b9bd78
commit 68ed783d6c
13 changed files with 800 additions and 513 deletions
+12 -316
View File
@@ -2,6 +2,8 @@ from datetime import datetime, timedelta, timezone
import re
from typing import Protocol, Tuple, runtime_checkable
from prompts import get_language_guidance_map, get_system_prompt_template, get_inline_examples
@runtime_checkable
class UserPreferences(Protocol):
@@ -224,339 +226,33 @@ def _canonical_language_id(language_id: str) -> str:
_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.""",
"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 $$.""",
"json": """
Language-specific guidance (json):
- Output strict JSON only (no comments, no trailing commas).
- Ensure valid quotes and braces.""",
"yaml": """
Language-specific guidance (yaml):
- Output valid YAML only.
- Use consistent indentation and avoid tabs.""",
"toml": """
Language-specific guidance (toml):
- Output valid TOML only.
- Keep key types consistent.""",
"ini": """
Language-specific guidance (ini):
- Output valid INI only.
- 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.""",
"bash": """
Language-specific guidance (bash):
- Output POSIX-compatible shell when possible.
- Avoid interactive prompts or destructive commands unless requested.""",
"powershell": """
Language-specific guidance (powershell):
- Output valid PowerShell commands.
- Avoid destructive commands unless explicitly requested.""",
"html": """
Language-specific guidance (html):
- Output valid HTML only.
- Keep markup minimal and well-formed.""",
"css": """
Language-specific guidance (css):
- Output valid CSS only.
- Use concise, readable selectors.""",
"diff": """
Language-specific guidance (diff):
- Output a unified diff only.
- Ensure @@ hunk headers and +/- lines are consistent.""",
"regex": """
Language-specific guidance (regex):
- Output the regex pattern only.
- Avoid delimiters unless explicitly requested.""",
"text": """
Language-specific guidance (text):
- Output plain text only.
- Avoid markdown formatting unless explicitly asked.""",
"xml": """
Language-specific guidance (xml):
- Output well-formed XML only.
- Ensure matching tags and proper escaping.""",
"dockerfile": """
Language-specific guidance (dockerfile):
- Output valid Dockerfile instructions only.
- Keep layers minimal and ordered logically.""",
"makefile": """
Language-specific guidance (makefile):
- Output valid Makefile syntax only.
- 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)
guidance_map = get_language_guidance_map()
guidance = guidance_map.get(canonical)
if guidance:
return guidance
if canonical in _JS_LANGS:
return _JS_CODE.format(lang=canonical)
return guidance_map.get("_js_code", "").replace("{lang}", canonical)
if canonical in _CODE_LANGS:
return _GENERIC_CODE.format(lang=canonical)
return _GENERIC_CODE.format(lang=canonical)
return guidance_map.get("_generic_code", "").replace("{lang}", canonical)
return guidance_map.get("_generic_code", "").replace("{lang}", canonical)
def build_inline_system_prompt(language_id: str = "markdown") -> str:
safe_language_id = _canonical_language_id(language_id)
language_guidance = _language_guidance(safe_language_id)
system_prompt = f"""You are an inline completion engine for a {safe_language_id} editor with ghost-text suggestions.
Return only the insertion text that should be placed between PREFIX and SUFFIX.
CORE PRINCIPLE: Output insertion text only. No explanations, no meta labels, no wrapper quotes.
PRIORITY 1: CONTEXT AWARENESS (Read these flags from user prompt)
- CURSOR_IN_FENCED_CODE_BLOCK: Are you inside a code fence?
- CURSOR_FENCE_LANGUAGE: What language is the current fence?
- PREFIX_ENDS_WITH_NEWLINE: Does prefix end with newline?
- SUFFIX_STARTS_WITH_NEWLINE: Does suffix start with newline?
- MERMAID_CONTEXT: Is this a Mermaid diagram context?
PRIORITY 2: SPECIALIZED CONTENT RULES
2.1 Code Block Handling:
If CURSOR_IN_FENCED_CODE_BLOCK=true:
- You are inside a code fence
- Output code lines ONLY (no triple backticks)
- Use single \\n for code line separation
If CURSOR_IN_FENCED_CODE_BLOCK=false and code needed:
- Wrap code in fenced block with language tag:
```{{language}}
code here
```
- Never use inline backticks for code snippets
2.2 Math Formatting (KaTeX):
- Inline math: wrap with $...$
- Block math: wrap with $$...$$
- Never output bare formulas
- Exception: inside latex/tex/katex fence, output raw LaTeX
2.3 Mermaid Diagrams:
If CURSOR_FENCE_LANGUAGE=mermaid:
- Output Mermaid syntax ONLY
- No backticks, no explanations
If MERMAID_CONTEXT=true and outside fence:
- Output complete fenced block:
```mermaid
diagram syntax
```
PRIORITY 3: MARKDOWN STRUCTURE
3.1 Newline Semantics:
- Single \\n: soft break (same paragraph, renders as space or <br>)
- Double \\n\\n: hard break (new paragraph/block)
- Use \\n\\n for: new paragraphs, before headings, starting lists/tables
- Use \\n for: continuation within blocks (list items, table cells)
- Exception: inside code blocks, use \\n freely for code lines
3.2 Boundary Management:
Check PREFIX_ENDS_WITH_NEWLINE and SUFFIX_STARTS_WITH_NEWLINE:
- If PREFIX lacks needed newline: start OUTPUT with \\n
- If SUFFIX lacks needed newline: end OUTPUT with \\n
- Common cases requiring leading \\n:
* Starting a list after "Steps:"
* Creating new paragraph after text
* Adding heading after paragraph
- Common cases requiring trailing \\n:
* Before new heading
* End of section
3.3 Context Stitching:
- Never repeat text from SUFFIX beginning
- Match PREFIX tone, style, indentation
- Continue structures: lists, tables, quotes, headings
PRIORITY 4: HIDDEN CONTEXT
- OCR metadata like <OCR:...> is hidden context
- Never copy OCR tags to output
- Use OCR content as semantic hint only
"""
template = get_system_prompt_template()
system_prompt = template.replace("{language_id}", safe_language_id)
if language_guidance:
system_prompt = f"{system_prompt.rstrip()}\\n{language_guidance.strip()}"
system_prompt = f"{system_prompt.rstrip()}\n{language_guidance.strip()}"
return system_prompt.strip()
INLINE_EXAMPLES = """=== CATEGORY A: PROSE CONTINUATION ===
[EX01] Simple prose continuation
<PREFIX>The quick brown fox </PREFIX>
<SUFFIX>jumps over the lazy dog.</SUFFIX>
Expected OUTPUT:
moved quietly and then
[EX02] Avoid repeating suffix
<PREFIX>Our launch plan starts with </PREFIX>
<SUFFIX>phase one, followed by phase two.</SUFFIX>
Expected OUTPUT:
careful internal testing before
WRONG: phase one starts with (repeats suffix)
=== CATEGORY B: MARKDOWN STRUCTURES ===
[EX03] Continue checklist
<PREFIX>## TODO
- [ ] Buy milk
- [ ] </PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
Write release notes and share draft with team
[EX04] Start list after header (PREFIX lacks newline)
PREFIX_ENDS_WITH_NEWLINE=false
<PREFIX>Deployment steps:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
- Build artifact
- Deploy service
[EX05] Continue table row
<PREFIX>| Name | Score |
| --- | --- |
| Alice | 92 |
| Bob | </PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
88 |
[EX06] Start new paragraph
<PREFIX>First paragraph ends.</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
Second paragraph starts.
WRONG: Second paragraph starts. (missing leading \\n\\n)
[EX07] Add newline before heading
PREFIX_ENDS_WITH_NEWLINE=false
<PREFIX>End of previous section.</PREFIX>
<SUFFIX>## Next Heading</SUFFIX>
Expected OUTPUT:
WRONG: (would join with heading without separation)
=== CATEGORY C: CODE BLOCKS ===
[EX08] Outside fence: wrap code in fence
CURSOR_IN_FENCED_CODE_BLOCK=false
<PREFIX>Parse this JSON payload in Python:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
```python
import json
data = json.loads(payload)
```
WRONG: import json\\ndata = json.loads(payload) (no fence)
[EX09] Inside fence: output code only
CURSOR_IN_FENCED_CODE_BLOCK=true
<PREFIX>```python
def add(a, b):
return </PREFIX>
<SUFFIX>
```</SUFFIX>
Expected OUTPUT:
a + b
WRONG: ```python\\nreturn a + b\\n``` (duplicate fences)
[EX10] Code inside fence uses single newline
CURSOR_IN_FENCED_CODE_BLOCK=true
<PREFIX>```python
def hello():</PREFIX>
<SUFFIX>
```</SUFFIX>
Expected OUTPUT:
print("Hello")
return True
(Note: single \\n between code lines, no markdown rules)
=== CATEGORY D: MATH ===
[EX11] Inline math
<PREFIX>The derivative of x^2 is </PREFIX>
<SUFFIX>.</SUFFIX>
Expected OUTPUT:
$2x$
WRONG: 2x (bare formula)
[EX12] Block math
<PREFIX>We can write the Gaussian integral as:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
$$
\\int_{-\\infty}^{\\infty} e^{-x^2}\\,dx = \\sqrt{\\pi}
$$
WRONG: \\int... (bare formula without $$)
=== CATEGORY E: MERMAID ===
[EX13] Inside mermaid fence
CURSOR_FENCE_LANGUAGE=mermaid
CURSOR_IN_FENCED_CODE_BLOCK=true
<PREFIX>```mermaid
flowchart TD
A[Start] --> </PREFIX>
<SUFFIX>
```</SUFFIX>
Expected OUTPUT:
B{Valid?}
B -->|Yes| C[Done]
WRONG: ```mermaid\\nB{Valid?}... (duplicate fence)
[EX14] Outside fence with mermaid context
CURSOR_IN_FENCED_CODE_BLOCK=false
MERMAID_CONTEXT=true
<PREFIX>Please provide a simple release pipeline diagram.</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
```mermaid
flowchart LR
Build --> Test --> Deploy
```
=== CATEGORY F: OCR METADATA ===
[EX15] Use OCR as context, never output
<PREFIX>![whiteboard](img.png) <OCR:equation y = mx + b>
The relationship is </PREFIX>
<SUFFIX>.</SUFFIX>
Expected OUTPUT:
$y = mx + b$
WRONG: <OCR:equation y = mx + b> (OCR tag in output)"""
_INLINE_EXAMPLES = get_inline_examples()
def build_completion_prompts(
@@ -636,7 +332,7 @@ Step 3: Choose newline type
- Do not repeat text from SUFFIX beginning
=== EXAMPLES BY CATEGORY ===
{INLINE_EXAMPLES}
{_INLINE_EXAMPLES}
=== NOW COMPLETE THE TASK ===