优化: 代码质量收尾(搜索索引补全+编译警告清理+i18n核验+String替newtype)
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
//! 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'
|
||||
|
||||
export interface ProjectRecord {
|
||||
id: string
|
||||
id: ProjectId
|
||||
name: string
|
||||
description: string
|
||||
status: ProjectStatus
|
||||
@@ -116,7 +131,7 @@ export interface AiScanResult {
|
||||
/** 灵感晋升结果(后端 promote_idea 返回) */
|
||||
export interface PromotionResult {
|
||||
idea_id: string
|
||||
project_id: string
|
||||
project_id: ProjectId
|
||||
promoted: boolean
|
||||
reason: string
|
||||
}
|
||||
@@ -133,8 +148,8 @@ export type TaskStatus =
|
||||
| 'todo' | 'in_progress' | 'in_review' | 'testing' | 'done' | 'blocked' | 'cancelled'
|
||||
|
||||
export interface TaskRecord {
|
||||
id: string
|
||||
project_id: string
|
||||
id: TaskId
|
||||
project_id: ProjectId
|
||||
title: string
|
||||
description: string
|
||||
status: TaskStatus
|
||||
@@ -159,7 +174,7 @@ export interface TaskRecord {
|
||||
}
|
||||
|
||||
export interface CreateTaskInput {
|
||||
project_id: string
|
||||
project_id: ProjectId
|
||||
title: string
|
||||
description?: string
|
||||
priority?: number
|
||||
@@ -423,7 +438,7 @@ export type AiChatEvent = ({
|
||||
// 上下文已清空:前端刷新消息列表 + toast 提示
|
||||
type: 'AiContextCleared'
|
||||
}) & {
|
||||
conversation_id?: string
|
||||
conversation_id?: ConvId
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,7 +462,7 @@ export type ContentPart =
|
||||
|
||||
/** AI 消息(前端渲染用,isError 标记错误消息用于差异化样式) */
|
||||
export interface AiMessage {
|
||||
id: string
|
||||
id: MessageId
|
||||
role: 'user' | 'assistant' | 'tool' | 'system'
|
||||
content: string
|
||||
isError?: boolean
|
||||
@@ -581,7 +596,7 @@ export interface AiToolCallInfo {
|
||||
|
||||
/** 对话列表摘要 */
|
||||
export interface AiConversationSummary {
|
||||
id: string
|
||||
id: ConvId
|
||||
title: string | null
|
||||
provider_id: string | null
|
||||
model: string | null
|
||||
@@ -620,7 +635,7 @@ export interface HumanApprovalResponse {
|
||||
|
||||
/** 切换对话返回(含 messages JSON) */
|
||||
export interface AiConversationDetail {
|
||||
id: string
|
||||
id: ConvId
|
||||
title: string | null
|
||||
messages: string // JSON string of ChatMessage[]
|
||||
/** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */
|
||||
|
||||
@@ -85,6 +85,8 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [
|
||||
|
||||
// ===== 高级 =====
|
||||
// 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: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' },
|
||||
{ category: 'advanced', key: 'approvalTimeout', labelKey: 'settings.labelApprovalTimeout', descKey: 'settings.descApprovalTimeout' },
|
||||
|
||||
@@ -14,7 +14,7 @@ import { reactive } from 'vue'
|
||||
import { aiApi } from '@/api'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
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)
|
||||
})
|
||||
getMessages().push({
|
||||
id: `approval-timeout-${nextMsgId()}`,
|
||||
id: `approval-timeout-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: t('ai.approvalTimeout', { toolName }),
|
||||
isError: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { persistUiState } from './useAiPanel'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
import { nextMsgId, getConvState, clearConvStreamState, startApprovalTimer } from './aiShared'
|
||||
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()
|
||||
|
||||
@@ -28,7 +28,7 @@ async function loadConversations() {
|
||||
&& !state.conversations.some(c => c.id === state.activeConversationId)) {
|
||||
const now = String(Date.now())
|
||||
state.conversations.unshift({
|
||||
id: state.activeConversationId,
|
||||
id: state.activeConversationId as ConvId,
|
||||
title: null,
|
||||
provider_id: null,
|
||||
model: null,
|
||||
@@ -102,7 +102,7 @@ export async function switchConversation(id: string) {
|
||||
if (mySwitchId !== _latestSwitchId) return
|
||||
void appSettings.set('df-ai-active-conv', created.id)
|
||||
// 用新对话 id 重走后续逻辑
|
||||
detail = { id: created.id, title: null, messages: '[]' }
|
||||
detail = { id: created.id as ConvId, title: null, messages: '[]' }
|
||||
state.messages = []
|
||||
notifyConversationChanged()
|
||||
void loadConversations()
|
||||
|
||||
@@ -32,7 +32,7 @@ export { getConvState } from './aiShared'
|
||||
import { resetStreamWatchdog, clearStreamWatchdog, clearAllStreamWatchdogs } from './useAiStream'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
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 _unlistenConvChanged: (() => void) | null = null
|
||||
@@ -283,7 +283,7 @@ function handleStreamingEvent(event: AiChatEvent): boolean {
|
||||
flushCurrentText()
|
||||
state.currentText = ''
|
||||
state.messages.push({
|
||||
id: `ai-${nextMsgId()}`,
|
||||
id: `ai-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
timestamp: Date.now(),
|
||||
@@ -313,7 +313,7 @@ function handleStreamingEvent(event: AiChatEvent): boolean {
|
||||
lastMsg.content = retryText
|
||||
} else {
|
||||
state.messages.push({
|
||||
id: `err-${nextMsgId()}`,
|
||||
id: `err-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: retryText,
|
||||
isError: true,
|
||||
@@ -557,7 +557,7 @@ function handleUserMessageEvent(event: AiChatEvent): boolean {
|
||||
return true
|
||||
}
|
||||
state.messages.push({
|
||||
id: `user-${nextMsgId()}`,
|
||||
id: `user-${nextMsgId()}` as MessageId,
|
||||
role: 'user',
|
||||
content: event.message,
|
||||
timestamp: Date.now(),
|
||||
@@ -623,7 +623,7 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
|
||||
// 注:此系统提示仅前端展示,后端已独立 push 到 session.messages 落库。
|
||||
if (event.incomplete) {
|
||||
state.messages.push({
|
||||
id: `incomplete-${nextMsgId()}`,
|
||||
id: `incomplete-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: t('ai.responseIncomplete'),
|
||||
timestamp: Date.now(),
|
||||
@@ -667,7 +667,7 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
|
||||
// AiMessage 类型未含 errorType 字段(不在本批白名单),用对象字面量 + cast 扩展;
|
||||
// 消费方(AiChat.vue canOpenSettings)经同 cast 读取,类型闭环在两端,不污染 types.ts。
|
||||
state.messages.push({
|
||||
id: `err-${nextMsgId()}`,
|
||||
id: `err-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: friendlyError(event.error),
|
||||
isError: true,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { invoke } from '@tauri-apps/api/core'
|
||||
import { ref } from 'vue'
|
||||
import { aiApi } from '@/api'
|
||||
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 { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||
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[]) {
|
||||
const userMsgId = `user-${nextMsgId()}`
|
||||
state.messages.push({
|
||||
id: userMsgId,
|
||||
id: userMsgId as MessageId,
|
||||
role: 'user',
|
||||
content: text.trim(),
|
||||
// 仅在非空时挂 parts(纯文本消息保持 undefined,渲染走原 content 路径零回归)
|
||||
@@ -85,7 +85,7 @@ async function doSend(text: string, skill?: string, force = false, parts?: Conte
|
||||
|
||||
const aiMsgId = `ai-${nextMsgId()}`
|
||||
state.messages.push({
|
||||
id: aiMsgId,
|
||||
id: aiMsgId as MessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
timestamp: Date.now(),
|
||||
@@ -141,7 +141,7 @@ async function regenerate() {
|
||||
state.messages.pop()
|
||||
const aiMsgId = `ai-${nextMsgId()}`
|
||||
state.messages.push({
|
||||
id: aiMsgId,
|
||||
id: aiMsgId as MessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
timestamp: Date.now(),
|
||||
@@ -211,7 +211,7 @@ async function editMessage(newMessage: string) {
|
||||
// push 空气泡占位(新 AI 回复将流入)
|
||||
const aiMsgId = `ai-${nextMsgId()}`
|
||||
state.messages.push({
|
||||
id: aiMsgId,
|
||||
id: aiMsgId as MessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
timestamp: Date.now(),
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { state } from '@/stores/ai'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import { nextMsgId, getConvState } from './aiShared'
|
||||
import type { MessageId } from '@/api/types'
|
||||
import { forceResetStreaming } from './streamingGuard'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
|
||||
@@ -124,7 +125,7 @@ export function onStreamTimeout(convId?: string) {
|
||||
? t('ai.streamInterruptedAfterTool')
|
||||
: t('ai.streamInterrupted')
|
||||
state.messages.push({
|
||||
id: `timeout-${nextMsgId()}`,
|
||||
id: `timeout-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content,
|
||||
isError: true,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { state } from '@/stores/ai'
|
||||
import { nextMsgId, getConvState, convStates } from './aiShared'
|
||||
import type { MessageId } from '@/api/types'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
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.messages.push({
|
||||
id: `ai-${nextMsgId()}`,
|
||||
id: `ai-${nextMsgId()}` as MessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
timestamp: Date.now(),
|
||||
|
||||
@@ -33,7 +33,5 @@ export default {
|
||||
removeModule: '删除工程',
|
||||
removeConfirm: '确定删除工程「{name}」吗?此操作不可撤销。',
|
||||
scanSubmodules: '扫描子仓库',
|
||||
// Git Changes tab
|
||||
loadMore: '加载更多',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { taskApi } from '@/api'
|
||||
import type { TaskQuery } from '@/api/types'
|
||||
import type { TaskQuery, ProjectId } from '@/api/types'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import { runWithCatch } from '@/composables/useStoreAction'
|
||||
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 r = await taskApi.create(input)
|
||||
state.tasks.push(r)
|
||||
|
||||
@@ -274,6 +274,7 @@ import FileExplorer from '@/components/project/FileExplorer.vue'
|
||||
import DependencyGraph from '@/components/project/DependencyGraph.vue'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useRendered } from '@/composables/useMarkdown'
|
||||
import type { ProjectId } from '@/api/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -406,7 +407,7 @@ async function submitNewTask() {
|
||||
submitting.value = true
|
||||
try {
|
||||
const r = await store.createTask({
|
||||
project_id: projectId.value,
|
||||
project_id: projectId.value as ProjectId,
|
||||
title: newTaskTitle.value.trim(),
|
||||
description: newTaskDesc.value.trim(),
|
||||
branch_name: newTaskBranch.value.trim() || undefined,
|
||||
|
||||
@@ -166,7 +166,7 @@ import { useProjectStore } from '@/stores/project'
|
||||
import { formatRelative } from '@/utils/time'
|
||||
import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project'
|
||||
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'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -368,7 +368,7 @@ async function confirmCreate() {
|
||||
submitting.value = true
|
||||
try {
|
||||
const r = await store.createTask({
|
||||
project_id: newTaskProjectId.value,
|
||||
project_id: newTaskProjectId.value as ProjectId,
|
||||
title: newTaskTitle.value.trim(),
|
||||
description: newTaskDesc.value.trim(),
|
||||
branch_name: newTaskBranch.value.trim() || undefined,
|
||||
|
||||
Reference in New Issue
Block a user