feat: add MCP/skill tool protocol, tasks/tools pages, chat UX overhaul

Tools page: skill & MCP server management with [[skill:]]/[[mcp:]] protocol (up to 3 rounds). New Tasks and Tools pages. Chat: session snapshots, empty-session reuse, per-message model_id, unified JSON prediction suggestions. Settings: upload size limit, prediction options, model enable/disable. Schema additions with ensure_column migrations. Add FEATURES/ARCHITECTURE docs.
This commit is contained in:
xianrenge
2026-08-16 19:50:41 +08:00
parent bd3f648ccd
commit 10ecf526ec
33 changed files with 6241 additions and 390 deletions
+1 -1
View File
@@ -5,5 +5,5 @@ pub mod types;
pub use error::{EngineError, Result};
pub use manager::EngineManager;
pub use remote::{stream_chat_remote, RemoteConfig};
pub use remote::{chat_remote, stream_chat_remote, RemoteConfig};
pub use types::{ChatMessage, ChatRequest, ChatStreamEvent, EngineConfig, EngineStatus};
+5
View File
@@ -271,6 +271,11 @@ pub(crate) fn sse_text_stream(
.or_else(|| value.get("content").and_then(|v| v.as_str()));
if let Some(text) = content {
yield Ok(ChatStreamEvent::Text(text.to_string()));
} else if let Some(reasoning) = value
.pointer("/choices/0/delta/reasoning_content")
.and_then(|v| v.as_str())
{
yield Ok(ChatStreamEvent::Reasoning(reasoning.to_string()));
}
}
Err(e) => {
+26
View File
@@ -33,6 +33,32 @@ pub async fn stream_chat_remote(
Ok(sse_text_stream(response.bytes_stream()))
}
/// 调用远程 OpenAI 兼容 API 并返回完整的非流式文本。
pub async fn chat_remote(cfg: &RemoteConfig, req: ChatRequest) -> Result<String> {
let base = normalize_base(&cfg.base_url);
let url = format!("{base}/chat/completions");
let client = reqwest::Client::new();
let mut builder = client.post(&url).json(&req);
if let Some(key) = &cfg.api_key {
if !key.trim().is_empty() {
builder = builder.bearer_auth(key);
}
}
let response = builder.send().await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(EngineError::EngineHttp(status, body));
}
let value: serde_json::Value = response.json().await?;
let text = value
.pointer("/choices/0/message/content")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
Ok(text)
}
/// 把用户填写的 base url 归一化为 `<scheme>://<host>/v1` 形式。
fn normalize_base(base: &str) -> String {
let mut s = base.trim().trim_end_matches('/').to_string();
+2
View File
@@ -55,6 +55,8 @@ impl Serialize for ChatMessage {
#[derive(Debug, Clone)]
pub enum ChatStreamEvent {
Text(String),
/// 推理模型(如 Qwen3.5/DeepSeek-R1)的思考过程增量文本
Reasoning(String),
Usage {
prompt_tokens: u32,
completion_tokens: u32,