新增: AI Chat多项增强(审批去重/编辑重发/导出/实体引用/会话置顶搜索)+任务推进链df-nodes落地

This commit is contained in:
2026-06-16 12:41:13 +08:00
parent 212a927eee
commit 7d5cd4c89a
62 changed files with 4576 additions and 248 deletions

View File

@@ -1047,6 +1047,7 @@ fn ai_conversation_from_row(row: &Row<'_>) -> std::result::Result<AiConversation
model: row.get("model")?,
models: row.get("models")?,
archived: row.get::<_, i32>("archived")? != 0,
pinned: row.get::<_, i32>("pinned")? != 0,
prompt_tokens: row.get("prompt_tokens")?,
completion_tokens: row.get("completion_tokens")?,
created_at: row.get("created_at")?,
@@ -1137,10 +1138,11 @@ impl_repo!(
from_row => |row| ai_conversation_from_row(row),
insert => |conn, rec| {
conn.execute(
"INSERT INTO ai_conversations (id, title, messages, provider_id, model, models, archived, prompt_tokens, completion_tokens, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
"INSERT INTO ai_conversations (id, title, messages, provider_id, model, models, archived, pinned, prompt_tokens, completion_tokens, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
params![
rec.id, rec.title, rec.messages, rec.provider_id, rec.model, rec.models, rec.archived,
if rec.pinned { 1i32 } else { 0i32 },
rec.prompt_tokens, rec.completion_tokens,
rec.created_at, rec.updated_at
],
@@ -1148,9 +1150,10 @@ impl_repo!(
},
update => |conn, rec| {
conn.execute(
"UPDATE ai_conversations SET title = ?1, messages = ?2, provider_id = ?3, model = ?4, models = ?5, archived = ?6, prompt_tokens = ?7, completion_tokens = ?8, updated_at = ?9 WHERE id = ?10",
"UPDATE ai_conversations SET title = ?1, messages = ?2, provider_id = ?3, model = ?4, models = ?5, archived = ?6, pinned = ?7, prompt_tokens = ?8, completion_tokens = ?9, updated_at = ?10 WHERE id = ?11",
params![
rec.title, rec.messages, rec.provider_id, rec.model, rec.models, rec.archived,
if rec.pinned { 1i32 } else { 0i32 },
rec.prompt_tokens, rec.completion_tokens,
rec.updated_at, rec.id
],
@@ -1237,6 +1240,42 @@ impl AiToolExecutionRepo {
.await
.map_err(storage_err)?
}
/// 审批历史面板分页查询:按 requested_at 倒序(最新在前),limit 默认 50。
///
/// 与 list_pending 同理走专用 SELECT,绕过通用 query 宏(后者硬编码
/// ORDER BY created_at,本表无该列)。limit/offset 上限钳制(limit ≤ 200),
/// 防前端恶意/失误传超大值。
pub async fn list_recent(
&self,
limit: u32,
offset: u32,
) -> Result<Vec<AiToolExecutionRecord>> {
let conn = self.conn.clone();
// 钳制 limit 防滥用(默认 50,最大 200)
let safe_limit = limit.min(200) as i64;
let safe_offset = offset as i64;
tokio::task::spawn_blocking(move || {
let guard = conn.blocking_lock();
let mut stmt = guard
.prepare(
"SELECT * FROM ai_tool_executions ORDER BY requested_at DESC LIMIT ?1 OFFSET ?2",
)
.map_err(storage_err)?;
let rows = stmt
.query_map(params![safe_limit, safe_offset], |row| {
ai_tool_execution_from_row(row)
})
.map_err(storage_err)?;
let mut results = Vec::new();
for r in rows {
results.push(r.map_err(storage_err)?);
}
Ok(results)
})
.await
.map_err(storage_err)?
}
}
impl_repo!(
@@ -1611,6 +1650,26 @@ impl AiConversationRepo {
.await
.map_err(storage_err)?
}
/// 设置置顶标记(仅改 pinned,不动 updated_at) — UX-17
///
/// 同 set_archived:置顶是纯元数据标记,不应改变相对时间。前端排序读 pinned DESC, updated_at DESC。
pub async fn set_pinned(&self, id: &str, pinned: bool) -> Result<bool> {
let conn = self.conn.clone();
let id = id.to_owned();
tokio::task::spawn_blocking(move || {
let guard = conn.blocking_lock();
let affected = guard
.execute(
"UPDATE ai_conversations SET pinned = ?1 WHERE id = ?2",
params![if pinned { 1 } else { 0 }, id],
)
.map_err(storage_err)?;
Ok(affected > 0)
})
.await
.map_err(storage_err)?
}
}
// ============================================================