feat(KIN-012): UI auto/review mode toggle, autopilot indicator, persist project mode in DB
- TaskDetail: hide Approve/Reject buttons in auto mode, show "Автопилот активен" badge
- TaskDetail: execution_mode persisted per-task via PATCH /api/tasks/{id}
- TaskDetail: loadMode reads DB value, falls back to localStorage per project
- TaskDetail: back navigation preserves status filter via ?back_status query param
- ProjectView: toggleMode now persists to DB via PATCH /api/projects/{id}
- ProjectView: loadMode reads project.execution_mode from DB first
- ProjectView: task list shows 🔓 badge for auto-mode tasks
- ProjectView: status filter synced to URL query param ?status=
- api.ts: add patchProject(), execution_mode field on Project interface
- core/db.py, core/models.py: execution_mode columns + migration for projects & tasks
- web/api.py: PATCH /api/projects/{id} and PATCH /api/tasks/{id} support execution_mode
- tests: 256 tests pass, new test_auto_mode.py with 60+ auto mode tests
- frontend: vitest config added for component tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3cb516193b
commit
4a27bf0693
12 changed files with 2698 additions and 30 deletions
|
|
@ -1,10 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api, type ProjectDetail, type AuditResult } from '../api'
|
||||
import Badge from '../components/Badge.vue'
|
||||
import Modal from '../components/Modal.vue'
|
||||
|
||||
const props = defineProps<{ id: string }>()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const project = ref<ProjectDetail | null>(null)
|
||||
const loading = ref(true)
|
||||
|
|
@ -12,7 +15,7 @@ const error = ref('')
|
|||
const activeTab = ref<'tasks' | 'decisions' | 'modules'>('tasks')
|
||||
|
||||
// Filters
|
||||
const taskStatusFilter = ref('')
|
||||
const taskStatusFilter = ref((route.query.status as string) || '')
|
||||
const decisionTypeFilter = ref('')
|
||||
const decisionSearch = ref('')
|
||||
|
||||
|
|
@ -20,12 +23,22 @@ const decisionSearch = ref('')
|
|||
const autoMode = ref(false)
|
||||
|
||||
function loadMode() {
|
||||
autoMode.value = localStorage.getItem(`kin-mode-${props.id}`) === 'auto'
|
||||
if (project.value?.execution_mode) {
|
||||
autoMode.value = project.value.execution_mode === 'auto'
|
||||
} else {
|
||||
autoMode.value = localStorage.getItem(`kin-mode-${props.id}`) === 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
async function toggleMode() {
|
||||
autoMode.value = !autoMode.value
|
||||
localStorage.setItem(`kin-mode-${props.id}`, autoMode.value ? 'auto' : 'review')
|
||||
try {
|
||||
await api.patchProject(props.id, { execution_mode: autoMode.value ? 'auto' : 'review' })
|
||||
if (project.value) project.value = { ...project.value, execution_mode: autoMode.value ? 'auto' : 'review' }
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
}
|
||||
}
|
||||
|
||||
// Audit
|
||||
|
|
@ -85,6 +98,10 @@ async function load() {
|
|||
}
|
||||
}
|
||||
|
||||
watch(taskStatusFilter, (val) => {
|
||||
router.replace({ query: { ...route.query, status: val || undefined } })
|
||||
})
|
||||
|
||||
onMounted(() => { load(); loadMode() })
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
|
|
@ -267,12 +284,15 @@ async function addDecision() {
|
|||
<div v-if="filteredTasks.length === 0" class="text-gray-600 text-sm">No tasks.</div>
|
||||
<div v-else class="space-y-1">
|
||||
<router-link v-for="t in filteredTasks" :key="t.id"
|
||||
:to="`/task/${t.id}`"
|
||||
:to="{ path: `/task/${t.id}`, query: taskStatusFilter ? { back_status: taskStatusFilter } : undefined }"
|
||||
class="flex items-center justify-between px-3 py-2 border border-gray-800 rounded text-sm hover:border-gray-600 no-underline block transition-colors">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span class="text-gray-500 shrink-0 w-24">{{ t.id }}</span>
|
||||
<Badge :text="t.status" :color="taskStatusColor(t.status)" />
|
||||
<span class="text-gray-300 truncate">{{ t.title }}</span>
|
||||
<span v-if="t.execution_mode === 'auto'"
|
||||
class="text-[10px] px-1 py-0.5 bg-yellow-900/40 text-yellow-400 border border-yellow-800 rounded shrink-0"
|
||||
title="Auto mode">🔓</span>
|
||||
<span v-if="t.parent_task_id" class="text-[10px] text-gray-600 shrink-0">from {{ t.parent_task_id }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-gray-600 shrink-0">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue