| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="referrer" content="no-referrer">
- <title>新 Query 搜索与评估 · 案例总览</title>
- <style>
- :root {
- --ink: #24211d;
- --muted: #6f6961;
- --line: #ded8ce;
- --paper: #fbfaf7;
- --panel: #ffffff;
- --mint: #1f8a70;
- --rose: #b24b63;
- --amber: #b87918;
- --cyan: #2a6f8f;
- --soft-mint: #e9f5f0;
- --soft-rose: #f8e9ee;
- --soft-amber: #fff2d9;
- --soft-cyan: #e7f2f7;
- --shadow: 0 18px 45px rgba(41, 35, 28, .08);
- }
- * {
- box-sizing: border-box;
- }
- body {
- margin: 0;
- color: var(--ink);
- background: var(--paper);
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
- line-height: 1.55;
- }
- header {
- padding: 30px 24px 18px;
- border-bottom: 1px solid var(--line);
- background: linear-gradient(180deg, #fff 0%, #fbfaf7 100%);
- }
- .wrap {
- max-width: 1440px;
- margin: 0 auto;
- }
- .eyebrow {
- display: flex;
- gap: 8px;
- align-items: center;
- color: var(--mint);
- font-size: 13px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0;
- }
- h1 {
- margin: 8px 0 4px;
- font-size: clamp(24px, 3.5vw, 36px);
- line-height: 1.1;
- letter-spacing: 0;
- }
- .lede {
- color: var(--muted);
- font-size: 14.5px;
- }
- /* Layout for two-column structure */
- .app-container {
- max-width: 1440px;
- margin: 0 auto;
- padding: 24px;
- display: grid;
- grid-template-columns: 320px 1fr;
- gap: 24px;
- align-items: start;
- }
- @media (max-width: 1024px) {
- .app-container {
- grid-template-columns: 1fr;
- }
- }
- /* Sidebar Styles */
- .sidebar {
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 12px;
- padding: 20px;
- box-shadow: var(--shadow);
- height: fit-content;
- max-height: calc(100vh - 180px);
- overflow-y: auto;
- position: sticky;
- top: 24px;
- }
- .sidebar h3 {
- margin-top: 0;
- font-size: 15px;
- font-weight: 700;
- border-bottom: 1px solid var(--line);
- padding-bottom: 10px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .sidebar-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-top: 12px;
- }
- .sidebar-item {
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 10px 12px;
- background: #fff;
- cursor: pointer;
- transition: all 0.15s ease;
- font-size: 13px;
- font-weight: 500;
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .sidebar-item:hover {
- border-color: #bbb;
- background: #fafaf9;
- }
- .sidebar-item.active {
- border-color: var(--mint);
- background: var(--soft-mint);
- color: var(--ink);
- font-weight: 700;
- box-shadow: inset 3px 0 0 var(--mint);
- }
- .sidebar-item .q-text {
- word-break: break-all;
- line-height: 1.35;
- }
- .sidebar-item .meta {
- font-size: 11px;
- color: var(--muted);
- display: flex;
- justify-content: space-between;
- margin-top: 2px;
- }
- .sidebar-item .meta strong {
- color: var(--mint);
- }
- /* Builder Card styling */
- .builder-card {
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 12px;
- padding: 20px;
- box-shadow: var(--shadow);
- }
- .builder-card h3 {
- margin-top: 0;
- margin-bottom: 16px;
- font-size: 15px;
- font-weight: 700;
- border-bottom: 1px solid var(--line);
- padding-bottom: 10px;
- }
- .dimensions {
- display: flex;
- flex-direction: column;
- }
- .dim-group {
- padding: 4px 0;
- border-bottom: 1px solid #f2f2f2;
- }
- .dim-group:last-child {
- border-bottom: none;
- }
- .level-row {
- display: flex;
- align-items: flex-start;
- padding: 7px 0;
- gap: 10px;
- }
- .level-row.indent {
- margin-left: 78px;
- }
- .dim-label {
- font-size: 12.5px;
- color: #999;
- min-width: 68px;
- padding-top: 6px;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- gap: 3px;
- }
- .dim-label::before {
- content: '·';
- color: #bbb;
- }
- .sub-label {
- font-size: 11.5px;
- color: #bbb;
- min-width: 56px;
- padding-top: 6px;
- flex-shrink: 0;
- }
- .chips-wrap {
- display: flex;
- flex-wrap: wrap;
- gap: 7px;
- align-items: center;
- flex: 1;
- }
- .chip {
- padding: 4px 12px;
- border-radius: 20px;
- font-size: 12.5px;
- cursor: pointer;
- border: 1.5px solid #e2e2e2;
- background: #fff;
- color: #666;
- transition: all .12s;
- user-select: none;
- white-space: nowrap;
- line-height: 1.4;
- }
- .chip:hover {
- border-color: #c0c0c0;
- color: #333;
- }
- .chip.none-active {
- background: #1a1a1a;
- border-color: #1a1a1a;
- color: #fff;
- font-weight: 500;
- }
- .chip.none-active:hover {
- background: #333;
- border-color: #333;
- }
- .chip.selected {
- background: var(--mint);
- border-color: var(--mint);
- color: #fff;
- font-weight: 500;
- }
- .chip.selected:hover {
- opacity: 0.9;
- }
- .placeholder-text {
- font-size: 12.5px;
- color: #ccc;
- font-style: italic;
- padding: 6px 0;
- }
- .preview-section {
- margin-top: 18px;
- padding: 14px 18px;
- background: #fafafa;
- border-radius: 8px;
- border: 1.5px solid #ebebeb;
- }
- .preview-header {
- font-size: 11px;
- color: #aaa;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- margin-bottom: 10px;
- }
- .preview-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- min-height: 34px;
- align-items: center;
- margin-bottom: 10px;
- }
- .preview-tag {
- background: #1a1a1a;
- color: #fff;
- padding: 4px 11px;
- border-radius: 7px;
- font-size: 13px;
- }
- .preview-sep {
- color: #d0d0d0;
- font-size: 14px;
- font-weight: 300;
- }
- .preview-empty {
- color: #ccc;
- font-size: 13px;
- }
- .preview-path {
- font-size: 12px;
- color: #bbb;
- margin-bottom: 12px;
- min-height: 18px;
- font-family: monospace;
- letter-spacing: 0.01em;
- }
- .btn-row {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- }
- .btn {
- border: 1px solid var(--line);
- background: #fff;
- color: var(--ink);
- border-radius: 8px;
- padding: 8px 12px;
- font: inherit;
- cursor: pointer;
- font-size: 13px;
- transition: all 0.15s;
- }
- .btn:hover {
- background: #f5f5f5;
- }
- .btn.active {
- color: #fff;
- background: var(--ink);
- border-color: var(--ink);
- }
- .btn-dark {
- background: #1a1a1a;
- color: #fff;
- border-color: #1a1a1a;
- }
- .btn-dark:hover {
- background: #333;
- border-color: #333;
- }
- /* Console logger styling */
- .exec-console {
- background: #181512;
- color: #dcd3c3;
- border: 1px solid #3d352e;
- border-radius: 12px;
- padding: 16px;
- font-family: ui-monospace, Menlo, Monaco, Consolas, monospace;
- box-shadow: var(--shadow);
- display: flex;
- flex-direction: column;
- height: 320px;
- }
- .exec-console-head {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1px solid #3d352e;
- padding-bottom: 8px;
- margin-bottom: 8px;
- font-size: 12px;
- color: #a3968d;
- }
- .exec-console-output {
- flex: 1;
- margin: 0;
- padding: 4px;
- overflow-y: auto;
- white-space: pre-wrap;
- word-break: break-all;
- font-size: 13px;
- line-height: 1.45;
- color: #e5d8c5;
- }
- /* Stats container styling */
- .stats {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 12px;
- margin-top: 16px;
- }
- .stat {
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 14px;
- box-shadow: var(--shadow);
- min-height: 86px;
- }
- .stat strong {
- display: block;
- font-size: 26px;
- line-height: 1.1;
- }
- .stat span {
- color: var(--muted);
- font-size: 13px;
- }
- /* Filters and Grid */
- .toolbar {
- display: flex;
- gap: 10px;
- align-items: center;
- justify-content: space-between;
- margin-top: 20px;
- margin-bottom: 18px;
- flex-wrap: wrap;
- }
- .filters {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- }
- select {
- border: 1px solid var(--line);
- background: #fff;
- color: var(--ink);
- border-radius: 8px;
- padding: 9px 12px;
- font: inherit;
- min-width: 150px;
- }
- .grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 16px;
- }
- @media (max-width: 768px) {
- .grid {
- grid-template-columns: 1fr;
- }
- }
- /* Results cards */
- .result {
- min-height: 480px;
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 8px;
- overflow: hidden;
- box-shadow: var(--shadow);
- display: flex;
- flex-direction: column;
- position: relative;
- transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.25s ease;
- }
- .result:hover {
- transform: translateY(-2px);
- box-shadow: 0 22px 50px rgba(41, 35, 28, .12);
- }
- .result.discard {
- border-color: rgba(178, 75, 99, 0.15);
- }
- .discard-overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(251, 250, 247, 0.85);
- backdrop-filter: blur(5px);
- -webkit-backdrop-filter: blur(5px);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 20px;
- text-align: center;
- z-index: 5;
- opacity: 1;
- transition: opacity 0.25s ease;
- pointer-events: none;
- }
- .result.discard:hover .discard-overlay {
- opacity: 0;
- }
- .discard-badge {
- background: var(--soft-rose);
- color: var(--rose);
- border: 1px solid rgba(178, 75, 99, 0.25);
- padding: 6px 16px;
- border-radius: 999px;
- font-size: 13px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 1px;
- margin-bottom: 12px;
- box-shadow: 0 2px 8px rgba(178, 75, 99, 0.1);
- }
- .discard-reason {
- color: var(--muted);
- font-size: 13px;
- line-height: 1.6;
- max-width: 90%;
- margin: 0 auto;
- display: -webkit-box;
- -webkit-line-clamp: 6;
- -webkit-box-orient: vertical;
- overflow: hidden;
- font-weight: 500;
- }
- .thumbs {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 2px;
- height: 140px;
- background: #eee7dc;
- overflow: hidden;
- }
- .thumbs img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- background: #eee7dc;
- }
- .thumbs img:first-child:nth-last-child(1) {
- grid-column: 1 / -1;
- }
- .body {
- padding: 15px;
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .meta {
- display: flex;
- justify-content: space-between;
- gap: 10px;
- color: var(--muted);
- font-size: 12px;
- margin-bottom: 8px;
- }
- .platform {
- color: #fff;
- border-radius: 999px;
- padding: 2px 8px;
- font-weight: 700;
- white-space: nowrap;
- }
- .p-xhs { background: var(--rose); }
- .p-gzh { background: var(--mint); }
- .p-zhihu { background: var(--cyan); }
- .p-x { background: var(--cyan); }
- .p-bili { background: #e06d93; }
- .p-douyin { background: #24211d; }
- .p-sph { background: #07c160; }
- .p-youtube { background: #c4302b; }
- .p-github { background: #24292e; }
- .p-toutiao { background: #f04142; }
- .p-weibo { background: #e6162d; }
- h2 {
- margin: 0 0 8px;
- font-size: 17px;
- line-height: 1.3;
- letter-spacing: 0;
- }
- .excerpt {
- color: var(--muted);
- font-size: 13px;
- display: -webkit-box;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- overflow: hidden;
- margin-bottom: 10px;
- }
- .tags {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin: 10px 0;
- }
- .tag {
- background: #f2eee7;
- border-radius: 999px;
- padding: 2px 8px;
- font-size: 11.5px;
- color: #514a42;
- }
- .scorebar {
- margin-top: auto;
- }
- .overall {
- display: flex;
- align-items: end;
- justify-content: space-between;
- border-top: 1px solid var(--line);
- padding-top: 10px;
- }
- .score {
- font-size: 32px;
- line-height: .9;
- font-weight: 800;
- }
- .decision {
- color: var(--mint);
- font-weight: 800;
- }
- .decision.discard {
- color: var(--amber);
- }
- .group-snapshot {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
- gap: 5px;
- margin-top: 8px;
- }
- .group-pill {
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 5px 6px;
- background: #fff;
- min-width: 0;
- }
- .group-pill span {
- display: block;
- color: var(--muted);
- font-size: 11px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .group-pill strong {
- display: block;
- font-size: 14px;
- line-height: 1.1;
- }
- .actions {
- display: flex;
- gap: 8px;
- margin-top: 12px;
- }
- .actions a,
- .actions button {
- flex: 1;
- text-align: center;
- text-decoration: none;
- color: var(--ink);
- background: #fff;
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 8px 10px;
- font-size: 13px;
- }
- /* Dialog styling */
- dialog {
- width: min(980px, calc(100vw - 28px));
- max-height: calc(100vh - 32px);
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 0;
- box-shadow: 0 28px 90px rgba(0, 0, 0, .25);
- }
- dialog::backdrop {
- background: rgba(38, 33, 27, .42);
- }
- dialog.fullscreen {
- width: 100vw !important;
- max-width: 100vw !important;
- height: 100vh !important;
- max-height: 100vh !important;
- border: none !important;
- border-radius: 0 !important;
- margin: 0 !important;
- top: 0 !important;
- left: 0 !important;
- }
- dialog.fullscreen #modalContentProcedure {
- height: calc(100vh - 120px) !important;
- max-height: none !important;
- }
- dialog.fullscreen #modalContentDetail {
- height: calc(100vh - 120px) !important;
- max-height: none !important;
- overflow-y: auto !important;
- }
- .modal-head {
- position: sticky;
- top: 0;
- background: #fff;
- border-bottom: 1px solid var(--line);
- padding: 16px;
- z-index: 2;
- display: flex;
- justify-content: space-between;
- gap: 14px;
- align-items: start;
- }
- .modal-head h3 {
- margin: 0;
- font-size: 20px;
- line-height: 1.25;
- }
- .modal-content {
- display: grid;
- grid-template-columns: 1.1fr .9fr;
- gap: 18px;
- padding: 16px;
- }
- .modal-content > section,
- .modal-content > aside {
- min-width: 0;
- }
- .section-title {
- margin: 18px 0 8px;
- font-weight: 800;
- }
- .raw {
- white-space: pre-wrap;
- background: #faf7f1;
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 12px;
- max-height: 330px;
- overflow: auto;
- color: #3d3831;
- font-size: 13px;
- }
- .images {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 8px;
- }
- .images img {
- width: 100%;
- max-height: 260px;
- object-fit: contain;
- border: 1px solid var(--line);
- border-radius: 8px;
- background: #f1ece4;
- }
- .scores {
- display: grid;
- gap: 12px;
- }
- .score-group {
- border: 1px solid var(--line);
- border-radius: 8px;
- background: #fff;
- overflow: hidden;
- }
- .score-group-head {
- display: flex;
- justify-content: space-between;
- gap: 10px;
- align-items: center;
- padding: 10px 11px;
- background: #faf7f1;
- border-bottom: 1px solid var(--line);
- font-size: 13px;
- font-weight: 800;
- }
- .score-group-head small {
- color: var(--muted);
- font-weight: 700;
- white-space: nowrap;
- }
- .score-group-body {
- display: grid;
- gap: 8px;
- padding: 10px;
- }
- .score-row {
- display: grid;
- grid-template-columns: 128px 1fr 34px;
- gap: 10px;
- align-items: center;
- font-size: 13px;
- }
- .score-row.missing {
- color: #a39b91;
- }
- .score-row.missing .meter span {
- display: none;
- }
- .meter {
- height: 9px;
- border-radius: 999px;
- background: #eee7dc;
- overflow: hidden;
- }
- .meter span {
- display: block;
- height: 100%;
- width: calc(var(--v) * 20%);
- background: var(--rose);
- }
- .rubric-note {
- background: var(--soft-cyan);
- border-left: 4px solid var(--cyan);
- padding: 10px 12px;
- color: #254c5d;
- border-radius: 4px;
- font-size: 13px;
- }
- .modal-tabs {
- display: flex;
- gap: 4px;
- padding: 0 16px;
- border-bottom: 1px solid var(--line);
- background: #faf7f1;
- }
- .modal-tab {
- background: transparent;
- border: none;
- border-bottom: 3px solid transparent;
- border-radius: 0;
- padding: 10px 16px;
- font-size: 14px;
- font-weight: 600;
- color: var(--muted);
- cursor: pointer;
- transition: all 0.2s ease;
- }
- .modal-tab:hover {
- color: var(--ink);
- background: rgba(0, 0, 0, 0.02);
- }
- .modal-tab.active {
- color: var(--mint);
- border-bottom-color: var(--mint);
- }
- /* Rating card styling */
- .sc-card {
- background: #fff;
- border: 1px solid var(--line);
- border-radius: 12px;
- padding: 16px 20px;
- margin-bottom: 16px;
- box-shadow: 0 4px 15px rgba(0,0,0,0.02);
- }
-
- .sc-card-head {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 14px;
- border-bottom: 1px solid #f3f0ea;
- padding-bottom: 10px;
- }
-
- .sc-card-head .title {
- font-size: 15px;
- font-weight: 700;
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .sc-card-head .badge {
- background: #eef2ff;
- color: #2563eb;
- font-size: 11px;
- padding: 2px 6px;
- border-radius: 4px;
- font-weight: 700;
- }
-
- .sc-card-head .avg-score {
- font-size: 13.5px;
- color: var(--muted);
- font-weight: 600;
- }
-
- .sc-card-head .avg-score strong {
- font-size: 24px;
- color: #2563eb;
- font-weight: 800;
- margin-left: 4px;
- }
-
- .sc-sub-header {
- font-size: 12px;
- color: var(--muted);
- font-weight: 700;
- margin: 14px 0 8px;
- border-bottom: 1px dashed #f0ebd8;
- padding-bottom: 4px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
-
- .sc-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 0;
- font-size: 13px;
- gap: 10px;
- }
-
- .sc-row .label {
- color: var(--ink);
- font-weight: 500;
- flex: 1;
- min-width: 100px;
- word-break: break-all;
- }
-
- .sc-row .bar-wrap {
- display: flex;
- align-items: center;
- gap: 10px;
- width: 160px;
- flex-shrink: 0;
- }
-
- .sc-row .bar {
- height: 6px;
- background: #eee7dc;
- border-radius: 999px;
- flex: 1;
- overflow: hidden;
- }
-
- .sc-row .bar-fill {
- height: 100%;
- background: #2563eb;
- border-radius: 999px;
- width: calc(var(--v) * 10%);
- }
-
- .sc-row .value {
- font-weight: 700;
- font-size: 13px;
- width: 20px;
- text-align: right;
- }
-
- .sc-row .info-icon {
- cursor: pointer;
- color: #9ca3af;
- transition: color 0.15s ease;
- font-size: 13px;
- user-select: none;
- }
-
- .sc-row .info-icon:hover {
- color: #3b82f6;
- }
- </style>
- </head>
- <body>
- <header>
- <div class="wrap">
- <div class="eyebrow">Content Search · runs_new/ 实时 · 维度构建器</div>
- <h1>新 Query 搜索与评估</h1>
- <p class="lede" id="lede">正在扫描 runs_new/ 目录...</p>
- </div>
- </header>
- <main class="app-container">
- <!-- Sidebar: previously searched queries -->
- <aside class="sidebar">
- <h3>
- <span>已检索 Query 列表</span>
- <button class="btn" onclick="loadData(true)" style="padding: 3px 8px; font-size: 11px;">刷新</button>
- </h3>
- <div class="sidebar-list" id="querySidebarList">
- <!-- Dynamic sidebar items -->
- </div>
- </aside>
- <!-- Main Workspace -->
- <div style="display: flex; flex-direction: column; gap: 24px; min-width: 0;">
- <!-- Query Builder Panel -->
- <section class="builder-card">
- <h3>Query 词组织器</h3>
- <div class="dimensions" id="dimensions"></div>
- <div class="preview-section">
- <div class="preview-header">Query 预览</div>
- <div class="preview-tags" id="previewTags">
- <span class="preview-empty">(请选择维度标签)</span>
- </div>
- <div class="preview-path" id="previewPath"></div>
-
- <div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; border-top: 1px solid var(--line); padding-top: 12px; margin-top: 12px;">
- <div style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
- <div style="display: flex; align-items: center; gap: 16px;">
- <span style="font-size: 13.5px; font-weight: 700; color: var(--muted);">检索渠道:</span>
- <div style="display: flex; gap: 10px; flex-wrap: wrap;" id="platformCheckboxes">
- <!-- 小红书 -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="xhs" checked onchange="togglePlatformInput(this)"> 小红书
- </label>
- <input type="number" name="platform_count" data-platform="xhs" min="1" max="100" value="20" style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink);">
- </div>
- <!-- 知乎 -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="zhihu" checked onchange="togglePlatformInput(this)"> 知乎
- </label>
- <input type="number" name="platform_count" data-platform="zhihu" min="1" max="100" value="20" style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink);">
- </div>
- <!-- 公众号 -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s; opacity: 0.6;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="gzh" onchange="togglePlatformInput(this)"> 公众号
- </label>
- <input type="number" name="platform_count" data-platform="gzh" min="1" max="100" value="20" disabled style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink); opacity: 0.4;">
- </div>
- <!-- 抖音 -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s; opacity: 0.6;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="douyin" onchange="togglePlatformInput(this)"> 抖音
- </label>
- <input type="number" name="platform_count" data-platform="douyin" min="1" max="100" value="20" disabled style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink); opacity: 0.4;">
- </div>
- <!-- 视频号 -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s; opacity: 0.6;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="sph" onchange="togglePlatformInput(this)"> 视频号
- </label>
- <input type="number" name="platform_count" data-platform="sph" min="1" max="100" value="20" disabled style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink); opacity: 0.4;">
- </div>
- <!-- YouTube -->
- <div style="display: inline-flex; align-items: center; gap: 6px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; transition: opacity 0.15s; opacity: 0.6;">
- <label style="font-size: 13.5px; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-weight: 500;">
- <input type="checkbox" name="platform" value="youtube" onchange="togglePlatformInput(this)"> YouTube
- </label>
- <input type="number" name="platform_count" data-platform="youtube" min="1" max="100" value="20" disabled style="width: 38px; border: 1px solid #d1d5db; border-radius: 4px; padding: 1px 3px; font-size: 11.5px; text-align: center; font-weight: bold; outline: none; color: var(--ink); opacity: 0.4;">
- </div>
- </div>
- </div>
- <div id="channelCountsRow" style="font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap;"></div>
- </div>
- <div class="btn-row">
- <button class="btn btn-dark" id="searchBtn" onclick="runSearchAndEvaluate()" style="background: var(--mint); color: #fff; border-color: var(--mint); font-weight: 700; padding: 6px 18px;">⚡ 搜索并评估</button>
- <button class="btn" onclick="clearAll()">清空</button>
- </div>
- </div>
- </div>
- </section>
- <!-- Subprocess log card -->
- <section class="exec-console" id="execConsoleCard" style="display: none;">
- <div class="exec-console-head">
- <span id="consoleTitle">🚀 搜索并评估任务控制台 - 准备就绪</span>
- <span id="consoleStatus" style="font-weight: bold; color: var(--amber);">idle</span>
- </div>
- <pre class="exec-console-output" id="consoleOutput"></pre>
- </section>
- <!-- Grid Cards Area -->
- <div id="resultsArea" style="display: none;">
- <div class="stats" id="stats"></div>
- <div class="toolbar">
- <div class="filters" id="platformFilterWrap"></div>
-
- <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
- <div class="threshold-control">
- <span style="color: var(--muted); font-size: 12px; font-weight: 700;">相关性过滤阈值:</span>
- <input type="number" id="relThreshold" min="0" max="10" step="0.5" value="4.0"
- oninput="renderGrid(); renderHead();"
- style="width: 55px; border: 1px solid #d1d5db; border-radius: 4px; padding: 2px 6px; font-weight: 700; text-align: center; color: #2563eb; outline: none;">
- </div>
- <button id="reevalBtn" class="btn" onclick="reevalCurrentQuery()" style="background: #faf7f1; color: var(--amber); border-color: rgba(184, 121, 24, 0.3);">♻️ 重新评估当前结果</button>
- <button id="editSpecBtn" class="btn" onclick="openSpecEditor()" style="background: #f0f7f6; color: var(--cyan); border-color: rgba(42, 111, 143, 0.3);">📝 编辑 Spec 提示词</button>
-
- <div style="display: flex; align-items: center; gap: 4px; background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 4px 10px; font-size: 13px; font-weight: 600; box-shadow: var(--shadow); height: 38px;">
- <span style="color: var(--muted); font-size: 12px; font-weight: 700;">提取并发:</span>
- <input type="number" id="batchConcurrency" min="1" max="16" value="4"
- style="width: 40px; border: 1px solid #d1d5db; border-radius: 4px; padding: 2px 4px; font-weight: 700; text-align: center; color: var(--ink); outline: none;">
- </div>
- <button id="batchProcBtn" class="btn" onclick="batchExtractProcedures()" style="background: #f0fdf4; color: var(--mint); border-color: rgba(31, 138, 112, 0.3); font-weight: 600; height: 38px;">⚡ 一键提取工序</button>
-
- <select id="sort" onchange="renderGrid()">
- <option value="score">按综合分排序</option>
- <option value="date">按发布时间排序</option>
- <option value="platform">按平台排序</option>
- </select>
- </div>
- </div>
- <div class="grid" id="grid"></div>
- </div>
- </div>
- </main>
- <!-- Details Dialog Modal -->
- <dialog id="detailDialog">
- <div class="modal-head">
- <div>
- <div id="modalMeta" class="meta"></div>
- <h3 id="modalTitle"></h3>
- </div>
- <div style="display: flex; gap: 8px; align-items: center;">
- <button id="toggleFullscreenBtn" class="btn" onclick="toggleModalFullscreen()" style="padding: 5px 12px; font-weight: 600; font-size: 12.5px;">📺 全屏</button>
- <button class="btn" onclick="detailDialog.close()">关闭</button>
- </div>
- </div>
-
- <div class="modal-tabs" id="modalTabs" style="display: none;">
- <button class="modal-tab active" onclick="switchModalTab('detail')" id="tabDetailBtn">帖子详情</button>
- <button class="modal-tab" onclick="switchModalTab('procedure')" id="tabProcedureBtn">对应工序</button>
- </div>
- <div class="modal-content" id="modalContentDetail">
- <section>
- <div class="rubric-note" id="modalReason"></div>
- <div class="section-title">抓取文本节选</div>
- <div class="raw" id="modalText"></div>
- <div class="section-title">图片预览</div>
- <div class="images" id="modalImages"></div>
- </section>
- <aside>
- <div class="section-title" style="display: flex; justify-content: space-between; align-items: center;">
- <span>评分详情</span>
- <span id="modalOverallScore" style="font-size: 13px; font-weight: 700; color: var(--muted); display: flex; align-items: center; gap: 4px; background: #faf7f1; border: 1px solid var(--line); padding: 3px 10px; border-radius: 6px;">
- 综合评分 <strong style="font-size: 19px; color: #2563eb; font-weight: 900; line-height: 1;" id="modalOverallScoreVal">—</strong>
- </span>
- </div>
- <div class="scores" id="modalScores"></div>
- <div class="section-title">类型 / 命中 query</div>
- <div class="tags" id="modalTags"></div>
- </aside>
- </div>
- <div id="modalContentProcedure" style="display: none; min-height: 500px; height: calc(100vh - 180px); max-height: 700px; padding: 16px; box-sizing: border-box; flex-direction: column;">
- <div id="procActionBar" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line); flex-shrink: 0;">
- <div style="font-size: 14px; font-weight: bold; color: var(--ink);" id="procStatusText">工序状态: 检测中...</div>
- <div style="display: flex; gap: 8px;" id="procActionBtns"></div>
- </div>
-
- <div style="flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column;">
- <div id="procSetupPanel" style="display: none; flex-direction: column; align-items: center; justify-content: center; text-align: center; height: 100%; padding: 40px 20px;">
- <div style="font-size: 36px; margin-bottom: 16px;">✨</div>
- <h4 style="margin: 0 0 10px; font-size: 18px;">提取本帖工序</h4>
- <p style="color: var(--muted); font-size: 14px; max-width: 500px; margin: 0 0 24px;">该帖子目前尚未生成对应的结构化工序。请在下方选择提取引擎和模型,点击开始提取。</p>
-
- <div style="display: flex; gap: 16px; margin-bottom: 24px; text-align: left; background: #fff; border: 1px solid var(--line); padding: 16px; border-radius: 8px; box-shadow: var(--shadow);">
- <div>
- <label style="display: block; font-size: 12px; font-weight: bold; margin-bottom: 6px; color: var(--muted);">提取引擎 (Engine)</label>
- <select id="procEngineSelect" style="min-width: 180px; padding: 6px 10px;" onchange="onProcEngineChange()">
- <option value="cyber_runner">Cyber Runner (自研/OpenRouter)</option>
- <option value="claude_sdk">Claude SDK (OAuth)</option>
- </select>
- </div>
- <div>
- <label style="display: block; font-size: 12px; font-weight: bold; margin-bottom: 6px; color: var(--muted);">AI 模型 (Model)</label>
- <select id="procModelSelect" style="min-width: 240px; padding: 6px 10px;"></select>
- </div>
- </div>
-
- <button id="startProcBtn" class="btn btn-dark" onclick="startProcedureExtraction()" style="background: var(--mint); border-color: var(--mint); padding: 10px 24px; font-size: 15px; font-weight: bold;">开始提取工序</button>
- </div>
-
- <div id="procConsolePanel" style="display: none; flex-direction: column; height: 100%; min-height: 0; background: #1e1b18; border: 1px solid #3d352e; border-radius: 8px; overflow: hidden;">
- <div style="background: #2b2520; padding: 6px 12px; border-bottom: 1px solid #3d352e; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; font-family: monospace; font-size: 12px; color: #a3968d;">
- <span>TERMINAL CONSOLE</span>
- <span id="procConsoleStatus">idle</span>
- </div>
- <pre id="procConsoleOutput" style="flex: 1; margin: 0; padding: 12px; overflow: auto; font-family: ui-monospace, Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.45; color: #cbbba9; background: #1e1b18; white-space: pre-wrap; word-break: break-all;"></pre>
- </div>
-
- <iframe id="procedureIframe" style="display: none; width: 100%; height: 100%; border: 1px solid var(--line); border-radius: 8px; background: #fff;" referrerpolicy="no-referrer"></iframe>
- </div>
- </div>
- </dialog>
- <!-- Spec Prompt Editor Dialog -->
- <dialog id="specEditorDialog" style="width: 850px; max-width: 95%; border: 1px solid var(--line); border-radius: 12px; padding: 0; box-shadow: var(--shadow); background: var(--panel);">
- <div style="display: flex; justify-content: space-between; align-items: center; background: #faf7f1; border-bottom: 1px solid var(--line); padding: 16px 20px;">
- <h3 style="margin: 0; font-size: 18px; color: var(--ink); font-weight: 800;">📝 编辑 Spec 提示词规范</h3>
- <button onclick="document.getElementById('specEditorDialog').close()" class="btn" style="padding: 5px 14px; font-weight: 600; font-size: 12px;">关闭</button>
- </div>
- <div style="padding: 20px;">
- <div style="margin-bottom: 16px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
- <span style="font-size: 13.5px; font-weight: 700; color: var(--ink);">选择提示词文件:</span>
- <select id="specFileSelect" onchange="loadSpecFileContent()" style="padding: 6px 12px; border-radius: 8px; border: 1px solid #d1d5db; outline: none; min-width: 320px; font-family: monospace; font-size: 13px; font-weight: 600;">
- </select>
- <span id="specLoadStatus" style="font-size: 13px; font-weight: 600;"></span>
- </div>
- <div style="position: relative; margin-bottom: 20px;">
- <textarea id="specContentTextarea" style="width: 100%; height: 500px; font-family: ui-monospace, Menlo, Monaco, Consolas, monospace; font-size: 13.5px; line-height: 1.55; padding: 14px; border: 1px solid #d1d5db; border-radius: 8px; box-sizing: border-box; outline: none; background: #fafaf9; color: #1f2937; resize: vertical; border-left: 4px solid var(--cyan);"></textarea>
- </div>
- <div style="display: flex; justify-content: flex-end; gap: 12px; align-items: center; border-top: 1px solid var(--line); padding-top: 16px;">
- <span id="specSaveStatus" style="font-size: 13.5px; font-weight: 700; margin-right: auto;"></span>
- <button onclick="document.getElementById('specEditorDialog').close()" class="btn" style="background: #f3f4f6;">取消</button>
- <button onclick="saveSpecFileContent()" class="btn" style="background: var(--mint); color: #fff; border-color: var(--mint); font-weight: bold;">💾 保存修改</button>
- </div>
- </div>
- </dialog>
- <script>
- const DIMS = [
- {
- "id": "tool_type",
- "label": "工具类型",
- "type": "flat",
- "items": ["AI", "桌面 APP", "云端 Web", "API·CLI", "插件扩展"]
- },
- {
- "id": "substance",
- "label": "实质",
- "type": "flat",
- "items": ["人像", "信息"]
- },
- {
- "id": "form",
- "label": "形式",
- "type": "flat",
- "items": ["真实感", "写实风格", "实景拍摄", "版面设计", "版面解构"]
- },
- {
- "id": "modality",
- "label": "模态",
- "type": "flat",
- "items": ["图片", "视频"]
- },
- {
- "id": "action",
- "label": "动作",
- "type": "hierarchical",
- "data": {
- "获取": {
- "搜索": ["检索", "下载"],
- "查询": ["调取"],
- "录入": ["上传", "拍摄", "录音", "键入"],
- "引用": ["选取"]
- },
- "提取": {
- "物理提取": ["裁切", "抠取", "抽帧"],
- "化学提取": ["识别", "反推", "解构"]
- },
- "生成": {
- "元素生成": ["元素生成"],
- "关系生成": ["数组生成", "结构生成"]
- },
- "修改": {
- "增": ["添加", "叠加"],
- "删": ["抹除", "剪除"],
- "变": ["重述", "风格化", "转换", "替换", "调整", "增强"]
- }
- }
- },
- {
- "id": "type",
- "label": "类型",
- "type": "hierarchical",
- "data": {
- "程序控制类型": {
- "指令": ["提示词", "负向提示词", "描述"],
- "参数": ["生成参数", "规格参数", "模型权重"],
- "评估": ["评分", "评语"],
- "流程": ["工作流", "批处理"]
- },
- "数据复用类型": {
- "原子": ["数字人", "版式"],
- "序列": ["模板"]
- },
- "内容类型": {
- "素材/化学变化": ["参考图", "参考视频", "参考音频", "对标内容", "分镜图", "转场", "蒙版", "控制图", "运动轨迹", "滤镜", "构图布局"],
- "素材/物理变化": ["截图", "视频片段", "转场片段", "关键帧", "音效", "特效"],
- "半成品/序列": ["大纲", "脚本", "分镜脚本", "剪辑脚本", "配音文案"],
- "半成品/原子": ["底图", "样图", "分镜视频"],
- "半成品/组合": ["图层组合", "拼图"],
- "准成品": ["歌词", "配音", "BGM", "字幕", "标题", "正文"],
- "成品": ["成品图", "视频成品", "合成图"]
- },
- "知识类型": {
- "知识库": ["知识库"]
- }
- }
- },
- {
- "id": "suffix",
- "label": "后缀",
- "type": "flat",
- "default": null,
- "items": ["怎么做"]
- }
- ];
- const builderState = {};
- DIMS.forEach(d => { builderState[d.id] = { l0: d.default !== undefined ? d.default : null, l1: null, l2: null }; });
- let DATA = { queries: [], actions: [], types: [], matrix: [] };
- let st = { qi: -1, fi: 0, channel: "all", sort: "score" };
- let VIEW = [];
-
- let currentProcTask = null;
- let procPollInterval = null;
- let isLogViewActive = false;
- let searchPollInterval = null;
- let reevalPollIntervals = {};
- // Chip Rendering functions
- function makeRow(indent) {
- const el = document.createElement('div');
- el.className = 'level-row' + (indent ? ' indent' : '');
- return el;
- }
- function makeLabel(text, small) {
- const el = document.createElement('span');
- el.className = small ? 'sub-label' : 'dim-label';
- el.textContent = text;
- return el;
- }
- function makeChipsWrap() {
- const el = document.createElement('div');
- el.className = 'chips-wrap';
- return el;
- }
- function makeChip(label, cls, onClick) {
- const btn = document.createElement('button');
- btn.className = 'chip ' + cls;
- btn.textContent = label;
- btn.addEventListener('click', onClick);
- return btn;
- }
- function renderDim(dimId) {
- const dim = DIMS.find(d => d.id === dimId);
- const grp = document.querySelector('.dim-group[data-id="' + dimId + '"]');
- grp.innerHTML = '';
- const sel = builderState[dimId];
- if (dim.type === 'flat') {
- const row = makeRow(false);
- row.appendChild(makeLabel(dim.label, false));
- if (!dim.items || !dim.items.length) {
- const ph = document.createElement('span');
- ph.className = 'placeholder-text';
- ph.textContent = '(待填写)';
- row.appendChild(ph);
- } else {
- const chips = makeChipsWrap();
- chips.appendChild(makeChip('无', sel.l0 == null ? 'none-active' : '', () => {
- builderState[dimId].l0 = null; renderDim(dimId); updateQueryPreview();
- }));
- dim.items.forEach(item => {
- chips.appendChild(makeChip(item, sel.l0 === item ? 'selected' : '', () => {
- builderState[dimId].l0 = item; renderDim(dimId); updateQueryPreview();
- }));
- });
- row.appendChild(chips);
- }
- grp.appendChild(row);
- return;
- }
- if (dim.type === 'hierarchical') {
- const row0 = makeRow(false);
- row0.appendChild(makeLabel(dim.label, false));
- const chips0 = makeChipsWrap();
- chips0.appendChild(makeChip('无', sel.l0 == null ? 'none-active' : '', () => {
- builderState[dimId] = { l0: null, l1: null, l2: null }; renderDim(dimId); updateQueryPreview();
- }));
- Object.keys(dim.data).forEach(key => {
- chips0.appendChild(makeChip(key, sel.l0 === key ? 'selected' : '', () => {
- builderState[dimId] = { l0: key, l1: null, l2: null }; renderDim(dimId); updateQueryPreview();
- }));
- });
- row0.appendChild(chips0);
- grp.appendChild(row0);
- if (sel.l0 && dim.data[sel.l0]) {
- const L1keys = Object.keys(dim.data[sel.l0]);
- const row1 = makeRow(true);
- row1.appendChild(makeLabel(sel.l0, true));
- const chips1 = makeChipsWrap();
- chips1.appendChild(makeChip('无', sel.l1 == null ? 'none-active' : '', () => {
- builderState[dimId].l1 = null; builderState[dimId].l2 = null; renderDim(dimId); updateQueryPreview();
- }));
- L1keys.forEach(key => {
- chips1.appendChild(makeChip(key, sel.l1 === key ? 'selected' : '', () => {
- builderState[dimId].l1 = key; builderState[dimId].l2 = null; renderDim(dimId); updateQueryPreview();
- }));
- });
- row1.appendChild(chips1);
- grp.appendChild(row1);
- }
- if (sel.l0 && sel.l1 && dim.data[sel.l0] && dim.data[sel.l0][sel.l1]) {
- const L2items = dim.data[sel.l0][sel.l1];
- const row2 = makeRow(true);
- row2.appendChild(makeLabel(sel.l1, true));
- const chips2 = makeChipsWrap();
- chips2.appendChild(makeChip('无', sel.l2 == null ? 'none-active' : '', () => {
- builderState[dimId].l2 = null; renderDim(dimId); updateQueryPreview();
- }));
- L2items.forEach(item => {
- chips2.appendChild(makeChip(item, sel.l2 === item ? 'selected' : '', () => {
- builderState[dimId].l2 = item; renderDim(dimId); updateQueryPreview();
- }));
- });
- row2.appendChild(chips2);
- grp.appendChild(row2);
- }
- }
- }
- function getSelections() {
- return DIMS.map(dim => {
- const sel = builderState[dim.id];
- if (dim.type === 'flat') {
- return sel.l0 ? { dim: dim.label, path: [sel.l0], val: sel.l0 } : null;
- }
- if (dim.type === 'hierarchical') {
- const path = [sel.l0, sel.l1, sel.l2].filter(Boolean);
- if (!path.length) return null;
- return { dim: dim.label, path, val: path[path.length - 1] };
- }
- return null;
- }).filter(Boolean);
- }
- function updateQueryPreview() {
- const sels = getSelections();
- const tagsEl = document.getElementById('previewTags');
- const pathEl = document.getElementById('previewPath');
- tagsEl.innerHTML = '';
- pathEl.textContent = '';
- if (!sels.length) {
- tagsEl.innerHTML = '<span class="preview-empty">(请选择维度标签)</span>';
- return;
- }
- sels.forEach((s, i) => {
- if (i > 0) {
- const sep = document.createElement('span');
- sep.className = 'preview-sep';
- sep.textContent = '·';
- tagsEl.appendChild(sep);
- }
- const tag = document.createElement('span');
- tag.className = 'preview-tag';
- tag.textContent = s.val;
- tagsEl.appendChild(tag);
- });
- pathEl.textContent = sels.map(s => '[' + s.dim + '] ' + s.path.join(' › ')).join(' ');
- }
- function getQueryText() {
- return getSelections().map(s => s.val).join(' ');
- }
- function clearAll() {
- DIMS.forEach(d => { builderState[d.id] = { l0: null, l1: null, l2: null }; });
- DIMS.forEach(d => renderDim(d.id));
- updateQueryPreview();
- }
- // Sidebar navigation functions
- function renderSidebar() {
- const list = document.getElementById("querySidebarList");
- list.innerHTML = "";
- if (!DATA.queries.length) {
- list.innerHTML = `<div style="color: #ccc; font-size: 13px; text-align: center; padding: 24px 0;">(暂无检索记录,请在上方组织新 Query 进行搜索)</div>`;
- document.getElementById("resultsArea").style.display = "none";
- return;
- }
- DATA.queries.forEach((q, i) => {
- const div = document.createElement("div");
- div.className = "sidebar-item" + (st.qi === i ? " active" : "");
- div.innerHTML = `
- <div class="q-text">${esc(q.original_q)}</div>
- <div class="meta">
- <span>采纳/命中: <strong>${q.hits}</strong></span>
- <span>总帖数: ${q.tot}</span>
- </div>
- `;
- div.onclick = () => {
- st.qi = i;
- st.fi = 0;
- st.channel = "all";
- renderSidebar();
- renderGrid();
- renderHead();
- document.getElementById("resultsArea").style.display = "block";
- };
- list.appendChild(div);
- });
- }
- function togglePlatformInput(chk) {
- const parent = chk.closest('div');
- if (!parent) return;
- const numInput = parent.querySelector('input[type="number"]');
- if (!numInput) return;
- if (chk.checked) {
- numInput.disabled = false;
- numInput.style.opacity = "1";
- parent.style.opacity = "1";
- } else {
- numInput.disabled = true;
- numInput.style.opacity = "0.4";
- parent.style.opacity = "0.6";
- }
- }
- // Search and evaluation trigger & log polling
- function runSearchAndEvaluate() {
- const q = getQueryText();
- if (!q) {
- alert("请先选择标签,组成要检索的 Query 词!");
- return;
- }
-
- const checkboxes = document.querySelectorAll('input[name="platform"]:checked');
- if (checkboxes.length === 0) {
- alert("请至少选择一个目标检索渠道!");
- return;
- }
- const platforms = Array.from(checkboxes).map(c => {
- const parent = c.closest('div');
- const numInput = parent ? parent.querySelector('input[type="number"]') : null;
- const count = numInput ? parseInt(numInput.value) : 20;
- return `${c.value}:${count}`;
- }).join(",");
-
- const searchBtn = document.getElementById("searchBtn");
- searchBtn.disabled = true;
- searchBtn.textContent = "⌛ 正在启动...";
-
- const consoleCard = document.getElementById("execConsoleCard");
- consoleCard.style.display = "flex";
- const consoleOutput = document.getElementById("consoleOutput");
- consoleOutput.textContent = "⏳ 正在向服务器提交搜索评估任务...\n";
-
- const statusSpan = document.getElementById("consoleStatus");
- statusSpan.textContent = "starting";
- statusSpan.style.color = "var(--amber)";
- document.getElementById("consoleTitle").textContent = `🚀 搜索并评估任务控制台 - [${q}]`;
-
- fetch("/api/run_search_eval", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ query: q, platforms: platforms })
- })
- .then(r => r.json().then(d => ({ ok: r.ok, d })))
- .then(({ ok, d }) => {
- if (!ok) {
- searchBtn.disabled = false;
- searchBtn.textContent = "⚡ 搜索并评估";
- statusSpan.textContent = "failed";
- statusSpan.style.color = "var(--rose)";
- consoleOutput.textContent += `❌ 启动失败: ${d.error || "未知错误"}\n`;
- return;
- }
-
- statusSpan.textContent = "running";
- statusSpan.style.color = "#00ff00";
- consoleOutput.textContent += `✓ 搜评流程异步启动完成,正在获取实时日志...\n`;
-
- startSearchPolling(q);
- })
- .catch(err => {
- searchBtn.disabled = false;
- searchBtn.textContent = "⚡ 搜索并评估";
- statusSpan.textContent = "failed";
- statusSpan.style.color = "var(--rose)";
- consoleOutput.textContent += `❌ 请求失败: ${err}\n`;
- });
- }
- function startSearchPolling(q) {
- if (searchPollInterval) clearInterval(searchPollInterval);
-
- const statusSpan = document.getElementById("consoleStatus");
- const consoleOutput = document.getElementById("consoleOutput");
- const searchBtn = document.getElementById("searchBtn");
-
- const poll = () => {
- fetch(`/api/search_eval_status?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(d => {
- if (d.status === "success") {
- clearInterval(searchPollInterval);
- searchPollInterval = null;
-
- statusSpan.textContent = "success";
- statusSpan.style.color = "var(--mint)";
- searchBtn.disabled = false;
- searchBtn.textContent = "⚡ 搜索并评估";
-
- consoleOutput.textContent += "\n🎉 搜评流程全部成功执行完毕,已保存于 runs_new 下!\n";
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
-
- loadData(true);
- } else if (d.status === "failed") {
- clearInterval(searchPollInterval);
- searchPollInterval = null;
-
- statusSpan.textContent = "failed";
- statusSpan.style.color = "var(--rose)";
- searchBtn.disabled = false;
- searchBtn.textContent = "⚡ 搜索并评估";
-
- consoleOutput.textContent += `\n❌ 搜评任务执行失败: ${d.error || "未知原因"}\n`;
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
-
- loadData(true);
- }
- });
-
- fetch(`/api/search_eval_log?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(d => {
- if (d.log) {
- consoleOutput.textContent = d.log;
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
- }
- });
- };
-
- poll();
- searchPollInterval = setInterval(poll, 1500);
- }
- // Grid rendering logic
- function curForm() {
- return st.qi === -1 ? null : (DATA.queries[st.qi] ? DATA.queries[st.qi].forms[st.fi] : null);
- }
- function isItemDiscarded(it) {
- if (it.anomaly) return false;
- const input = document.getElementById("relThreshold");
- const userThreshold = input ? parseFloat(input.value) : 4.0;
-
- let isDiscard = false;
- const relVal = it.production_relevance !== null && it.production_relevance !== undefined ? parseFloat(it.production_relevance) : null;
- if (relVal !== null && !isNaN(relVal)) {
- if (relVal < userThreshold) {
- isDiscard = true;
- }
- }
-
- if (it.recency_hard !== null && it.recency_hard !== undefined && it.recency_hard < 2) {
- isDiscard = true;
- }
-
- if (it.overall !== null && it.overall !== undefined) {
- if (it.overall < 6.0) {
- isDiscard = true;
- }
- }
- return isDiscard;
- }
- function updateThresholdLimits() {
- const input = document.getElementById("relThreshold");
- if (input) {
- input.max = "10";
- input.value = "4.0";
- input.step = "0.5";
- }
- }
- function updateChannelCounts() {
- const f = curForm();
- const div = document.getElementById("channelCountsRow");
- if (!div) return;
- if (!f || !f.results || f.results.length === 0) {
- div.innerHTML = "";
- return;
- }
- const counts = {};
- f.results.forEach(r => {
- const pk = r.platformKey || "other";
- counts[pk] = (counts[pk] || 0) + 1;
- });
- const parts = [];
- const sortedKeys = Object.keys(counts).sort((a, b) => counts[b] - counts[a]);
- sortedKeys.forEach(pk => {
- const name = PLATC[pk] || pk;
- parts.push(`
- <span style="background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; display: inline-flex; align-items: center; gap: 6px; font-weight: 500; font-size: 11.5px; box-shadow: 0 1px 3px rgba(0,0,0,0.02);">
- <span class="platform p-${pk}" style="width: 7px; height: 7px; border-radius: 50%; display: inline-block; padding: 0; margin-right: 0;"></span>
- <span style="color: var(--muted);">${name}</span>
- <strong style="color: var(--ink); font-weight: 700;">${counts[pk]}</strong>
- </span>
- `);
- });
- div.innerHTML = `<span style="font-weight: 700; color: var(--muted); margin-right: 4px; font-size: 12px;">已收录帖数:</span>` + parts.join("");
- }
- function renderHead() {
- const f = curForm();
- const div = document.getElementById("stats");
- if (!f) {
- div.innerHTML = "";
- const cDiv = document.getElementById("channelCountsRow");
- if (cDiv) cDiv.innerHTML = "";
- return;
- }
-
- const total = f.results.length;
- const report = f.results.filter(r => !r.anomaly && !isItemDiscarded(r)).length;
- const discard = f.results.filter(r => !r.anomaly && isItemDiscarded(r)).length;
- const failed = f.results.filter(r => r.anomaly).length;
- div.innerHTML = `
- <div class="stat"><strong>${total}</strong><span>平台抓取总数</span></div>
- <div class="stat" style="color:var(--mint)"><strong>${report}</strong><span>采纳上报案例</span></div>
- <div class="stat" style="color:var(--amber)"><strong>${discard}</strong><span>不符过滤弃用</span></div>
- <div class="stat" style="color:var(--rose)"><strong>${failed}</strong><span>评估失败/异常</span></div>
- `;
- updateChannelCounts();
- }
- function renderGrid() {
- const f = curForm();
- const grid = document.getElementById("grid");
- if (!f) { grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;color:#999;padding:40px 0;">暂无数据</div>`; return; }
-
- // Filter platforms checkboxes and render buttons
- const platforms = Array.from(new Set(f.results.map(r => r.platformKey)));
- const filterWrap = document.getElementById("platformFilterWrap");
-
- let pFilterHtml = `<button class="btn ${st.channel === 'all' ? 'active' : ''}" onclick="setChannel('all')">全部渠道 (${f.results.length})</button>`;
- platforms.forEach(p => {
- const count = f.results.filter(r => r.platformKey === p).length;
- pFilterHtml += `<button class="btn ${st.channel === p ? 'active' : ''}" onclick="setChannel('${p}')">${PLATC[p] || p} (${count})</button>`;
- });
- filterWrap.innerHTML = pFilterHtml;
- let list = f.results;
- if (st.channel !== "all") {
- list = list.filter(r => r.platformKey === st.channel);
- }
- // Sort
- const sortBy = document.getElementById("sort").value;
- if (sortBy === "score") {
- list.sort((a, b) => (b.overall || 0) - (a.overall || 0));
- } else if (sortBy === "date") {
- list.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
- } else if (sortBy === "platform") {
- list.sort((a, b) => (a.platformKey || "").localeCompare(b.platformKey || ""));
- }
- if (list.length === 0) {
- grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;color:#999;padding:40px 0;">无筛选结果</div>`;
- return;
- }
- grid.innerHTML = list.map((it, i) => {
- const idx = f.results.indexOf(it);
- const isDiscard = isItemDiscarded(it);
- const overallStr = it.overall !== null && it.overall !== undefined ? it.overall.toFixed(1) : '—';
- const discardOverlay = (isDiscard && !it.anomaly) ? `
- <div class="discard-overlay">
- <div class="discard-badge">已过滤</div>
- <div class="discard-reason">${esc(it.reason || "时效性或综合质量均分未达标")}</div>
- </div>
- ` : '';
- const thumbs = it.images && it.images.length > 0 ? `
- <div class="thumbs">
- ${it.images.map(img => `<img src="${esc(img)}" referrerpolicy="no-referrer" onerror="this.src='${NOIMG}'">`).join('')}
- </div>
- ` : '';
- return `
- <div class="result ${isDiscard ? 'discard' : ''}" data-idx="${idx}">
- ${discardOverlay}
- ${thumbs}
- <div class="body">
- <div class="meta">
- <span class="platform p-${it.platformKey}">${esc(it.platform)}</span>
- <span>${esc(it.date)}</span>
- </div>
- <h2 title="${esc(it.title)}">${esc(it.title)}</h2>
- <div class="excerpt">${esc(it.text)}</div>
-
- <div class="tags">
- ${(it.tools || []).map(t => `<span class="tag">${esc(t)}</span>`).join('')}
- </div>
- <div class="scorebar">
- <div class="overall">
- <span class="decision ${isDiscard ? 'discard' : ''}">${isDiscard ? '已过滤' : '上报'}</span>
- <span class="score" style="color: ${it.anomaly ? 'var(--rose)' : '#2563eb'}">${overallStr}</span>
- </div>
- <div class="group-snapshot">
- ${groupSnapshot(it)}
- </div>
- <div class="actions">
- <a href="${esc(it.url)}" target="_blank" onclick="event.stopPropagation()">🌐 源链接</a>
- <button onclick="event.stopPropagation(); showDetail(${idx})">🔍 查看详情</button>
- </div>
- </div>
- </div>
- </div>
- `;
- }).join('');
- }
- function setChannel(ch) {
- st.channel = ch;
- renderGrid();
- }
- // Modal dialogue script
- function esc(s) {
- return (s === undefined || s === null ? "" : String(s)).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
- }
- const PLATC = { xhs: "小红书", gzh: "公众号", zhihu: "知乎", x: "X", bili: "B站", douyin: "抖音", sph: "视频号", youtube: "YouTube", github: "GitHub", toutiao: "头条", weibo: "微博" };
- const NOIMG = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400'%3E%3Crect width='600' height='400' fill='%23eee7dc'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%236f6961' font-size='28'%3ENo image%3C/text%3E%3C/svg%3E";
- const scoreGroupsNew = [
- { id: "relevance", title: "相关性", short: "相关", keys: ["relevance_production", "relevance_query"] },
- { id: "fixed", title: "固定维度", short: "固定", keys: ["recency", "popularity", "feedback"] },
- { id: "usecase", title: "用例维度", short: "用例", keys: ["realism", "expressiveness"] },
- { id: "dynamic", title: "动态维度", short: "动态", keys: [] }
- ];
- function makeModalRow(label, scoreKey, it) {
- const rawV = it.scores[scoreKey];
- const v = (rawV !== undefined && rawV !== null) ? parseFloat(rawV) : NaN;
- const hasScore = !isNaN(v);
- const valStr = hasScore ? (Number.isInteger(v) ? v : v.toFixed(1)) : '-';
- const barV = hasScore ? v : 0;
- const reason = it.score_reasons ? it.score_reasons[scoreKey] : '';
- const infoIcon = reason ? `<span class="info-icon" onclick="pinScoreReason(this, '${esc(label)}', '${esc(scoreKey)}')" title="评判理由: ${esc(reason)}" style="margin-left: 5px; cursor: pointer; color: var(--muted); opacity: 0.7;">ⓘ</span>` : '';
- return `
- <div class="sc-row ${!hasScore ? 'missing' : ''}">
- <span class="label">${esc(label)}</span>
- <div class="bar-wrap">
- <div class="bar">
- <div class="bar-fill" style="--v: ${barV}"></div>
- </div>
- <span class="value">${valStr}</span>
- ${infoIcon}
- </div>
- </div>
- `;
- }
- function pinScoreReason(el, label, key) {
- alert(`${label} 判定理由:\n\n${el.title.replace("评判理由: ", "")}`);
- }
- function groupAverage(it, g) {
- if (!it.scores) return null;
- let keys = g.keys;
- if (g.id === "dynamic") {
- keys = [];
- if (it.knowledge_type && (it.knowledge_type.includes("procedure") || it.knowledge_type.includes("工序"))) {
- keys.push("procedure_completeness", "procedure_input", "procedure_implementation", "procedure_output", "procedure_generality");
- }
- if (it.knowledge_type && (it.knowledge_type.includes("step") || it.knowledge_type.includes("能力") || it.knowledge_type.includes("步骤"))) {
- keys.push("step_input", "step_implementation", "step_output", "step_generality");
- }
- if (it.knowledge_type && (it.knowledge_type.includes("tool") || it.knowledge_type.includes("工具"))) {
- keys.push("tool_boundary", "tool_comparison", "tool_specificity", "tool_example", "tool_limits");
- }
- }
- const vs = keys.map(k => parseFloat(it.scores[k])).filter(v => !isNaN(v));
- return vs.length ? vs.reduce((a, b) => a + b, 0) / vs.length : null;
- }
- function getQualityAverage(it) {
- if (!it.scores) return null;
- const keys = ["recency", "popularity", "feedback", "realism", "expressiveness"];
- if (it.knowledge_type && (it.knowledge_type.includes("procedure") || it.knowledge_type.includes("工序"))) {
- keys.push("procedure_completeness", "procedure_input", "procedure_implementation", "procedure_output", "procedure_generality");
- }
- if (it.knowledge_type && (it.knowledge_type.includes("step") || it.knowledge_type.includes("能力") || it.knowledge_type.includes("步骤"))) {
- keys.push("step_input", "step_implementation", "step_output", "step_generality");
- }
- if (it.knowledge_type && (it.knowledge_type.includes("tool") || it.knowledge_type.includes("工具"))) {
- keys.push("tool_boundary", "tool_comparison", "tool_specificity", "tool_example", "tool_limits");
- }
- const vs = keys.map(k => parseFloat(it.scores[k])).filter(v => !isNaN(v));
- return vs.length ? vs.reduce((a, b) => a + b, 0) / vs.length : null;
- }
- function fmt(v) {
- return v === null ? "N/A" : v.toFixed(1);
- }
- function groupSnapshot(it) {
- const relAvg = groupAverage(it, scoreGroupsNew[0]);
- const qualAvg = getQualityAverage(it);
- return `
- <div class="group-pill"><span>相关度</span><strong>${fmt(relAvg)}</strong></div>
- <div class="group-pill"><span>制作质量</span><strong>${fmt(qualAvg)}</strong></div>
- `;
- }
- function renderNewScores(it) {
- const relAvg = groupAverage(it, scoreGroupsNew[0]);
- const relAvgStr = relAvg !== null ? relAvg.toFixed(1) : 'N/A';
-
- let relevanceHtml = `
- <div class="sc-card">
- <div class="sc-card-head">
- <div class="title"><span class="badge">01</span>相关性</div>
- <div class="avg-score">均分 <strong>${relAvgStr}</strong><span style="font-size:12px;color:#9ca3af;">/10</span></div>
- </div>
- <div class="sc-card-body">
- ${makeModalRow("和内容制作知识相关", "relevance_production", it)}
- ${makeModalRow("和 query 相关", "relevance_query", it)}
- </div>
- </div>
- `;
- const qualAvg = getQualityAverage(it);
- const qualAvgStr = qualAvg !== null ? qualAvg.toFixed(1) : 'N/A';
- let qualityHtml = `
- <div class="sc-card">
- <div class="sc-card-head">
- <div class="title"><span class="badge">02</span>质量</div>
- <div class="avg-score">均分 <strong>${qualAvgStr}</strong><span style="font-size:12px;color:#9ca3af;">/10</span></div>
- </div>
- <div class="sc-card-body">
- <div class="sc-sub-header">固定维度</div>
- ${makeModalRow("时效性", "recency", it)}
- ${makeModalRow("热度性", "popularity", it)}
- ${makeModalRow("评论反馈", "feedback", it)}
-
- <div class="sc-sub-header">用例</div>
- ${makeModalRow("真实感 (非AI)", "realism", it)}
- ${makeModalRow("表现力", "expressiveness", it)}
- `;
- let dynamicHtml = '';
- if (it.knowledge_type && (it.knowledge_type.includes("procedure") || it.knowledge_type.includes("工序"))) {
- dynamicHtml += `
- <div class="sc-sub-header">工序</div>
- ${makeModalRow("流程完整性", "procedure_completeness", it)}
- ${makeModalRow("输入完整性", "procedure_input", it)}
- ${makeModalRow("实现完整性", "procedure_implementation", it)}
- ${makeModalRow("输出完整性", "procedure_output", it)}
- ${makeModalRow("泛化性", "procedure_generality", it)}
- `;
- }
- if (it.knowledge_type && (it.knowledge_type.includes("step") || it.knowledge_type.includes("能力") || it.knowledge_type.includes("步骤"))) {
- dynamicHtml += `
- <div class="sc-sub-header">能力</div>
- ${makeModalRow("输入完整性", "step_input", it)}
- ${makeModalRow("实现完整性", "step_implementation", it)}
- ${makeModalRow("输出完整性", "step_output", it)}
- ${makeModalRow("泛化性", "step_generality", it)}
- `;
- }
- if (it.knowledge_type && (it.knowledge_type.includes("tool") || it.knowledge_type.includes("工具"))) {
- dynamicHtml += `
- <div class="sc-sub-header">工具</div>
- ${makeModalRow("能力边界覆盖", "tool_boundary", it)}
- ${makeModalRow("有效比较", "tool_comparison", it)}
- ${makeModalRow("参数/接口具体性", "tool_specificity", it)}
- ${makeModalRow("实操示例", "tool_example", it)}
- ${makeModalRow("版本&限制", "tool_limits", it)}
- `;
- }
- qualityHtml += dynamicHtml + `
- </div>
- </div>
- `;
- return relevanceHtml + qualityHtml;
- }
- let detailDialogIndex = -1;
- function showDetail(idx) {
- detailDialogIndex = idx;
- const f = curForm();
- const it = f.results[idx];
-
- const isDiscard = isItemDiscarded(it);
- const overallStr = it.overall !== null && it.overall !== undefined ? it.overall.toFixed(1) : '—';
-
- document.getElementById("modalMeta").innerHTML = `
- <span class="platform p-${it.platformKey}">${esc(it.platform)}</span>
- <span>发布日期: ${esc(it.date)}</span>
- <span style="margin-left:auto; font-family: monospace;">CaseID: ${esc(it.case_id)}</span>
- `;
- document.getElementById("modalTitle").textContent = it.title;
- document.getElementById("modalReason").textContent = it.reason || "时效与品质良好";
- document.getElementById("modalText").textContent = it.text;
-
- document.getElementById("modalOverallScoreVal").textContent = overallStr;
- document.getElementById("modalOverallScoreVal").style.color = it.anomaly ? "var(--rose)" : "#2563eb";
-
- document.getElementById("modalScores").innerHTML = renderNewScores(it);
-
- const tags = (it.tools || []).map(t => `<span class="tag">${esc(t)}</span>`).join("");
- const foundBy = (it.found_by || []).map(q => `<span class="tag" style="background:#e0f2fe; color:#0369a1;">${esc(q)}</span>`).join("");
- document.getElementById("modalTags").innerHTML = tags + foundBy;
-
- const imgDiv = document.getElementById("modalImages");
- imgDiv.innerHTML = "";
- if (it.images && it.images.length > 0) {
- it.images.forEach(img => {
- imgDiv.innerHTML += `<img src="${esc(img)}" referrerpolicy="no-referrer" onclick="window.open(this.src)" style="cursor:zoom-in;">`;
- });
- } else {
- imgDiv.innerHTML = `<div style="grid-column:1/-1; color:#999; font-size:13px;">无图片</div>`;
- }
- const tabs = document.getElementById("modalTabs");
- tabs.style.display = "flex";
-
- switchModalTab("detail");
-
- const dialog = document.getElementById("detailDialog");
- dialog.classList.remove("fullscreen");
- document.getElementById("toggleFullscreenBtn").textContent = "📺 全屏";
- dialog.showModal();
- }
- function switchModalTab(tab) {
- const dBtn = document.getElementById("tabDetailBtn");
- const pBtn = document.getElementById("tabProcedureBtn");
- const dContent = document.getElementById("modalContentDetail");
- const pContent = document.getElementById("modalContentProcedure");
- if (tab === "detail") {
- dBtn.classList.add("active");
- pBtn.classList.remove("active");
- dContent.style.display = "grid";
- pContent.style.display = "none";
- } else {
- pBtn.classList.add("active");
- dBtn.classList.remove("active");
- dContent.style.display = "none";
- pContent.style.display = "flex";
-
- loadProcedureState();
- }
- }
- function toggleModalFullscreen() {
- const dialog = document.getElementById("detailDialog");
- const btn = document.getElementById("toggleFullscreenBtn");
- if (dialog.classList.contains("fullscreen")) {
- dialog.classList.remove("fullscreen");
- btn.textContent = "📺 全屏";
- } else {
- dialog.classList.add("fullscreen");
- btn.textContent = "📺 退出全屏";
- }
- }
- // Procedure workflow extraction & terminal logger
- function loadProcedureState() {
- if (procPollInterval) clearInterval(procPollInterval);
-
- const f = curForm();
- const it = f.results[detailDialogIndex];
- const q = DATA.queries[st.qi].key;
- const form = "A";
- const caseId = it.case_id;
- const setupPanel = document.getElementById("procSetupPanel");
- const consolePanel = document.getElementById("procConsolePanel");
- const iframe = document.getElementById("procedureIframe");
- const statusText = document.getElementById("procStatusText");
- const actionBtns = document.getElementById("procActionBtns");
- setupPanel.style.display = "none";
- consolePanel.style.display = "none";
- iframe.style.display = "none";
- actionBtns.innerHTML = "";
- statusText.textContent = "正在检测工序状态...";
- fetch(`/api/procedure_status?q=${encodeURIComponent(q)}&form=${form}&case_id=${encodeURIComponent(caseId)}`)
- .then(r => r.json())
- .then(d => {
- if (d.status === "success") {
- statusText.textContent = "工序状态: 已提取";
- iframe.src = "/" + d.procedure_html;
- iframe.style.display = "block";
-
- actionBtns.innerHTML = `
- <button class="btn" onclick="showProcSetupPanel()">♻️ 重新生成工序</button>
- <button class="btn" onclick="openProcLog('${esc(q)}', '${form}', '${esc(caseId)}')">📋 提取日志</button>
- `;
- } else if (d.status === "running") {
- statusText.textContent = "工序状态: 正在提取中...";
- consolePanel.style.display = "flex";
- pollProcLog(q, form, caseId);
- } else if (d.status === "failed") {
- statusText.textContent = "工序状态: 提取失败";
- consolePanel.style.display = "flex";
- document.getElementById("procConsoleOutput").textContent = d.error || "提取进程异常退出。";
- actionBtns.innerHTML = `
- <button class="btn" onclick="showProcSetupPanel()">♻️ 重试提取</button>
- <button class="btn" onclick="openProcLog('${esc(q)}', '${form}', '${esc(caseId)}')">📋 提取日志</button>
- `;
- } else {
- // not_started
- statusText.textContent = "工序状态: 未提取";
- showProcSetupPanel();
- }
- });
- }
- function showProcSetupPanel() {
- document.getElementById("procSetupPanel").style.display = "flex";
- document.getElementById("procConsolePanel").style.display = "none";
- document.getElementById("procedureIframe").style.display = "none";
-
- const engine = document.getElementById("procEngineSelect").value;
- populateModels(engine);
- }
- const MODELS_BY_ENGINE = {
- cyber_runner: [
- "google/gemini-3.1-flash-lite",
- "google/gemini-3.5-flash",
- "anthropic/claude-3.5-haiku",
- "anthropic/claude-3.5-sonnet",
- "qwen/qwen-2.5-72b-instruct"
- ],
- claude_sdk: [
- "claude-3-5-sonnet-20241022",
- "claude-3-5-haiku-20241022"
- ]
- };
- function populateModels(engine) {
- const select = document.getElementById("procModelSelect");
- const models = MODELS_BY_ENGINE[engine] || [];
- select.innerHTML = models.map(m => `<option value="${esc(m)}">${esc(m)}</option>`).join("");
- }
- function onProcEngineChange() {
- const engine = document.getElementById("procEngineSelect").value;
- populateModels(engine);
- }
- function startProcedureExtraction() {
- const f = curForm();
- const it = f.results[detailDialogIndex];
- const q = DATA.queries[st.qi].key;
- const form = "A";
- const caseId = it.case_id;
- const engine = document.getElementById("procEngineSelect").value;
- const model = document.getElementById("procModelSelect").value;
- document.getElementById("procSetupPanel").style.display = "none";
- document.getElementById("procConsolePanel").style.display = "flex";
- document.getElementById("procStatusText").textContent = "工序状态: 启动提取任务...";
- fetch("/api/generate_procedure", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ q, form, case_id: caseId, engine, model })
- })
- .then(r => r.json())
- .then(d => {
- if (d.status === "started") {
- pollProcLog(q, form, caseId);
- } else {
- alert("提取任务启动失败: " + d.error);
- showProcSetupPanel();
- }
- })
- .catch(err => {
- alert("网络请求出错: " + err);
- showProcSetupPanel();
- });
- }
- function pollProcLog(q, form, caseId) {
- if (procPollInterval) clearInterval(procPollInterval);
-
- const consoleOutput = document.getElementById("procConsoleOutput");
- const statusText = document.getElementById("procStatusText");
- const consoleStatus = document.getElementById("procConsoleStatus");
- consoleStatus.textContent = "streaming";
- consoleStatus.style.color = "#00ff00";
- const poll = () => {
- fetch(`/api/procedure_status?q=${encodeURIComponent(q)}&form=${form}&case_id=${encodeURIComponent(caseId)}`)
- .then(r => r.json())
- .then(d => {
- if (d.status === "success") {
- clearInterval(procPollInterval);
- procPollInterval = null;
- loadProcedureState();
- } else if (d.status === "failed") {
- clearInterval(procPollInterval);
- procPollInterval = null;
- statusText.textContent = "工序状态: 提取失败";
- consoleOutput.textContent += `\n❌ 进程异常退出: ${d.error || "未知原因"}`;
- consoleStatus.textContent = "failed";
- consoleStatus.style.color = "var(--rose)";
-
- document.getElementById("procActionBtns").innerHTML = `
- <button class="btn" onclick="showProcSetupPanel()">♻️ 重试提取</button>
- <button class="btn" onclick="openProcLog('${esc(q)}', '${form}', '${esc(caseId)}')">📋 提取日志</button>
- `;
- }
- });
- fetch(`/api/procedure_log?q=${encodeURIComponent(q)}&form=${form}&case_id=${encodeURIComponent(caseId)}`)
- .then(r => r.json())
- .then(d => {
- if (d.log) {
- consoleOutput.textContent = d.log;
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
- }
- });
- };
- poll();
- procPollInterval = setInterval(poll, 1500);
- }
- function openProcLog(q, form, caseId) {
- const win = window.open("", "_blank");
- win.document.write("<h3>加载日志中...</h3>");
- fetch(`/api/procedure_log?q=${encodeURIComponent(q)}&form=${form}&case_id=${encodeURIComponent(caseId)}`)
- .then(r => r.json())
- .then(d => {
- win.document.body.innerHTML = "";
- const pre = win.document.createElement("pre");
- pre.style.whiteSpace = "pre-wrap";
- pre.style.fontFamily = "monospace";
- pre.textContent = d.log || "无日志输出";
- win.document.body.appendChild(pre);
- })
- .catch(err => {
- win.document.body.innerHTML = "<h3>日志加载出错: " + err + "</h3>";
- });
- }
- // Spec prompt editor script
- const ALLOWED_SPEC_FILES = [
- "README.md",
- "tools.md",
- "extraction/phase1-skeleton.md",
- "extraction/phase2-normalize.md",
- "extraction/phase3-finalize.md",
- "taxonomy/type_suggestions.md"
- ];
- function openSpecEditor() {
- const select = document.getElementById("specFileSelect");
- select.innerHTML = ALLOWED_SPEC_FILES.map(f => `<option value="${esc(f)}">${esc(f)}</option>`).join("");
-
- document.getElementById("specLoadStatus").textContent = "";
- document.getElementById("specSaveStatus").textContent = "";
- document.getElementById("specContentTextarea").value = "";
-
- const dialog = document.getElementById("specEditorDialog");
- dialog.showModal();
- loadSpecFileContent();
- }
- function loadSpecFileContent() {
- const file = document.getElementById("specFileSelect").value;
- const status = document.getElementById("specLoadStatus");
- const textarea = document.getElementById("specContentTextarea");
-
- status.textContent = "⏳ 正在读取...";
- status.style.color = "var(--amber)";
- textarea.disabled = true;
-
- fetch(`/api/spec_content?file=${encodeURIComponent(file)}`)
- .then(r => r.json().then(d => ({ ok: r.ok, d })))
- .then(({ ok, d }) => {
- textarea.disabled = false;
- if (ok) {
- textarea.value = d.content || "";
- status.textContent = "✓ 读取成功";
- status.style.color = "var(--mint)";
- } else {
- status.textContent = "❌ 读取失败: " + (d.error || "未知错误");
- status.style.color = "var(--rose)";
- }
- })
- .catch(err => {
- textarea.disabled = false;
- status.textContent = "❌ 读取失败: " + err;
- status.style.color = "var(--rose)";
- });
- }
- function saveSpecFileContent() {
- const file = document.getElementById("specFileSelect").value;
- const content = document.getElementById("specContentTextarea").value;
- const status = document.getElementById("specSaveStatus");
-
- status.textContent = "⏳ 正在保存到磁盘...";
- status.style.color = "var(--amber)";
-
- fetch("/api/save_spec", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ file, content })
- })
- .then(r => r.json().then(d => ({ ok: r.ok, d })))
- .then(({ ok, d }) => {
- if (ok) {
- status.textContent = "✅ 保存成功!新的 Prompt 规则已在本地生效。";
- status.style.color = "var(--mint)";
- setTimeout(() => {
- if (status.textContent.includes("保存成功")) {
- status.textContent = "";
- }
- }, 4000);
- } else {
- status.textContent = "❌ 保存失败: " + (d.error || "未知错误");
- status.style.color = "var(--rose)";
- }
- })
- .catch(err => {
- status.textContent = "❌ 网络错误: " + err;
- status.style.color = "var(--rose)";
- });
- }
- // Re-eval current query result cases
- function reevalCurrentQuery() {
- if (st.qi === -1 || !DATA.queries[st.qi]) return;
- const q = DATA.queries[st.qi].key;
- const btn = document.getElementById("reevalBtn");
-
- btn.disabled = true;
- btn.textContent = `♻️ 重评中 ${q}...`;
- fetch("/api/reeval", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ q })
- })
- .then(r => r.json())
- .then(d => {
- if (d.status === "started") {
- startReevalPolling(q);
- } else {
- alert("重评任务启动失败: " + d.error);
- btn.disabled = false;
- btn.textContent = "♻️ 重新评估当前结果";
- }
- })
- .catch(err => {
- alert("请求出错: " + err);
- btn.disabled = false;
- btn.textContent = "♻️ 重新评估当前结果";
- });
- }
- function startReevalPolling(q) {
- if (reevalPollIntervals[q]) return;
- const btn = document.getElementById("reevalBtn");
-
- const poll = () => {
- fetch(`/api/reeval_status?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(d => {
- const isCurrent = DATA.queries[st.qi] && DATA.queries[st.qi].key === q;
- if (d.status === "success") {
- clearInterval(reevalPollIntervals[q]);
- delete reevalPollIntervals[q];
- if (isCurrent) {
- btn.disabled = false;
- btn.textContent = '♻️ 重新评估当前结果';
- }
- loadData(true);
- alert(`Query "${q}" 重新评估完成!已自动更新本页评分展示。`);
- } else if (d.status === "failed") {
- clearInterval(reevalPollIntervals[q]);
- delete reevalPollIntervals[q];
- if (isCurrent) {
- btn.disabled = false;
- btn.textContent = '♻️ 重新评估当前结果';
- }
- alert(`Query "${q}" 重新评估失败: ${d.error}`);
- } else if (d.status === "running") {
- if (isCurrent) {
- btn.disabled = true;
- btn.textContent = `♻️ 重评中 ${q}...`;
- }
- }
- })
- .catch(err => console.error("Poll error:", err));
- };
-
- poll();
- reevalPollIntervals[q] = setInterval(poll, 3000);
- }
- let batchProcPollInterval = null;
- function batchExtractProcedures() {
- if (st.qi === -1 || !DATA.queries[st.qi]) return;
- const q = DATA.queries[st.qi].key;
- const form = ['A', 'B', 'C'][st.fi];
-
- const concurrencyInput = document.getElementById("batchConcurrency");
- const concurrency = concurrencyInput ? parseInt(concurrencyInput.value) : 4;
- const model = "google/gemini-3.1-flash-lite";
-
- const btn = document.getElementById("batchProcBtn");
- btn.disabled = true;
- btn.textContent = "⚡ 提交任务...";
-
- const execConsoleCard = document.getElementById("execConsoleCard");
- if (execConsoleCard) {
- execConsoleCard.style.display = "flex";
- }
- const consoleTitle = document.getElementById("consoleTitle");
- if (consoleTitle) consoleTitle.textContent = `🚀 批量工序提取控制台 - ${q}`;
- const consoleStatus = document.getElementById("consoleStatus");
- if (consoleStatus) {
- consoleStatus.textContent = "running";
- consoleStatus.style.color = "var(--mint)";
- }
- const consoleOutput = document.getElementById("consoleOutput");
- if (consoleOutput) consoleOutput.textContent = `[info] 正在为 Query: "${q}" (Form ${form}) 提交批量工序提取任务...\n`;
- fetch("/api/batch_generate_procedure", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ q, form, concurrency, model })
- })
- .then(r => r.json().then(d => ({ ok: r.ok, d })))
- .then(({ ok, d }) => {
- if (!ok) {
- btn.disabled = false;
- btn.textContent = "⚡ 一键提取工序";
- if (consoleStatus) {
- consoleStatus.textContent = "failed";
- consoleStatus.style.color = "var(--rose)";
- }
- if (consoleOutput) consoleOutput.textContent += `[error] 提交失败: ${d.error || "未知错误"}\n`;
- alert("提交批量提取任务失败: " + (d.error || "未知错误"));
- return;
- }
-
- if (consoleOutput) consoleOutput.textContent += `[info] 任务启动成功,后台 PID: ${d.pid || "N/A"}\n[info] 正在获取日志...\n`;
- startBatchProcPolling(q);
- })
- .catch(err => {
- btn.disabled = false;
- btn.textContent = "⚡ 一键提取工序";
- if (consoleStatus) {
- consoleStatus.textContent = "failed";
- consoleStatus.style.color = "var(--rose)";
- }
- if (consoleOutput) consoleOutput.textContent += `[error] 网络错误: ${err}\n`;
- alert("网络错误: " + err);
- });
- }
- function startBatchProcPolling(q) {
- if (batchProcPollInterval) {
- clearInterval(batchProcPollInterval);
- }
-
- const btn = document.getElementById("batchProcBtn");
- const consoleStatus = document.getElementById("consoleStatus");
- const consoleOutput = document.getElementById("consoleOutput");
-
- const poll = () => {
- fetch(`/api/batch_generate_status?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(d => {
- const isCurrent = DATA.queries[st.qi] && DATA.queries[st.qi].key === q;
-
- if (d.status === "success") {
- clearInterval(batchProcPollInterval);
- batchProcPollInterval = null;
- if (isCurrent) {
- btn.disabled = false;
- btn.textContent = "⚡ 一键提取工序";
- if (consoleStatus) {
- consoleStatus.textContent = "success";
- consoleStatus.style.color = "var(--mint)";
- }
- }
- fetch(`/api/batch_generate_log?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(ld => {
- if (isCurrent && ld.log && consoleOutput) {
- consoleOutput.textContent = ld.log + "\n🎉 批量工序提取已全部成功完成,已自动生成并编译 workflow json 和 HTML 文件!\n";
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
- }
- });
- loadData(true);
- } else if (d.status === "failed") {
- clearInterval(batchProcPollInterval);
- batchProcPollInterval = null;
- if (isCurrent) {
- btn.disabled = false;
- btn.textContent = "⚡ 一键提取工序";
- if (consoleStatus) {
- consoleStatus.textContent = "failed";
- consoleStatus.style.color = "var(--rose)";
- }
- }
- fetch(`/api/batch_generate_log?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(ld => {
- if (isCurrent && ld.log && consoleOutput) {
- consoleOutput.textContent = ld.log + `\n❌ 任务执行失败: ${d.error || "未知原因"}\n`;
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
- }
- });
- } else if (d.status === "running") {
- if (isCurrent) {
- btn.disabled = true;
- btn.textContent = "⚡ 批量提取中...";
- if (consoleStatus) {
- consoleStatus.textContent = "running";
- consoleStatus.style.color = "var(--mint)";
- }
- }
- fetch(`/api/batch_generate_log?q=${encodeURIComponent(q)}`)
- .then(r => r.json())
- .then(ld => {
- if (isCurrent && ld.log && consoleOutput) {
- consoleOutput.textContent = ld.log;
- consoleOutput.scrollTop = consoleOutput.scrollHeight;
- }
- });
- }
- })
- .catch(err => console.error("Batch poll error:", err));
- };
-
- poll();
- batchProcPollInterval = setInterval(poll, 2000);
- }
- function loadData(forceReload) {
- document.getElementById("lede").textContent = "正在加载数据...";
- fetch("/api/data")
- .then(r => r.json())
- .then(d => {
- DATA = d;
- document.getElementById("lede").textContent = `runs_new/ 下共找到 ${d.queries.length} 个检索到的 query。`;
-
- updateThresholdLimits();
-
- if (DATA.queries.length > 0) {
- if (st.qi === -1 || st.qi >= DATA.queries.length) {
- st.qi = 0;
- }
- st.fi = 0;
- document.getElementById("resultsArea").style.display = "block";
- } else {
- st.qi = -1;
- document.getElementById("resultsArea").style.display = "none";
- }
-
- renderSidebar();
- renderGrid();
- renderHead();
-
- if (d.active_reevals) {
- Object.keys(d.active_reevals).forEach(q => {
- if (d.active_reevals[q] === "running") {
- startReevalPolling(q);
- }
- });
- }
- if (d.active_batch_tasks) {
- Object.keys(d.active_batch_tasks).forEach(q => {
- if (d.active_batch_tasks[q] === "running") {
- startBatchProcPolling(q);
- }
- });
- }
- })
- .catch(err => {
- document.getElementById("lede").textContent = "读取数据失败:" + err;
- });
- }
- // App Initialization
- (function init() {
- // Build query builder dimension groups
- const root = document.getElementById('dimensions');
- DIMS.forEach(dim => {
- const grp = document.createElement('div');
- grp.className = 'dim-group';
- grp.dataset.id = dim.id;
- root.appendChild(grp);
- renderDim(dim.id);
- });
- updateQueryPreview();
- // Load scanned queries list
- loadData(false);
- })();
- </script>
- </body>
- </html>
|