新增: 仲裁合并(冲突检测+ConflictResolver+reviewer仲裁基础)
- Coordinator.merge: 跳过失败SubTask+同文件冲突检测+extract_written_files - ConflictResolver.vue: 双栏diff展示+接受A/接受B/合并/手动按钮 - i18n: 冲突相关翻译键(中英文) - 7个merge测试全绿(含4个新增冲突检测)
This commit is contained in:
@@ -811,4 +811,6 @@ export interface ConflictInfo {
|
||||
conflict_type: ConflictType
|
||||
subtask_a: string | null
|
||||
subtask_b: string | null
|
||||
diff_a?: string | null
|
||||
diff_b?: string | null
|
||||
}
|
||||
|
||||
119
src/components/ai/ConflictResolver.vue
Normal file
119
src/components/ai/ConflictResolver.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="conflict-resolver" v-if="conflicts.length > 0">
|
||||
<div class="conflict-header">
|
||||
<span class="conflict-title">⚠ {{ t('aiChat.conflictsPending', { n: conflicts.length }) }}</span>
|
||||
</div>
|
||||
<div v-for="c in conflicts" :key="c.id" class="conflict-item">
|
||||
<div class="conflict-file">
|
||||
<span class="conflict-type-badge" :class="'badge--' + c.conflict_type">
|
||||
{{ c.conflict_type === 'semantic' ? '语义' : '文件' }}
|
||||
</span>
|
||||
<code>{{ c.file_path }}</code>
|
||||
</div>
|
||||
<div class="conflict-diffs" v-if="c.diff_a || c.diff_b">
|
||||
<div class="diff-panel diff-panel--a">
|
||||
<div class="diff-panel-label">Agent A ({{ c.subtask_a || '?' }})</div>
|
||||
<pre class="diff-content">{{ c.diff_a || '(无改动)' }}</pre>
|
||||
</div>
|
||||
<div class="diff-panel diff-panel--b">
|
||||
<div class="diff-panel-label">Agent B ({{ c.subtask_b || '?' }})</div>
|
||||
<pre class="diff-content">{{ c.diff_b || '(无改动)' }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conflict-actions">
|
||||
<button class="btn btn-sm btn-ghost" @click="emit('resolve', { id: c.id, resolution: 'a' })">
|
||||
{{ t('aiChat.acceptA') }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost" @click="emit('resolve', { id: c.id, resolution: 'b' })">
|
||||
{{ t('aiChat.acceptB') }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost" @click="emit('resolve', { id: c.id, resolution: 'merged' })">
|
||||
{{ t('aiChat.mergeBoth') }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost" @click="emit('resolve', { id: c.id, resolution: 'manual' })">
|
||||
{{ t('aiChat.manualResolve') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ConflictInfo } from '@/api/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps<{
|
||||
conflicts: ConflictInfo[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'resolve', payload: { id: string; resolution: string }): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.conflict-resolver {
|
||||
border: 1px solid var(--df-warning, #ffc107);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
margin: 8px 0;
|
||||
background: var(--df-bg-secondary);
|
||||
}
|
||||
.conflict-header {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: var(--df-warning, #ffc107);
|
||||
}
|
||||
.conflict-item {
|
||||
border-top: 1px solid var(--df-border);
|
||||
padding: 8px 0;
|
||||
}
|
||||
.conflict-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.conflict-type-badge {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.badge--file { background: rgba(255, 193, 7, 0.15); color: #ffc107; }
|
||||
.badge--semantic { background: rgba(220, 53, 69, 0.15); color: #dc3545; }
|
||||
.conflict-file code { font-size: 12px; color: var(--df-text); }
|
||||
.conflict-diffs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.diff-panel {
|
||||
border: 1px solid var(--df-border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.diff-panel--a { border-left: 3px solid var(--df-primary); }
|
||||
.diff-panel--b { border-left: 3px solid var(--df-success); }
|
||||
.diff-panel-label {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
background: var(--df-bg);
|
||||
color: var(--df-text-dim);
|
||||
}
|
||||
.diff-content {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
margin: 0;
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.conflict-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,11 @@ export default {
|
||||
// ── Multi-Agent parallel execution ──
|
||||
planProgress: 'Execution Plan',
|
||||
planLayer: 'Layer {n}',
|
||||
conflictsPending: '{n} conflicts to resolve',
|
||||
acceptA: 'Accept A',
|
||||
acceptB: 'Accept B',
|
||||
mergeBoth: 'Merge Both',
|
||||
manualResolve: 'Manual',
|
||||
|
||||
// ── Conversation sidebar ──
|
||||
sidebarTitle: 'Chats',
|
||||
|
||||
@@ -3,6 +3,11 @@ export default {
|
||||
// ── 多 Agent 并行执行 ──
|
||||
planProgress: '执行计划',
|
||||
planLayer: '第 {n} 层',
|
||||
conflictsPending: '{n} 处冲突待处理',
|
||||
acceptA: '接受 A',
|
||||
acceptB: '接受 B',
|
||||
mergeBoth: '合并两者',
|
||||
manualResolve: '手动处理',
|
||||
|
||||
// ── 对话侧栏 ──
|
||||
sidebarTitle: '对话',
|
||||
|
||||
Reference in New Issue
Block a user