diff --git a/tests/operator_gui/test_static_workbench.py b/tests/operator_gui/test_static_workbench.py index 9dd79f5..d933895 100644 --- a/tests/operator_gui/test_static_workbench.py +++ b/tests/operator_gui/test_static_workbench.py @@ -788,3 +788,18 @@ def test_visual_assets_are_referenced_for_review_images(): "match-web.svg", ]: assert not (assets_dir / orphan_asset).exists(), f"{orphan_asset} is unused; do not reintroduce it" + + +def test_suggested_queries_support_one_click_and_batch_execution(): + script = _read(APP_JS) + styles = _read(STYLES) + + assert "executeSuggestedQueries" in script + assert "data-run-suggested-query" in script + assert 'id="run-all-suggested-queries"' in script + assert "모두 실행" in script + assert "바로 실행" in script + assert "suggested-query-run-status" in script + # 기존 "입력칸 채우기" 버튼은 수정용으로 유지된다. + assert "data-suggested-query" in script + assert ".suggested-query-item" in styles diff --git a/web/operator-gui/app.js b/web/operator-gui/app.js index f4b4fa4..9038cc5 100644 --- a/web/operator-gui/app.js +++ b/web/operator-gui/app.js @@ -27,6 +27,8 @@ const knowledgeEntries = []; const searchCoverage = {}; +let suggestedQueryRunSummary = null; // { caseId, message } — 현재 케이스의 마지막 추천 쿼리 실행 결과 + const DEFAULT_COVERAGE_THRESHOLDS = Object.freeze({ coverageGoodRate: 70, @@ -888,59 +890,47 @@ function suggestedEvidenceQueries(submission) { function renderEvidenceNextActions(submission) { + const summary = + suggestedQueryRunSummary && suggestedQueryRunSummary.caseId === submission.id + ? `
${escapeHtml(suggestedQueryRunSummary.message)}
` + : ""; - if (!evidenceNeedsFollowup(submission)) return ""; + if (!evidenceNeedsFollowup(submission)) return summary; const queries = suggestedEvidenceQueries(submission); - const reasons = evidenceFollowupReasons(submission); - - if (!queries.length) return ""; + if (!queries.length) return summary; return ` -
-
- 근거 보강 추천 - - 현재 근거가 부족합니다. 추천 쿼리를 선택하면 수동 검색 입력칸에만 채워집니다. - + 추천 쿼리를 바로 실행하거나, 쿼리 본문을 눌러 수동 검색 입력칸에서 수정할 수 있습니다.
- -
- ${queries - .map( - (query) => ` - - - + + + + `, - ) - .join("")} -
- +
+ +
+
+ ${summary}
- `; - } @@ -968,6 +958,39 @@ function applySuggestedQuery(button) { } +async function executeSuggestedQueries(queries) { + const submission = getSelectedCase(); + if (!submission || !queries.length) return; + + const statusTarget = document.getElementById("suggested-query-run-status"); + const naver = providers.find((provider) => provider.id === "naver"); + if (!naver || !naver.enabled) { + if (statusTarget) statusTarget.textContent = "네이버 외부 검색 tool이 비활성입니다."; + return; + } + + const results = []; + for (const [index, query] of queries.entries()) { + if (statusTarget) statusTarget.textContent = `"${query}" 실행 중… (${index + 1}/${queries.length})`; + try { + await apiJson("/api/search/manual", { + method: "POST", + body: JSON.stringify({ submission_id: submission.id, provider: "naver", query }), + }); + results.push(`"${query}" 완료`); + } catch (errorValue) { + results.push(`"${query}" 실패: ${errorValue.message}`); + } + } + + suggestedQueryRunSummary = { + caseId: submission.id, + message: `추천 쿼리 실행 — ${results.join(" · ")}`, + }; + await refreshFromApi(); +} + + function filteredSubmissions() { const query = state.filters.query.trim().toLowerCase(); @@ -2129,6 +2152,8 @@ function selectCase(caseId) { state.selectedCaseId = caseId; + suggestedQueryRunSummary = null; + switchView("workbench"); switchWorkbenchTab("evidence"); @@ -3547,6 +3572,18 @@ function bindEvents() { + const runSuggestedButton = event.target.closest("[data-run-suggested-query]"); + if (runSuggestedButton) { + void executeSuggestedQueries([runSuggestedButton.dataset.runSuggestedQuery]); + return; + } + + const runAllSuggestedButton = event.target.closest("#run-all-suggested-queries"); + if (runAllSuggestedButton) { + void executeSuggestedQueries(suggestedEvidenceQueries(getSelectedCase())); + return; + } + const rerunQueryButton = event.target.closest("[data-rerun-query]"); if (rerunQueryButton) { diff --git a/web/operator-gui/styles.css b/web/operator-gui/styles.css index 4e7644c..11f43a6 100644 --- a/web/operator-gui/styles.css +++ b/web/operator-gui/styles.css @@ -2758,3 +2758,13 @@ tbody tr.selected-row, grid-column: auto; } } + +.suggested-query-item { + display: inline-flex; + gap: 4px; + align-items: center; +} + +.suggested-query-actions { + margin-top: 8px; +}