重构: confirmDialog抽composable+tool_result反向查找+search列限定
- FR-D5 抽 useConfirm 收敛 4 视图(Projects/ProjectDetail/Ideas/Settings)确认弹层重复 - FR-D4 replace_tool_result_content 正向 O(n) 改反向 rposition(审批替换命中最近,调用低频) - FR-D2 search_vector SELECT * 改显式 14 列(消除隐式依赖,字段级精简待单独立项)
This commit is contained in:
@@ -278,8 +278,11 @@ impl ContextManager {
|
|||||||
|
|
||||||
/// 就地替换某条 tool_result 的内容(兼容审批 replace_tool_result)
|
/// 就地替换某条 tool_result 的内容(兼容审批 replace_tool_result)
|
||||||
/// 返回 true 如果找到并替换了
|
/// 返回 true 如果找到并替换了
|
||||||
|
///
|
||||||
|
/// 反向遍历:tool_result 由 append 进入历史,被替换的通常是最近的审批占位,
|
||||||
|
/// 从尾部查找命中即停,避免对长历史做正向 O(n) 累积扫描。
|
||||||
pub fn replace_tool_result_content(&mut self, tool_call_id: &str, new_content: &str) -> bool {
|
pub fn replace_tool_result_content(&mut self, tool_call_id: &str, new_content: &str) -> bool {
|
||||||
let pos = self.messages.iter().position(|t| {
|
let pos = self.messages.iter().rposition(|t| {
|
||||||
matches!(t.message.role, MessageRole::Tool)
|
matches!(t.message.role, MessageRole::Tool)
|
||||||
&& t.message.tool_call_id.as_deref() == Some(tool_call_id)
|
&& t.message.tool_call_id.as_deref() == Some(tool_call_id)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1174,13 +1174,29 @@ impl KnowledgeRepo {
|
|||||||
///
|
///
|
||||||
/// 返回 (记录, 相似度分数)。数据量 <50k 时暴力遍历 <50ms,够用;
|
/// 返回 (记录, 相似度分数)。数据量 <50k 时暴力遍历 <50ms,够用;
|
||||||
/// 更大规模再升 sqlite-vec HNSW(结果不变,只提速)。
|
/// 更大规模再升 sqlite-vec HNSW(结果不变,只提速)。
|
||||||
|
///
|
||||||
|
/// 列限定: 显式列出所需列(与 search/list_by_status/top_used 一致),避免 SELECT *
|
||||||
|
/// 拉到未知新增列;embedding 单独取(不入 KnowledgeRecord)。
|
||||||
|
///
|
||||||
|
/// TODO(性能,低优先): 调用方(hybrid_search→merge_hybrid_results→build_knowledge_context)
|
||||||
|
/// 实际只消费 id/kind/title/content/reuse_count;reasoning(AI 生成大文本)、tags、
|
||||||
|
/// source_project/source_ref 等元字段未被使用却仍随每行读出。真正省 IO 需返回精简结构
|
||||||
|
/// (如 KnowledgeVectorHit { id, kind, title, content, reuse_count })替换返回类型,
|
||||||
|
/// 但这会改变 search_vector 签名与 merge_hybrid_results 调用契约——当前保守不动,
|
||||||
|
/// 待向量检索量级或 reasoning 文本体积成为瓶颈再单独立项。SELECT 列化本身不省字段,
|
||||||
|
/// 仅消除 SELECT * 的隐式依赖与未知列风险。
|
||||||
pub async fn search_vector(&self, query_vec: &[f32], limit: usize) -> Result<Vec<(KnowledgeRecord, f32)>> {
|
pub async fn search_vector(&self, query_vec: &[f32], limit: usize) -> Result<Vec<(KnowledgeRecord, f32)>> {
|
||||||
let conn = self.conn.clone();
|
let conn = self.conn.clone();
|
||||||
let query_vec = query_vec.to_vec();
|
let query_vec = query_vec.to_vec();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let guard = conn.blocking_lock();
|
let guard = conn.blocking_lock();
|
||||||
|
// 显式列: 14 个 KnowledgeRecord 字段 + embedding(余弦计算用)
|
||||||
|
const COLS: &str = "id,kind,title,content,tags,status,confidence,reuse_count,verified,\
|
||||||
|
source_project,source_ref,reasoning,created_at,updated_at,embedding";
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare("SELECT * FROM knowledges WHERE status = 'published' AND embedding IS NOT NULL")
|
.prepare(&format!(
|
||||||
|
"SELECT {COLS} FROM knowledges WHERE status = 'published' AND embedding IS NOT NULL"
|
||||||
|
))
|
||||||
.map_err(|e| Error::Storage(e.to_string()))?;
|
.map_err(|e| Error::Storage(e.to_string()))?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map([], |row| {
|
.query_map([], |row| {
|
||||||
|
|||||||
50
src/composables/useConfirm.ts
Normal file
50
src/composables/useConfirm.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//! 统一确认弹层状态机 — 抽自 Projects/ProjectDetail/Ideas/Settings 四处重复
|
||||||
|
//!
|
||||||
|
//! 背景:原生 window.confirm 在 Tauri webview 带 localhost:port 来源信息无法去除,
|
||||||
|
//! 改为自建弹层。四视图各自重复一份 {visible,msg,resolve} + Promise 机制,此处收敛。
|
||||||
|
//!
|
||||||
|
//! 分工:本 composable 只管「状态 + Promise」,不渲染 UI ——
|
||||||
|
//! - Projects/ProjectDetail/Ideas 复用 ../components/ConfirmDialog.vue
|
||||||
|
//! - Settings 用内联模板(沿用其既有 toast/confirm 同框布局)
|
||||||
|
//! 各视图绑定 confirmState 的 visible/msg 并把按钮点击回 answerConfirm 即可。
|
||||||
|
//!
|
||||||
|
//! i18n:composable 在 setup 内调用,文案由调用方用各自 useI18n().t 翻译后传入 msg,
|
||||||
|
//! 这里无需 i18n 依赖。dangerLabel 由 ConfirmDialog 组件侧兜底为 common.delete。
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export interface ConfirmState {
|
||||||
|
visible: boolean
|
||||||
|
msg: string
|
||||||
|
resolve: null | ((v: boolean) => void)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 弹出确认对话框,返回 Promise;resolve(true) 表示用户确认,resolve(false) 表示取消 */
|
||||||
|
export type ConfirmFn = (msg: string) => Promise<boolean>
|
||||||
|
|
||||||
|
/** 关闭弹层并回传结果 */
|
||||||
|
export type AnswerFn = (ok: boolean) => void
|
||||||
|
|
||||||
|
export function useConfirm() {
|
||||||
|
const confirmState = ref<ConfirmState>({
|
||||||
|
visible: false,
|
||||||
|
msg: '',
|
||||||
|
resolve: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
function confirmDialog(msg: string): Promise<boolean> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
confirmState.value.msg = msg
|
||||||
|
confirmState.value.visible = true
|
||||||
|
confirmState.value.resolve = resolve
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function answerConfirm(ok: boolean) {
|
||||||
|
confirmState.value.visible = false
|
||||||
|
confirmState.value.resolve?.(ok)
|
||||||
|
confirmState.value.resolve = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return { confirmState, confirmDialog, answerConfirm }
|
||||||
|
}
|
||||||
@@ -207,26 +207,15 @@ import { Message } from '@arco-design/web-vue'
|
|||||||
import { useProjectStore } from '../stores/project'
|
import { useProjectStore } from '../stores/project'
|
||||||
import { formatDate } from '../utils/time'
|
import { formatDate } from '../utils/time'
|
||||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||||
|
import { useConfirm } from '../composables/useConfirm'
|
||||||
import type { IdeaRecord } from '../api/types'
|
import type { IdeaRecord } from '../api/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useProjectStore()
|
const store = useProjectStore()
|
||||||
|
|
||||||
// ── 确认弹层(替代原生 window.confirm,后者在 Tauri webview 带 localhost 来源信息无法去除) ──
|
// 确认弹层状态机抽至 composables/useConfirm(原 4 视图重复:Projects/ProjectDetail/Ideas/Settings)
|
||||||
const confirmState = ref({ visible: false, msg: '', resolve: null as null | ((v: boolean) => void) })
|
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||||
function confirmDialog(msg: string): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
confirmState.value.msg = msg
|
|
||||||
confirmState.value.visible = true
|
|
||||||
confirmState.value.resolve = resolve
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function answerConfirm(ok: boolean) {
|
|
||||||
confirmState.value.visible = false
|
|
||||||
confirmState.value.resolve?.(ok)
|
|
||||||
confirmState.value.resolve = null
|
|
||||||
}
|
|
||||||
|
|
||||||
type FilterKey = 'all' | 'hot' | 'pending' | 'promoted'
|
type FilterKey = 'all' | 'hot' | 'pending' | 'promoted'
|
||||||
|
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ import { formatDate } from '../utils/time'
|
|||||||
import { parseStack } from '../utils/project'
|
import { parseStack } from '../utils/project'
|
||||||
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
||||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||||
|
import { useConfirm } from '../composables/useConfirm'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -222,20 +223,8 @@ const logListRef = ref<HTMLElement | null>(null)
|
|||||||
const showApprovalDialog = ref(false)
|
const showApprovalDialog = ref(false)
|
||||||
let unlisten: (() => void) | null = null
|
let unlisten: (() => void) | null = null
|
||||||
|
|
||||||
// ── 确认弹层(替代原生 window.confirm,后者在 Tauri webview 带 localhost 来源信息无法去除) ──
|
// 确认弹层状态机抽至 composables/useConfirm(原 4 视图重复:Projects/ProjectDetail/Ideas/Settings)
|
||||||
const confirmState = ref({ visible: false, msg: '', resolve: null as null | ((v: boolean) => void) })
|
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||||
function confirmDialog(msg: string): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
confirmState.value.msg = msg
|
|
||||||
confirmState.value.visible = true
|
|
||||||
confirmState.value.resolve = resolve
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function answerConfirm(ok: boolean) {
|
|
||||||
confirmState.value.visible = false
|
|
||||||
confirmState.value.resolve?.(ok)
|
|
||||||
confirmState.value.resolve = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 阶段定义(labelKey 对应 i18n projectDetail.stage*) ──
|
// ── 阶段定义(labelKey 对应 i18n projectDetail.stage*) ──
|
||||||
const stages = [
|
const stages = [
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ import { formatDate } from '../utils/time'
|
|||||||
import { parseStack } from '../utils/project'
|
import { parseStack } from '../utils/project'
|
||||||
import { projectStatusLabel as statusLabel, projectBadgeClass as stageClass } from '../constants/project'
|
import { projectStatusLabel as statusLabel, projectBadgeClass as stageClass } from '../constants/project'
|
||||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||||
|
import { useConfirm } from '../composables/useConfirm'
|
||||||
import type { ProjectRecord } from '../api/types'
|
import type { ProjectRecord } from '../api/types'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -133,20 +134,8 @@ const { t } = useI18n()
|
|||||||
// 状态映射统一走 ../constants/project(与 ProjectDetail/Tasks/Dashboard 一致)
|
// 状态映射统一走 ../constants/project(与 ProjectDetail/Tasks/Dashboard 一致)
|
||||||
// formatDate 由 ../utils/time 提供(统一毫秒字符串解析,根治 Invalid Date)
|
// formatDate 由 ../utils/time 提供(统一毫秒字符串解析,根治 Invalid Date)
|
||||||
|
|
||||||
// ── 确认弹层(替代原生 window.confirm,后者在 Tauri webview 带 localhost 来源信息无法去除) ──
|
// 确认弹层状态机抽至 composables/useConfirm(原 4 视图重复:Projects/ProjectDetail/Ideas/Settings)
|
||||||
const confirmState = ref({ visible: false, msg: '', resolve: null as null | ((v: boolean) => void) })
|
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||||
function confirmDialog(msg: string): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
confirmState.value.msg = msg
|
|
||||||
confirmState.value.visible = true
|
|
||||||
confirmState.value.resolve = resolve
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function answerConfirm(ok: boolean) {
|
|
||||||
confirmState.value.visible = false
|
|
||||||
confirmState.value.resolve?.(ok)
|
|
||||||
confirmState.value.resolve = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 新建项目 ──
|
// ── 新建项目 ──
|
||||||
const showCreateModal = ref(false)
|
const showCreateModal = ref(false)
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ import { reactive, ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { aiApi, knowledgeApi } from '../api'
|
import { aiApi, knowledgeApi } from '../api'
|
||||||
import { useAppSettingsStore } from '../stores/appSettings'
|
import { useAppSettingsStore } from '../stores/appSettings'
|
||||||
|
import { useConfirm } from '../composables/useConfirm'
|
||||||
import type { AiProviderConfig } from '../api/types'
|
import type { AiProviderConfig } from '../api/types'
|
||||||
import i18n from '../i18n'
|
import i18n from '../i18n'
|
||||||
|
|
||||||
@@ -399,24 +400,9 @@ function showToast(msg: string, type: 'error' | 'warning' | 'info' = 'info') {
|
|||||||
_toastTimer = setTimeout(() => { toast.visible = false }, 3000)
|
_toastTimer = setTimeout(() => { toast.visible = false }, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自建确认弹层(替代 window.confirm —— 后者带 webview 来源域名/端口,无法去除)
|
// 确认弹层状态机抽至 composables/useConfirm(原 4 视图重复:Projects/ProjectDetail/Ideas/Settings)
|
||||||
const confirmState = reactive({
|
// 模板 confirmState.visible/msg 在 <script setup> 中自动解包 ref,沿用既有内联确认弹层
|
||||||
visible: false,
|
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||||
msg: '',
|
|
||||||
resolve: null as null | ((v: boolean) => void),
|
|
||||||
})
|
|
||||||
function confirmDialog(msg: string): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
confirmState.msg = msg
|
|
||||||
confirmState.visible = true
|
|
||||||
confirmState.resolve = resolve
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function answerConfirm(ok: boolean) {
|
|
||||||
confirmState.visible = false
|
|
||||||
confirmState.resolve?.(ok)
|
|
||||||
confirmState.resolve = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const providerForm = reactive({
|
const providerForm = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user