preview.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. <template>
  2. <main class="mx-auto max-w-5xl space-y-5 p-6">
  3. <h1 class="text-xl font-semibold">B 方案 · 图片与答案展示验证</h1>
  4. <p class="text-sm text-gray-500">本地合成答复,仅验证真实组件显示;未调用上游账号。</p>
  5. <AccountTrafficControls :account-id="42" />
  6. <button type="button" class="btn btn-secondary" @click="statisticsGroupId = 1">查看分组完整统计(合成数据)</button>
  7. <GroupStatisticsDialog :group-id="statisticsGroupId" @close="statisticsGroupId = null" />
  8. <div class="grid gap-5 sm:grid-cols-2">
  9. <TestResultCard v-for="summary in account.tests" :key="summary.test_type" :account="account" :summary="summary" :now="Date.now()" @detail="detailId = $event" />
  10. </div>
  11. <button class="btn btn-secondary" @click="dark = !dark">切换明暗</button>
  12. <TestSettingsPanel :settings="settings" />
  13. <TestDetailDialog :record-id="detailId" @close="detailId = null" />
  14. </main>
  15. </template>
  16. <script setup lang="ts">
  17. import { ref, watch } from 'vue'
  18. import TestResultCard from '../../src/components/admin/intelligent-tests/TestResultCard.vue'
  19. import TestDetailDialog from '../../src/components/admin/intelligent-tests/TestDetailDialog.vue'
  20. import TestSettingsPanel from '../../src/components/admin/intelligent-tests/TestSettingsPanel.vue'
  21. import AccountTrafficControls from '../../src/components/account/AccountTrafficControls.vue'
  22. import GroupStatisticsDialog from '../../src/components/admin/groups/GroupStatisticsDialog.vue'
  23. import { accountTrafficAPI, defaultTrafficPolicy } from '../../src/api/admin/accountTraffic'
  24. import { intelligentTestsAPI, type TestAccount, type TestRecord, type TestSetting } from '../../src/api/intelligentTests'
  25. const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 420"><rect width="600" height="420" fill="#f0f9ff"/><circle cx="490" cy="70" r="35" fill="#fde68a"/><path d="M30 354 Q280 340 570 354" fill="none" stroke="#b9d4d9" stroke-width="5"/><g fill="none" stroke="#284657" stroke-width="9"><circle cx="170" cy="290" r="65"/><circle cx="425" cy="290" r="65"/><path d="M170 290L242 183L302 290Z M242 183L383 183L302 290 M425 290L383 157L410 146 M222 176L268 176"/></g><path d="M212 165Q187 110 235 101Q280 94 306 124Q321 93 314 59Q332 42 353 64Q369 88 355 120Q347 159 307 181Q265 211 212 165Z" fill="#fffdf8" stroke="#284657" stroke-width="5"/><path d="M230 133Q270 177 305 143Q283 201 230 133Z" fill="#d4e5e7" stroke="#284657" stroke-width="3"/><path d="M352 78L475 107L350 105Z" fill="#f6b544" stroke="#284657" stroke-width="4"/><path d="M350 105Q390 150 475 107" fill="#f9cc70" stroke="#284657" stroke-width="3"/><circle cx="342" cy="72" r="4" fill="#172c36"/><path d="M285 183L314 219L300 275 M274 190L251 222L274 251" fill="none" stroke="#e29732" stroke-width="9" stroke-linecap="round"/><path d="M283 278L321 278" fill="none" stroke="#284657" stroke-width="6"/></svg>`
  26. const base: TestRecord = { id:101, account_id:42, test_type:'pelican',status:'completed',score:null,result:svg,result_image:svg,duration_ms:8420,model:'fixture-text-model',anti_degradation:true,started_at:'2026-09-14T08:00:00Z',finished_at:'2026-09-14T08:00:08Z',created_at:'2026-09-14T08:00:00Z',evaluation:{evaluator_version:2,answer_verdict:'not_evaluated',format_verdict:'compliant',capability_verdict:'insufficient_evidence',reason:'SVG 结构可显示,图像内容尚未评估'},config_snapshot:{execution:{strategy:'legacy',identity_mode:'session',effective_tls:'nodejs24',concurrency:4}},input:'画一只骑自行车的鹈鹕'}
  27. const candy: TestRecord = {...base,id:102,test_type:'candy',score:100,result:'**ANSWER: 12颗**\n最后盒子里有12颗糖。',result_image:'',evaluation:{evaluator_version:2,answer_verdict:'correct',format_verdict:'non_compliant',capability_verdict:'insufficient_evidence',reason:'最终答案与标准答案一致;完整推导未单独验证',format_reason:'格式有差异,答案正确性单独判断',expected_answer:'12',actual_answer:'12颗'}}
  28. const account:TestAccount = {account_id:42,name:'本地演示账号',platform:'openai',account_type:'oauth',account_status:'active',group_ids:[],anti_degradation:true,tests:[{test_type:'pelican',latest:base,latest_completed:base,history_count:1,consecutive_anomalies:0,risk:''},{test_type:'candy',latest:candy,latest_completed:candy,history_count:1,consecutive_anomalies:0,risk:''}]}
  29. intelligentTestsAPI.detail = async id => id === 101 ? base : candy
  30. base.evaluation!.evaluator_version = 3
  31. candy.evaluation!.evaluator_version = 3
  32. ;(base.config_snapshot as {execution:Record<string,unknown>}).execution.integrity_mode = 'observe'
  33. account.tests[1].latest = { ...candy, id:103, status:'queued', queue_reason:'所选模型限流冷却尚未结束,任务延后执行', available_at:new Date(Date.now()+90000).toISOString() }
  34. const settings:TestSetting[] = [{test_type:'candy',name:'糖果测试',enabled:true,user_visible:false,config:{prompt:'盒子里有24颗糖。小明取走总数的四分之一,小红取走剩余一半,放回3颗。',model:'',evaluator:'exact_answer',expected_answer:'12',answer_type:'number',answer_unit:'颗',answer_unit_mode:'configured',answer_format:'answer_line',timeout_seconds:120}}]
  35. intelligentTestsAPI.saveSetting = async setting => setting // fixture only, no API write
  36. const detailId = ref<number|null>(null), dark = ref(false)
  37. const statisticsGroupId = ref<number | null>(null)
  38. let fixturePolicy = defaultTrafficPolicy()
  39. accountTrafficAPI.get = async () => ({ policy: { ...fixturePolicy }, state_available: true, hard_limit: 128, state: { effective_concurrency: 128, recommended_concurrency: 128, in_flight: 0, requests_last_minute: 0, accepted: 0, rejected_rpm: 0, rejected_concurrency: 0, upstream_429: 0, upstream_5xx: 0, completed: 0, average_duration_ms: 0 } })
  40. accountTrafficAPI.save = async (_id, policy) => { fixturePolicy = { ...policy }; return { policy: { ...policy }, state_available: true, hard_limit: 128 } }
  41. watch(dark, value => document.documentElement.classList.toggle('dark',value))
  42. </script>