前端优化

This commit is contained in:
2026-06-12 13:36:39 +08:00
parent f92ad5fab4
commit 522e88a6a2
11 changed files with 42 additions and 19 deletions
+6 -1
View File
@@ -34,11 +34,13 @@ export async function request<T>(path: string, opts: RequestOptions = {}): Promi
const controller = new AbortController() const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS) const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS)
let onAbort: (() => void) | undefined
if (opts.signal) { if (opts.signal) {
if (opts.signal.aborted) { if (opts.signal.aborted) {
controller.abort() controller.abort()
} else { } else {
opts.signal.addEventListener('abort', () => controller.abort(), { once: true }) onAbort = () => controller.abort()
opts.signal.addEventListener('abort', onAbort, { once: true })
} }
} }
@@ -78,6 +80,9 @@ export async function request<T>(path: string, opts: RequestOptions = {}): Promi
return data as T return data as T
} finally { } finally {
clearTimeout(timer) clearTimeout(timer)
if (opts.signal && onAbort) {
opts.signal.removeEventListener('abort', onAbort)
}
} }
} }
+8 -4
View File
@@ -57,11 +57,12 @@ const allowlistText = computed({
}) })
interface EnvRow { interface EnvRow {
id: string
k: string k: string
v: string v: string
} }
const envEntries = ref<EnvRow[]>( const envEntries = ref<EnvRow[]>(
Object.entries(local.value.env).map(([k, v]) => ({ k, v })), Object.entries(local.value.env).map(([k, v]) => ({ id: crypto.randomUUID(), k, v })),
) )
watch( watch(
@@ -77,7 +78,7 @@ watch(
) )
function addEnv() { function addEnv() {
envEntries.value.push({ k: '', v: '' }) envEntries.value.push({ id: crypto.randomUUID(), k: '', v: '' })
} }
function removeEnv(idx: number) { function removeEnv(idx: number) {
envEntries.value.splice(idx, 1) envEntries.value.splice(idx, 1)
@@ -94,6 +95,7 @@ const mcpTypeOptions = [
] ]
interface McpRow { interface McpRow {
id: string
name: string name: string
type: 'stdio' | 'http' | 'sse' type: 'stdio' | 'http' | 'sse'
command: string command: string
@@ -123,6 +125,7 @@ function parseKV(s: string): Record<string, string> | undefined {
function specsToRows(specs: McpServerSpec[]): McpRow[] { function specsToRows(specs: McpServerSpec[]): McpRow[] {
return specs.map((s) => ({ return specs.map((s) => ({
id: crypto.randomUUID(),
name: s.name, name: s.name,
type: s.type, type: s.type,
command: s.command ?? '', command: s.command ?? '',
@@ -173,6 +176,7 @@ watch(
function addMcpServer() { function addMcpServer() {
mcpRows.value.push({ mcpRows.value.push({
id: crypto.randomUUID(),
name: '', name: '',
type: 'http', type: 'http',
command: '', command: '',
@@ -258,7 +262,7 @@ function removeMcpServer(idx: number) {
</p> </p>
<div <div
v-for="(e, idx) in envEntries" v-for="(e, idx) in envEntries"
:key="idx" :key="e.id"
class="flex gap-2 mb-2" class="flex gap-2 mb-2"
> >
<NInput <NInput
@@ -305,7 +309,7 @@ function removeMcpServer(idx: number) {
</label> </label>
<div <div
v-for="(m, idx) in mcpRows" v-for="(m, idx) in mcpRows"
:key="idx" :key="m.id"
class="border rounded p-3 mb-2 space-y-2" class="border rounded p-3 mb-2 space-y-2"
> >
<div class="flex gap-2"> <div class="flex gap-2">
+3
View File
@@ -0,0 +1,3 @@
import MarkdownIt from 'markdown-it'
export const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
+1
View File
@@ -94,6 +94,7 @@
<MessageBubble <MessageBubble
v-for="m in chat.messages" v-for="m in chat.messages"
:key="m.id" :key="m.id"
v-memo="[m.id, m.content, m.status]"
:message="m" :message="m"
@retry="onRetry" @retry="onRetry"
/> />
@@ -93,13 +93,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { NCollapse, NCollapseItem, NButton } from 'naive-ui' import { NCollapse, NCollapseItem, NButton } from 'naive-ui'
import MarkdownIt from 'markdown-it'
import type { ChatMessage } from '@/api/chat' import type { ChatMessage } from '@/api/chat'
import { md } from '@/utils/markdown'
import { parseInteractiveMessage, type InteractiveQuestionAnswer } from '@/utils/interactiveMessage' import { parseInteractiveMessage, type InteractiveQuestionAnswer } from '@/utils/interactiveMessage'
import QuestionCard from './QuestionCard.vue' import QuestionCard from './QuestionCard.vue'
const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
// savable:仅需求阶段对话面板传 true,对成功的 assistant 回复显示「保存为产物」。 // savable:仅需求阶段对话面板传 true,对成功的 assistant 回复显示「保存为产物」。
const props = defineProps<{ message: ChatMessage; savable?: boolean; answerDisabled?: boolean }>() const props = defineProps<{ message: ChatMessage; savable?: boolean; answerDisabled?: boolean }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -112,9 +112,8 @@ import {
NTag, NTag,
NThing, NThing,
} from 'naive-ui' } from 'naive-ui'
import MarkdownIt from 'markdown-it'
import type { Artifact, ArtifactPhase } from '@/api/projects' import type { Artifact, ArtifactPhase } from '@/api/projects'
import { md } from '@/utils/markdown'
import { useRequirementsStore } from '@/stores/requirements' import { useRequirementsStore } from '@/stores/requirements'
const PHASE_TITLE: Record<ArtifactPhase, string> = { const PHASE_TITLE: Record<ArtifactPhase, string> = {
@@ -131,7 +130,6 @@ const props = defineProps<{
}>() }>()
const store = useRequirementsStore() const store = useRequirementsStore()
const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
const artifacts = ref<Artifact[]>([]) const artifacts = ref<Artifact[]>([])
const selectedVersion = ref<number | null>(null) const selectedVersion = ref<number | null>(null)
@@ -71,6 +71,7 @@
<MessageBubble <MessageBubble
v-for="m in session.messages.value" v-for="m in session.messages.value"
:key="m.id" :key="m.id"
v-memo="[m.id, m.content, m.status]"
:message="m" :message="m"
:savable="!readonly" :savable="!readonly"
:answer-disabled="readonly || session.isStreaming.value" :answer-disabled="readonly || session.isStreaming.value"
@@ -201,11 +201,11 @@ import {
NTag, NTag,
NThing, NThing,
} from 'naive-ui' } from 'naive-ui'
import MarkdownIt from 'markdown-it'
import { chatApi, type Conversation } from '@/api/chat' import { chatApi, type Conversation } from '@/api/chat'
import { acpApi, type AcpSession } from '@/api/acp' import { acpApi, type AcpSession } from '@/api/acp'
import type { Artifact, ArtifactPhase, Phase } from '@/api/projects' import type { Artifact, ArtifactPhase, Phase } from '@/api/projects'
import { md } from '@/utils/markdown'
import { useRequirementsStore } from '@/stores/requirements' import { useRequirementsStore } from '@/stores/requirements'
import SessionStatusBadge from '@/components/acp/SessionStatusBadge.vue' import SessionStatusBadge from '@/components/acp/SessionStatusBadge.vue'
@@ -227,7 +227,6 @@ const props = defineProps<{
const router = useRouter() const router = useRouter()
const store = useRequirementsStore() const store = useRequirementsStore()
const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
const loading = ref(false) const loading = ref(false)
const artifacts = ref<Artifact[]>([]) const artifacts = ref<Artifact[]>([])
@@ -68,9 +68,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { NCard, NDivider, NEmpty, NList, NListItem, NSpin, NTag, NThing } from 'naive-ui' import { NCard, NDivider, NEmpty, NList, NListItem, NSpin, NTag, NThing } from 'naive-ui'
import MarkdownIt from 'markdown-it'
import type { Artifact, ArtifactPhase } from '@/api/projects' import type { Artifact, ArtifactPhase } from '@/api/projects'
import { md } from '@/utils/markdown'
import { useRequirementsStore } from '@/stores/requirements' import { useRequirementsStore } from '@/stores/requirements'
const PHASE_TITLE: Record<ArtifactPhase, string> = { const PHASE_TITLE: Record<ArtifactPhase, string> = {
@@ -86,7 +85,6 @@ const props = defineProps<{
}>() }>()
const store = useRequirementsStore() const store = useRequirementsStore()
const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
const artifacts = ref<Artifact[]>([]) const artifacts = ref<Artifact[]>([])
const loading = ref(false) const loading = ref(false)
@@ -54,10 +54,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { NButton, NInput, NModal, NTabPane, NTabs, useMessage } from 'naive-ui' import { NButton, NInput, NModal, NTabPane, NTabs, useMessage } from 'naive-ui'
import MarkdownIt from 'markdown-it'
import type { ArtifactPhase } from '@/api/projects' import type { ArtifactPhase } from '@/api/projects'
import type { ChatMessage } from '@/api/chat' import type { ChatMessage } from '@/api/chat'
import { md } from '@/utils/markdown'
import { useRequirementsStore } from '@/stores/requirements' import { useRequirementsStore } from '@/stores/requirements'
const PHASE_TITLE: Record<ArtifactPhase, string> = { const PHASE_TITLE: Record<ArtifactPhase, string> = {
@@ -78,7 +78,6 @@ const emit = defineEmits<{ 'update:show': [v: boolean]; saved: [] }>()
const store = useRequirementsStore() const store = useRequirementsStore()
const message = useMessage() const message = useMessage()
const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
const content = ref('') const content = ref('')
const note = ref('') const note = ref('')
+18
View File
@@ -20,5 +20,23 @@ export default defineConfig({
build: { build: {
outDir: 'dist', outDir: 'dist',
emptyOutDir: true, emptyOutDir: true,
sourcemap: true,
cssCodeSplit: true,
chunkSizeWarningLimit: 500,
rollupOptions: {
output: {
manualChunks(id) {
if (
id.includes('node_modules/vue/') ||
id.includes('node_modules/vue-router/') ||
id.includes('node_modules/pinia/')
) {
return 'vendor'
}
if (id.includes('node_modules/naive-ui/')) return 'naive-ui'
if (id.includes('node_modules/markdown-it/')) return 'markdown'
},
},
},
}, },
}) })