feat: one-click and batch execution for suggested evidence queries
This commit is contained in:
parent
63bbf0d755
commit
4abb837aaa
3 changed files with 92 additions and 30 deletions
|
|
@ -788,3 +788,18 @@ def test_visual_assets_are_referenced_for_review_images():
|
||||||
"match-web.svg",
|
"match-web.svg",
|
||||||
]:
|
]:
|
||||||
assert not (assets_dir / orphan_asset).exists(), f"{orphan_asset} is unused; do not reintroduce it"
|
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
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ const knowledgeEntries = [];
|
||||||
|
|
||||||
const searchCoverage = {};
|
const searchCoverage = {};
|
||||||
|
|
||||||
|
let suggestedQueryRunSummary = null; // { caseId, message } — 현재 케이스의 마지막 추천 쿼리 실행 결과
|
||||||
|
|
||||||
const DEFAULT_COVERAGE_THRESHOLDS = Object.freeze({
|
const DEFAULT_COVERAGE_THRESHOLDS = Object.freeze({
|
||||||
|
|
||||||
coverageGoodRate: 70,
|
coverageGoodRate: 70,
|
||||||
|
|
@ -888,59 +890,47 @@ function suggestedEvidenceQueries(submission) {
|
||||||
|
|
||||||
|
|
||||||
function renderEvidenceNextActions(submission) {
|
function renderEvidenceNextActions(submission) {
|
||||||
|
const summary =
|
||||||
|
suggestedQueryRunSummary && suggestedQueryRunSummary.caseId === submission.id
|
||||||
|
? `<div class="inline-status" id="suggested-query-run-result">${escapeHtml(suggestedQueryRunSummary.message)}</div>`
|
||||||
|
: "";
|
||||||
|
|
||||||
if (!evidenceNeedsFollowup(submission)) return "";
|
if (!evidenceNeedsFollowup(submission)) return summary;
|
||||||
|
|
||||||
const queries = suggestedEvidenceQueries(submission);
|
const queries = suggestedEvidenceQueries(submission);
|
||||||
|
|
||||||
const reasons = evidenceFollowupReasons(submission);
|
const reasons = evidenceFollowupReasons(submission);
|
||||||
|
if (!queries.length) return summary;
|
||||||
if (!queries.length) return "";
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|
||||||
<section class="evidence-next-action-panel" aria-label="근거 보강 추천">
|
<section class="evidence-next-action-panel" aria-label="근거 보강 추천">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<strong>근거 보강 추천</strong>
|
<strong>근거 보강 추천</strong>
|
||||||
|
<span>추천 쿼리를 바로 실행하거나, 쿼리 본문을 눌러 수동 검색 입력칸에서 수정할 수 있습니다.</span>
|
||||||
<span>현재 근거가 부족합니다. 추천 쿼리를 선택하면 수동 검색 입력칸에만 채워집니다.</span>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="evidence-followup-reasons">
|
<ul class="evidence-followup-reasons">
|
||||||
|
|
||||||
${reasons.map((reason) => `<li>${escapeHtml(reason)}</li>`).join("")}
|
${reasons.map((reason) => `<li>${escapeHtml(reason)}</li>`).join("")}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="suggested-query-list">
|
<div class="suggested-query-list">
|
||||||
|
|
||||||
${queries
|
${queries
|
||||||
|
|
||||||
.map(
|
.map(
|
||||||
|
|
||||||
(query) => `
|
(query) => `
|
||||||
|
<span class="suggested-query-item">
|
||||||
<button class="row-action" type="button" data-suggested-query="${escapeHtml(query)}" data-suggested-provider="naver">
|
<button class="row-action" type="button" data-suggested-query="${escapeHtml(query)}" data-suggested-provider="naver">
|
||||||
|
|
||||||
${escapeHtml(query)}
|
${escapeHtml(query)}
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
<button class="row-action" type="button" data-run-suggested-query="${escapeHtml(query)}">바로 실행</button>
|
||||||
|
</span>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
.join("")}
|
.join("")}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="suggested-query-actions">
|
||||||
|
<button class="row-action" type="button" id="run-all-suggested-queries">모두 실행</button>
|
||||||
|
</div>
|
||||||
|
<div id="suggested-query-run-status" class="inline-status" aria-live="polite"></div>
|
||||||
|
${summary}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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() {
|
function filteredSubmissions() {
|
||||||
const query = state.filters.query.trim().toLowerCase();
|
const query = state.filters.query.trim().toLowerCase();
|
||||||
|
|
@ -2129,6 +2152,8 @@ function selectCase(caseId) {
|
||||||
|
|
||||||
state.selectedCaseId = caseId;
|
state.selectedCaseId = caseId;
|
||||||
|
|
||||||
|
suggestedQueryRunSummary = null;
|
||||||
|
|
||||||
switchView("workbench");
|
switchView("workbench");
|
||||||
|
|
||||||
switchWorkbenchTab("evidence");
|
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]");
|
const rerunQueryButton = event.target.closest("[data-rerun-query]");
|
||||||
|
|
||||||
if (rerunQueryButton) {
|
if (rerunQueryButton) {
|
||||||
|
|
|
||||||
|
|
@ -2758,3 +2758,13 @@ tbody tr.selected-row,
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.suggested-query-item {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-query-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue