优化: 代码质量收尾(搜索索引补全+编译警告清理+i18n核验+String替newtype)

This commit is contained in:
2026-07-01 12:11:35 +08:00
parent 0338210ba2
commit 6771d396f0
15 changed files with 242 additions and 36 deletions

View File

@@ -75,7 +75,7 @@ impl PlanExecutor {
); );
// 层内并行JoinSet 收集 // 层内并行JoinSet 收集
let mut join_set: tokio::task::JoinSet<SubTaskResult> = tokio::task::JoinSet::new(); let _join_set: tokio::task::JoinSet<SubTaskResult> = tokio::task::JoinSet::new();
let summary_clone = parent_summary.clone(); let summary_clone = parent_summary.clone();
for task in layer { for task in layer {

View File

@@ -4,7 +4,7 @@
use std::sync::Arc; use std::sync::Arc;
use rusqlite::{params, OptionalExtension, Row}; use rusqlite::{params, Row};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::db::Database; use crate::db::Database;

188
docs/Batch-Overview.md Normal file
View File

@@ -0,0 +1,188 @@
# DevFlow 批次可视化总览
> 基于 [Batch.md](../Batch.md) 生成 | 更新: 2026-07-01
---
## 一、批次时间线(已完成)
```mermaid
gantt
title DevFlow 批次推进时间线
dateFormat YYYY-MM-DD
axisFormat %m-%d
section 审批与工具卡
Batch 1 审批超时后端兜底 :done, b1, 2026-06-14, 1d
Batch 2 工具卡片拆分核验 :done, b2, after b1, 1d
Batch 3 条件表达式 UI 翻译 :done, b3, after b2, 1d
section 数据与状态
Batch 4 知识提炼切读消息表 :done, b4, after b3, 1d
Batch 5 关闭已完成待办项 :done, b5, after b4, 1d
Batch 6 应用状态拆分+路径授权 :done, b6, after b5, 1d
section 跨端与消息
Batch 7 小程序配置持久化 :done, b7, after b6, 1d
Batch 8 消息分页懒加载 :done, b8, after b7, 1d
Batch 9 多工程数据层 :done, b9, after b8, 1d
section 文件浏览器
Batch 10 文件浏览器 UI :done, b10, after b9, 1d
Batch 11 Git 只读 AI 工具 :done, b11, after b10, 1d
Batch 12 Git 写操作 AI 工具 :done, b12, after b11, 1d
Batch 13 文件浏览器增强 :done, b13, after b12, 1d
section 收尾与重构
Batch 14 浏览器收尾 :done, b14, after b13, 1d
Batch 15 多工程管理 :done, b15, after b14, 1d
Batch 16 提交历史作者 :done, b16, after b15, 1d
section 崩溃修复与 UX
渲染崩溃修复 TDZ+窗口权限 :done, fix1, after b16, 1d
Batch 17 Git 能力闭环 :done, b17, after fix1, 1d
Batch 18 小程序审批超时 :done, b18, after b17, 1d
section UX 与架构
Batch 19 任务管理 UX 重构 :done, b19, after b18, 1d
Batch 20 coordinator.rs 删除 :done, b20, after b19, 1d
任务快捷操作菜单 :done, menu1, after b20, 1d
section DAG 与依赖图
TDZ 防御 :done, tdz1, after menu1, 1d
Plan 执行器骨架 :done, plan1, after tdz1, 1d
Batch 25 工程依赖图数据层 :done, b25, after plan1, 1d
section 增强
Batch 26 dagre 布局 :done, b26, after b25, 1d
Batch 28 架构债收尾 :done, b28, after b26, 1d
Batch 29 环检测+PNG+总数 :done, b29, after b28, 1d
section 待推进
Batch 30 代码质量收尾 :b30, after b29, 3d
Batch 31 MessageList 拆分 :b31, after b30, 3d
Batch 32 ChatInput 拆分 :b32, after b31, 3d
Batch 33 多 ReAct 对接 :b33, after b32, 5d
Batch 34 产品完善 :b34, after b33, 3d
```
---
## 二、功能领域分布
```mermaid
pie title 批次功能领域分布(按完成批次数)
"文件浏览器/Git" : 8
"任务管理 UX" : 3
"工程系统" : 4
"AI/Agent" : 4
"架构债/重构" : 4
"小程序" : 2
"Bug 修复" : 3
"依赖图可视化" : 4
```
---
## 三、批次依赖关系图
> 仅展示批次级别依赖,子任务明细见下方表格。
```mermaid
flowchart TD
B6["B6 状态拆分"] --> B9["B9 多工程数据层"]
B9 --> B10["B10 文件浏览器"]
B9 --> B25["B25 依赖图数据"]
B10 --> B13["B13 浏览器增强"]
B13 --> B14["B14 收尾"]
B13 --> B15["B15 多工程管理"]
B13 --> B16["B16 提交作者"]
B13 --> B17["B17 Git 闭环"]
B25 --> B26["B26 dagre布局"]
B26 --> B29["B29 环检测+PNG"]
B9 --> B11["B11 Git只读"]
B11 --> B12["B12 Git写操作"]
PLAN["Plan执行器"] --> B33["B33 多ReAct"]
B30["B30 代码质量"] --> B31["B31 MsgList拆分"]
B31 --> B32["B32 ChatInput拆分"]
style B6 fill:#2d5a2d,color:#fff
style B9 fill:#2d5a2d,color:#fff
style B10 fill:#2d5a2d,color:#fff
style B11 fill:#2d5a2d,color:#fff
style B12 fill:#2d5a2d,color:#fff
style B13 fill:#2d5a2d,color:#fff
style B14 fill:#2d5a2d,color:#fff
style B15 fill:#2d5a2d,color:#fff
style B16 fill:#2d5a2d,color:#fff
style B17 fill:#2d5a2d,color:#fff
style B25 fill:#2d5a2d,color:#fff
style B26 fill:#2d5a2d,color:#fff
style B29 fill:#2d5a2d,color:#fff
style PLAN fill:#2d5a2d,color:#fff
style B30 fill:#5a4a2d,color:#fff
style B31 fill:#5a4a2d,color:#fff
style B32 fill:#5a4a2d,color:#fff
style B33 fill:#5a4a2d,color:#fff
```
绿色 = 已完成 | 橙色 = 待推进
---
## 四、批次子任务明细
| 批次 | 状态 | 子任务 |
|------|------|--------|
| Batch 9 | ✅ | project_modules 表 · ModuleRepo CRUD · 创建项目适配 · AI 工具注册 |
| Batch 10 | ✅ | 文件树组件 · 文件预览 · Git 状态标记 · 面包屑导航 |
| Batch 13 | ✅ | 行号/图标 · Diff 视图 · Git 变更面板 · 窗口分离 · 分页提交历史 |
| Batch 17 | ✅ | Diff 行号解析 · 分支只读展示 · 提交详情 · 写入后自动刷新 |
| Batch 19 | ✅ | 搜索框 · 筛选改下拉 · 排序选择器 · 分组可折叠 · 快捷操作菜单 |
| Batch 25 | ✅ | module_dependencies 表 · DependencyRepo CRUD · 依赖边渲染 · 小地图 |
| Batch 26 | ✅ | dagre 布局 · 添加依赖弹窗 · 边类型着色 |
| Batch 29 | ✅ | 环形检测 IPC · PNG 导出 · count_tasks IPC · 真实 total |
| Batch 30 | ⏸️ | 搜索索引核验 · 编译警告清理 · i18n 核验 · String 替 newtype |
| Batch 31 | ⏸️ | 提取 MessageItem.vue · 提取 useMessageScroll.ts |
| Batch 32 | ⏸️ | 提取 SkillMention.vue · 提取 ImageInput.vue |
| Batch 33 | ⏸️ | PlanExecutor 接入 loop · plan_hint 生成 DAG · 执行进度展示 |
---
## 四、关键指标
| 指标 | 数值 |
|------|------|
| 已完成批次 | 29 |
| 待推进批次 | 5 |
| 总提交数Batch 1-29 | ~35 |
| 涉及 Rust 文件 | ~25 |
| 涉及 Vue/TS 文件 | ~40 |
| 新增数据库迁移版本 | V35 |
| 新增 IPC 命令 | ~15 |
| 新增 npm 依赖 | @antv/x6 / dagre / highlight.js |
| 当前最大文件 | MessageList.vue1552行待拆分 |
---
## 五、待推进批次优先级矩阵
| 批次 | 价值 | 风险 | 象限 | 建议 |
|------|------|------|------|------|
| Batch 30 代码质量 | 中 | 低 | 高价值低风险 | ✅ 优先做 |
| Batch 31 MessageList 拆分 | 高 | 高 | 高价值高风险 | ⚠️ 需谨慎,要测试覆盖 |
| Batch 32 ChatInput 拆分 | 高 | 高 | 高价值高风险 | ⚠️ 需谨慎,要测试覆盖 |
| Batch 33 多 ReAct 对接 | 很高 | 很高 | 高价值高风险 | ⚠️ 核心架构改动 |
| Batch 34 产品完善 | 中 | 中 | 中价值中风险 | 🟡 可延后 |
---
## 六、批次提交密度
```mermaid
xychart-beta
title "批次提交密度(按阶段)"
x-axis ["阶段1", "阶段2", "阶段3", "阶段4", "阶段5", "阶段6", "阶段7", "阶段8", "阶段9"]
y-axis "批次数" 0 --> 8
bar [3, 3, 2, 3, 3, 3, 3, 3, 3]
```

View File

@@ -1,5 +1,20 @@
//! TypeScript 类型定义 — 与 Rust Record 结构体严格对齐 //! TypeScript 类型定义 — 与 Rust Record 结构体严格对齐
// ============================================================
// 域 ID newtype(纯类型级标记,零运行时开销)
// ============================================================
/** 项目 ID — branded newtype,替代 string */
export type ProjectId = string & { readonly __brand: 'ProjectId' }
/** 任务 ID — branded newtype,替代 string */
export type TaskId = string & { readonly __brand: 'TaskId' }
/** 对话 ID — branded newtype,替代 string */
export type ConvId = string & { readonly __brand: 'ConvId' }
/** 工程/模块 ID — branded newtype,替代 string */
export type ModuleId = string & { readonly __brand: 'ModuleId' }
/** 消息 ID — branded newtype,替代 string */
export type MessageId = string & { readonly __brand: 'MessageId' }
// ============================================================ // ============================================================
// 灵感 // 灵感
// ============================================================ // ============================================================
@@ -78,7 +93,7 @@ export type ProjectStatus =
| 'planning' | 'in_progress' | 'testing' | 'releasing' | 'completed' | 'paused' | 'cancelled' | 'planning' | 'in_progress' | 'testing' | 'releasing' | 'completed' | 'paused' | 'cancelled'
export interface ProjectRecord { export interface ProjectRecord {
id: string id: ProjectId
name: string name: string
description: string description: string
status: ProjectStatus status: ProjectStatus
@@ -116,7 +131,7 @@ export interface AiScanResult {
/** 灵感晋升结果(后端 promote_idea 返回) */ /** 灵感晋升结果(后端 promote_idea 返回) */
export interface PromotionResult { export interface PromotionResult {
idea_id: string idea_id: string
project_id: string project_id: ProjectId
promoted: boolean promoted: boolean
reason: string reason: string
} }
@@ -133,8 +148,8 @@ export type TaskStatus =
| 'todo' | 'in_progress' | 'in_review' | 'testing' | 'done' | 'blocked' | 'cancelled' | 'todo' | 'in_progress' | 'in_review' | 'testing' | 'done' | 'blocked' | 'cancelled'
export interface TaskRecord { export interface TaskRecord {
id: string id: TaskId
project_id: string project_id: ProjectId
title: string title: string
description: string description: string
status: TaskStatus status: TaskStatus
@@ -159,7 +174,7 @@ export interface TaskRecord {
} }
export interface CreateTaskInput { export interface CreateTaskInput {
project_id: string project_id: ProjectId
title: string title: string
description?: string description?: string
priority?: number priority?: number
@@ -423,7 +438,7 @@ export type AiChatEvent = ({
// 上下文已清空:前端刷新消息列表 + toast 提示 // 上下文已清空:前端刷新消息列表 + toast 提示
type: 'AiContextCleared' type: 'AiContextCleared'
}) & { }) & {
conversation_id?: string conversation_id?: ConvId
} }
/** /**
@@ -447,7 +462,7 @@ export type ContentPart =
/** AI 消息前端渲染用isError 标记错误消息用于差异化样式) */ /** AI 消息前端渲染用isError 标记错误消息用于差异化样式) */
export interface AiMessage { export interface AiMessage {
id: string id: MessageId
role: 'user' | 'assistant' | 'tool' | 'system' role: 'user' | 'assistant' | 'tool' | 'system'
content: string content: string
isError?: boolean isError?: boolean
@@ -581,7 +596,7 @@ export interface AiToolCallInfo {
/** 对话列表摘要 */ /** 对话列表摘要 */
export interface AiConversationSummary { export interface AiConversationSummary {
id: string id: ConvId
title: string | null title: string | null
provider_id: string | null provider_id: string | null
model: string | null model: string | null
@@ -620,7 +635,7 @@ export interface HumanApprovalResponse {
/** 切换对话返回(含 messages JSON */ /** 切换对话返回(含 messages JSON */
export interface AiConversationDetail { export interface AiConversationDetail {
id: string id: ConvId
title: string | null title: string | null
messages: string // JSON string of ChatMessage[] messages: string // JSON string of ChatMessage[]
/** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */ /** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */

View File

@@ -85,6 +85,8 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [
// ===== 高级 ===== // ===== 高级 =====
// F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名 // F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名
// dataDir(应用数据目录):AdvancedSection.vue 渲染行,此前漏入索引
{ category: 'advanced', key: 'dataDir', labelKey: 'settings.labelDataDir', descKey: 'settings.descDataDir' },
{ category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' }, { category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' },
{ category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' }, { category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' },
{ category: 'advanced', key: 'approvalTimeout', labelKey: 'settings.labelApprovalTimeout', descKey: 'settings.descApprovalTimeout' }, { category: 'advanced', key: 'approvalTimeout', labelKey: 'settings.labelApprovalTimeout', descKey: 'settings.descApprovalTimeout' },

View File

@@ -14,7 +14,7 @@ import { reactive } from 'vue'
import { aiApi } from '@/api' import { aiApi } from '@/api'
import { useAppSettingsStore } from '@/stores/appSettings' import { useAppSettingsStore } from '@/stores/appSettings'
import { t } from '@/i18n/i18n-helpers' import { t } from '@/i18n/i18n-helpers'
import type { AiMessage, AiToolCallInfo, ConvState } from '@/api/types' import type { AiMessage, AiToolCallInfo, ConvState, MessageId } from '@/api/types'
/** /**
@@ -154,7 +154,7 @@ export function startApprovalTimer(
console.error('[AI] 审批超时自动拒绝 IPC 未送达:', e) console.error('[AI] 审批超时自动拒绝 IPC 未送达:', e)
}) })
getMessages().push({ getMessages().push({
id: `approval-timeout-${nextMsgId()}`, id: `approval-timeout-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: t('ai.approvalTimeout', { toolName }), content: t('ai.approvalTimeout', { toolName }),
isError: true, isError: true,

View File

@@ -12,7 +12,7 @@ import { persistUiState } from './useAiPanel'
import { setStreaming } from './streamingGuard' import { setStreaming } from './streamingGuard'
import { nextMsgId, getConvState, clearConvStreamState, startApprovalTimer } from './aiShared' import { nextMsgId, getConvState, clearConvStreamState, startApprovalTimer } from './aiShared'
import { t } from '@/i18n/i18n-helpers' import { t } from '@/i18n/i18n-helpers'
import type { AiConversationDetail, AiMessage, AiToolCallInfo } from '@/api/types' import type { AiConversationDetail, AiMessage, AiToolCallInfo, ConvId } from '@/api/types'
const appSettings = useAppSettingsStore() const appSettings = useAppSettingsStore()
@@ -28,7 +28,7 @@ async function loadConversations() {
&& !state.conversations.some(c => c.id === state.activeConversationId)) { && !state.conversations.some(c => c.id === state.activeConversationId)) {
const now = String(Date.now()) const now = String(Date.now())
state.conversations.unshift({ state.conversations.unshift({
id: state.activeConversationId, id: state.activeConversationId as ConvId,
title: null, title: null,
provider_id: null, provider_id: null,
model: null, model: null,
@@ -102,7 +102,7 @@ export async function switchConversation(id: string) {
if (mySwitchId !== _latestSwitchId) return if (mySwitchId !== _latestSwitchId) return
void appSettings.set('df-ai-active-conv', created.id) void appSettings.set('df-ai-active-conv', created.id)
// 用新对话 id 重走后续逻辑 // 用新对话 id 重走后续逻辑
detail = { id: created.id, title: null, messages: '[]' } detail = { id: created.id as ConvId, title: null, messages: '[]' }
state.messages = [] state.messages = []
notifyConversationChanged() notifyConversationChanged()
void loadConversations() void loadConversations()

View File

@@ -32,7 +32,7 @@ export { getConvState } from './aiShared'
import { resetStreamWatchdog, clearStreamWatchdog, clearAllStreamWatchdogs } from './useAiStream' import { resetStreamWatchdog, clearStreamWatchdog, clearAllStreamWatchdogs } from './useAiStream'
import { setStreaming } from './streamingGuard' import { setStreaming } from './streamingGuard'
import { loadConversations } from './useAiConversations' import { loadConversations } from './useAiConversations'
import type { AiChatEvent, AiMessage, AiToolCallInfo } from '@/api/types' import type { AiChatEvent, AiMessage, AiToolCallInfo, MessageId } from '@/api/types'
let _unlistenAiEvent: (() => void) | null = null let _unlistenAiEvent: (() => void) | null = null
let _unlistenConvChanged: (() => void) | null = null let _unlistenConvChanged: (() => void) | null = null
@@ -283,7 +283,7 @@ function handleStreamingEvent(event: AiChatEvent): boolean {
flushCurrentText() flushCurrentText()
state.currentText = '' state.currentText = ''
state.messages.push({ state.messages.push({
id: `ai-${nextMsgId()}`, id: `ai-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: '', content: '',
timestamp: Date.now(), timestamp: Date.now(),
@@ -313,7 +313,7 @@ function handleStreamingEvent(event: AiChatEvent): boolean {
lastMsg.content = retryText lastMsg.content = retryText
} else { } else {
state.messages.push({ state.messages.push({
id: `err-${nextMsgId()}`, id: `err-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: retryText, content: retryText,
isError: true, isError: true,
@@ -557,7 +557,7 @@ function handleUserMessageEvent(event: AiChatEvent): boolean {
return true return true
} }
state.messages.push({ state.messages.push({
id: `user-${nextMsgId()}`, id: `user-${nextMsgId()}` as MessageId,
role: 'user', role: 'user',
content: event.message, content: event.message,
timestamp: Date.now(), timestamp: Date.now(),
@@ -623,7 +623,7 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
// 注:此系统提示仅前端展示,后端已独立 push 到 session.messages 落库。 // 注:此系统提示仅前端展示,后端已独立 push 到 session.messages 落库。
if (event.incomplete) { if (event.incomplete) {
state.messages.push({ state.messages.push({
id: `incomplete-${nextMsgId()}`, id: `incomplete-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: t('ai.responseIncomplete'), content: t('ai.responseIncomplete'),
timestamp: Date.now(), timestamp: Date.now(),
@@ -667,7 +667,7 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
// AiMessage 类型未含 errorType 字段(不在本批白名单),用对象字面量 + cast 扩展; // AiMessage 类型未含 errorType 字段(不在本批白名单),用对象字面量 + cast 扩展;
// 消费方(AiChat.vue canOpenSettings)经同 cast 读取,类型闭环在两端,不污染 types.ts。 // 消费方(AiChat.vue canOpenSettings)经同 cast 读取,类型闭环在两端,不污染 types.ts。
state.messages.push({ state.messages.push({
id: `err-${nextMsgId()}`, id: `err-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: friendlyError(event.error), content: friendlyError(event.error),
isError: true, isError: true,

View File

@@ -19,7 +19,7 @@ import { invoke } from '@tauri-apps/api/core'
import { ref } from 'vue' import { ref } from 'vue'
import { aiApi } from '@/api' import { aiApi } from '@/api'
import { state } from '@/stores/ai' import { state } from '@/stores/ai'
import type { ContentPart, AiMessage, MentionSpan } from '@/api/types' import type { ContentPart, AiMessage, MentionSpan, MessageId } from '@/api/types'
import { t } from '@/i18n/i18n-helpers' import { t } from '@/i18n/i18n-helpers'
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream' import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
import { setStreaming } from './streamingGuard' import { setStreaming } from './streamingGuard'
@@ -73,7 +73,7 @@ const modelOverride = ref<string | null>(null)
async function doSend(text: string, skill?: string, force = false, parts?: ContentPart[], spans?: MentionSpan[]) { async function doSend(text: string, skill?: string, force = false, parts?: ContentPart[], spans?: MentionSpan[]) {
const userMsgId = `user-${nextMsgId()}` const userMsgId = `user-${nextMsgId()}`
state.messages.push({ state.messages.push({
id: userMsgId, id: userMsgId as MessageId,
role: 'user', role: 'user',
content: text.trim(), content: text.trim(),
// 仅在非空时挂 parts(纯文本消息保持 undefined,渲染走原 content 路径零回归) // 仅在非空时挂 parts(纯文本消息保持 undefined,渲染走原 content 路径零回归)
@@ -85,7 +85,7 @@ async function doSend(text: string, skill?: string, force = false, parts?: Conte
const aiMsgId = `ai-${nextMsgId()}` const aiMsgId = `ai-${nextMsgId()}`
state.messages.push({ state.messages.push({
id: aiMsgId, id: aiMsgId as MessageId,
role: 'assistant', role: 'assistant',
content: '', content: '',
timestamp: Date.now(), timestamp: Date.now(),
@@ -141,7 +141,7 @@ async function regenerate() {
state.messages.pop() state.messages.pop()
const aiMsgId = `ai-${nextMsgId()}` const aiMsgId = `ai-${nextMsgId()}`
state.messages.push({ state.messages.push({
id: aiMsgId, id: aiMsgId as MessageId,
role: 'assistant', role: 'assistant',
content: '', content: '',
timestamp: Date.now(), timestamp: Date.now(),
@@ -211,7 +211,7 @@ async function editMessage(newMessage: string) {
// push 空气泡占位(新 AI 回复将流入) // push 空气泡占位(新 AI 回复将流入)
const aiMsgId = `ai-${nextMsgId()}` const aiMsgId = `ai-${nextMsgId()}`
state.messages.push({ state.messages.push({
id: aiMsgId, id: aiMsgId as MessageId,
role: 'assistant', role: 'assistant',
content: '', content: '',
timestamp: Date.now(), timestamp: Date.now(),

View File

@@ -14,6 +14,7 @@
import { state } from '@/stores/ai' import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers' import { t } from '@/i18n/i18n-helpers'
import { nextMsgId, getConvState } from './aiShared' import { nextMsgId, getConvState } from './aiShared'
import type { MessageId } from '@/api/types'
import { forceResetStreaming } from './streamingGuard' import { forceResetStreaming } from './streamingGuard'
import { emit } from '@tauri-apps/api/event' import { emit } from '@tauri-apps/api/event'
@@ -124,7 +125,7 @@ export function onStreamTimeout(convId?: string) {
? t('ai.streamInterruptedAfterTool') ? t('ai.streamInterruptedAfterTool')
: t('ai.streamInterrupted') : t('ai.streamInterrupted')
state.messages.push({ state.messages.push({
id: `timeout-${nextMsgId()}`, id: `timeout-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content, content,
isError: true, isError: true,

View File

@@ -11,6 +11,7 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { state } from '@/stores/ai' import { state } from '@/stores/ai'
import { nextMsgId, getConvState, convStates } from './aiShared' import { nextMsgId, getConvState, convStates } from './aiShared'
import type { MessageId } from '@/api/types'
import { setStreaming } from './streamingGuard' import { setStreaming } from './streamingGuard'
import { persistUiState } from './useAiPanel' import { persistUiState } from './useAiPanel'
@@ -194,7 +195,7 @@ export async function restoreGeneratingState(opts?: { fromMainPanel?: boolean; c
state.currentText = (opts?.convId && localStorage.getItem(textKey(opts.convId))) || localStorage.getItem('df-ai-text') || '' state.currentText = (opts?.convId && localStorage.getItem(textKey(opts.convId))) || localStorage.getItem('df-ai-text') || ''
} }
state.messages.push({ state.messages.push({
id: `ai-${nextMsgId()}`, id: `ai-${nextMsgId()}` as MessageId,
role: 'assistant', role: 'assistant',
content: '', content: '',
timestamp: Date.now(), timestamp: Date.now(),

View File

@@ -33,7 +33,5 @@ export default {
removeModule: '删除工程', removeModule: '删除工程',
removeConfirm: '确定删除工程「{name}」吗?此操作不可撤销。', removeConfirm: '确定删除工程「{name}」吗?此操作不可撤销。',
scanSubmodules: '扫描子仓库', scanSubmodules: '扫描子仓库',
// Git Changes tab
loadMore: '加载更多',
}, },
} }

View File

@@ -1,5 +1,5 @@
import { taskApi } from '@/api' import { taskApi } from '@/api'
import type { TaskQuery } from '@/api/types' import type { TaskQuery, ProjectId } from '@/api/types'
import { t } from '@/i18n/i18n-helpers' import { t } from '@/i18n/i18n-helpers'
import { runWithCatch } from '@/composables/useStoreAction' import { runWithCatch } from '@/composables/useStoreAction'
import { state } from './state' import { state } from './state'
@@ -22,7 +22,7 @@ export function createTasksStore() {
}) })
} }
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string; idea_id?: string }) { async function createTask(input: { project_id: ProjectId; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string; idea_id?: string }) {
const record = await runWithCatch(state, t('tasks.err.createFailed'), async () => { const record = await runWithCatch(state, t('tasks.err.createFailed'), async () => {
const r = await taskApi.create(input) const r = await taskApi.create(input)
state.tasks.push(r) state.tasks.push(r)

View File

@@ -274,6 +274,7 @@ import FileExplorer from '@/components/project/FileExplorer.vue'
import DependencyGraph from '@/components/project/DependencyGraph.vue' import DependencyGraph from '@/components/project/DependencyGraph.vue'
import { useConfirm } from '@/composables/useConfirm' import { useConfirm } from '@/composables/useConfirm'
import { useRendered } from '@/composables/useMarkdown' import { useRendered } from '@/composables/useMarkdown'
import type { ProjectId } from '@/api/types'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -406,7 +407,7 @@ async function submitNewTask() {
submitting.value = true submitting.value = true
try { try {
const r = await store.createTask({ const r = await store.createTask({
project_id: projectId.value, project_id: projectId.value as ProjectId,
title: newTaskTitle.value.trim(), title: newTaskTitle.value.trim(),
description: newTaskDesc.value.trim(), description: newTaskDesc.value.trim(),
branch_name: newTaskBranch.value.trim() || undefined, branch_name: newTaskBranch.value.trim() || undefined,

View File

@@ -166,7 +166,7 @@ import { useProjectStore } from '@/stores/project'
import { formatRelative } from '@/utils/time' import { formatRelative } from '@/utils/time'
import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project' import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project'
import { taskApi } from '@/api' import { taskApi } from '@/api'
import type { TaskRecord, TaskQuery } from '@/api/types' import type { TaskRecord, TaskQuery, ProjectId } from '@/api/types'
import Paginator from '../components/Paginator.vue' import Paginator from '../components/Paginator.vue'
const router = useRouter() const router = useRouter()
@@ -368,7 +368,7 @@ async function confirmCreate() {
submitting.value = true submitting.value = true
try { try {
const r = await store.createTask({ const r = await store.createTask({
project_id: newTaskProjectId.value, project_id: newTaskProjectId.value as ProjectId,
title: newTaskTitle.value.trim(), title: newTaskTitle.value.trim(),
description: newTaskDesc.value.trim(), description: newTaskDesc.value.trim(),
branch_name: newTaskBranch.value.trim() || undefined, branch_name: newTaskBranch.value.trim() || undefined,