Compare commits

...

3 Commits

Author SHA1 Message Date
ydy0615 6102f905ee feat: implement inline autocomplete suggestions with FastAPI backend and Milkdown editor integration 2026-01-18 19:42:58 +08:00
ydy0615 ba49f82953 Add tokenization and context provider API types
- Implemented window delineation tests for indentation-based tokenization.
- Created tokenizer module with various tokenization strategies including TTokenizer and ApproximateTokenizer.
- Added type definitions for authentication parameters and code citation notifications.
- Introduced context provider API for extensions to supply additional context items to Copilot.
- Defined core types and schemas for position and range.
- Established status types for agent status management in IDEs.
2026-01-18 10:24:32 +08:00
ydy0615 55c1b180f7 feat(editor): implement WYSIWYG Markdown editor using Milkdown Crepe
Replace the existing contenteditable-based markdown editor with a full-featured WYSIWYG editor using @milkdown/crepe. The new implementation provides:
- True WYSIWYG editing experience with instant Markdown syntax rendering
- Slash command menu support for quick formatting
- Code block highlighting and image paste support
- Built-in export to markdown file functionality

Changes include new MilkdownEditor component, updated App.vue integration, theme styling imports, and optimized Vite configuration for the new dependencies.
2026-01-18 09:08:38 +08:00
362 changed files with 3162 additions and 155 deletions
+3
View File
@@ -0,0 +1,3 @@
OPENAI_API_KEY=ollama
OLLAMA_BASE_URL=http://192.168.0.120:11434/v1/
OLLAMA_MODEL=gpt-oss:120b
+3
View File
@@ -0,0 +1,3 @@
OPENAI_API_KEY=ollama
OLLAMA_BASE_URL=http://192.168.0.120:11434/v1/
OLLAMA_MODEL=gpt-oss:120b
+44
View File
@@ -0,0 +1,44 @@
import os
from typing import AsyncGenerator
from openai import AsyncOpenAI
import json
api_key = os.getenv('OPENAI_API_KEY', 'ollama')
base_url = os.getenv('OLLAMA_BASE_URL', 'http://192.168.0.120:11434/v1/')
model = os.getenv('OLLAMA_MODEL', 'gpt-oss:120b')
print(f"[LLM] API key configured: {'Yes' if api_key else 'No'}")
print(f"[LLM] Base URL: {base_url}")
print(f"[LLM] Model: {model}")
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
async def stream_openai(prompt: str) -> AsyncGenerator[str, None]:
"""
调用 OpenAI/Ollama API 并流式返回补全内容。
参考 completions-sample-code 的 streaming 逻辑。
"""
print(f"[LLM] Calling API with prompt length: {len(prompt)}")
try:
stream = await client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=True,
max_tokens=128,
temperature=0.2,
)
chunk_count = 0
async for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
chunk_count += 1
print(f"[LLM] Chunk {chunk_count}: {content}")
yield json.dumps({"content": content})
print(f"[LLM] Stream complete, total chunks: {chunk_count}")
except Exception as e:
error_msg = f"Error: {str(e)}"
print(f"[LLM] Error: {error_msg}")
yield json.dumps({"error": str(e)})
+47
View File
@@ -0,0 +1,47 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import os
import json
app = FastAPI()
class CompletionRequest(BaseModel):
prefix: str
suffix: str
languageId: str = 'markdown'
def generate_stream(request: CompletionRequest):
from prompt import build_prompt
from llm import stream_openai
print(f"[Backend] Received request - prefix length: {len(request.prefix)}, suffix length: {len(request.suffix)}")
try:
prompt = build_prompt(request.prefix, request.suffix)
print(f"[Backend] Built prompt (first 100 chars): {prompt[:100]}...")
async def gen():
chunk_count = 0
async for chunk in stream_openai(prompt):
chunk_count += 1
yield f"data: {chunk}\n\n"
if chunk_count % 5 == 0:
print(f"[Backend] Sent chunk {chunk_count}")
yield "data: {\"done\": true}\n\n"
print(f"[Backend] Stream complete, total chunks: {chunk_count}")
return gen()
except Exception as e:
error_msg = f"{{\"error\": \"{str(e)}\"}}"
print(f"[Backend] Error: {e}")
yield f"data: {error_msg}\n\n"
@app.post("/v1/completions")
async def create_completion(request: CompletionRequest):
print(f"[Backend] POST /v1/completions called")
return StreamingResponse(generate_stream(request), media_type="text/event-stream")
if __name__ == "__main__":
import uvicorn
print("[Backend] Starting server on http://0.0.0.0:8000")
uvicorn.run(app, host="0.0.0.0", port=8000)
+28
View File
@@ -0,0 +1,28 @@
import os
from typing import Tuple
def build_prompt(prefix: str, suffix: str) -> str:
"""
构建用于代码补全的 Prompt。
参考 completions-sample-code 的 extractPrompt 逻辑简化实现。
"""
MAX_CONTEXT_LINES = 30
prefix_lines = prefix.split('\n')
suffix_lines = suffix.split('\n') if suffix else []
recent_prefix = '\n'.join(prefix_lines[-MAX_CONTEXT_LINES:])
recent_suffix = '\n'.join(suffix_lines[:5])
prompt = f"""
You are a helpful writing assistant. Continue the text naturally based on the context.
Context (before cursor):
{recent_prefix}
Complete this:
{suffix if suffix else '(cursor here)'}
Continue:"""
return prompt.strip()
+5
View File
@@ -0,0 +1,5 @@
fastapi
uvicorn
openai
pydantic
python-dotenv
@@ -1,13 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
import { basename } from '../../../util/vs/base/common/path';
export class GhostTextContext extends InlineEditRequestLogContext {
override getDebugName(): string {
return `Ghost | ${basename(this.filePath)} (v${this.version})`;
}
}

Some files were not shown because too many files have changed in this diff Show More