feat: expose google_search as operator manual text-query provider

This commit is contained in:
유창욱 2026-06-12 17:46:45 +09:00
parent 4abb837aaa
commit cf342425c5
3 changed files with 26 additions and 17 deletions

View file

@ -458,7 +458,10 @@ def test_safety_rules_are_visible_in_ui_contract():
assert 'id="decision-memo"' in html
assert 'id="manual-query-provider"' in html
assert 'option value="google_search"' not in html
# 2026-06-12 안전 규칙 개정(설계 승인 2026-06-11): 운영자 수동 검색에
# 구글 근거 검색(텍스트 쿼리)을 동적 옵션으로 노출한다. 정적 HTML 기본값은
# 네이버만 유지하고, 이미지 업로드 역검색 금지는 그대로다.
assert '{ id: "google_search", label: providerLabels.google_search }' in script
assert "네이버" in html
assert "reverse search" not in html.lower()
assert "sourceEvidenceIds" in script
@ -601,13 +604,18 @@ def test_operator_gui_exposes_keyword_candidate_collection_workflow():
assert "제출 제목/파일명 기반" in search
def test_google_custom_search_is_not_exposed_as_operator_choice():
def test_google_custom_search_is_exposed_as_operator_text_query_choice():
html = _read(INDEX)
script = _read(APP_JS)
search = _read(OPERATOR_SEARCH_JS)
labels = _read(OPERATOR_LABELS_JS)
assert "구글 맞춤 검색" not in html
assert "구글 맞춤 검색" not in script
# 정적 HTML은 네이버 기본값만 두고 부트스트랩 후 동적으로 채운다.
assert 'value="google_search"' not in html
assert '{ id: "google_search", label: providerLabels.google_search }' in script
assert "구글 근거 검색" in labels
assert 'provider === "google_search" ? "google_search" : "naver"' in search
assert "reverse search" not in html.lower()
assert "operatorSearchProviders" in script
assert "visibleProviderControls" in script

View file

@ -12,7 +12,10 @@
} = window.OperatorLabels;
const retiredProviderIds = new Set(["google_search"]);
const operatorSearchProviders = [{ id: "naver", label: providerLabels.naver }];
const operatorSearchProviders = [
{ id: "naver", label: providerLabels.naver },
{ id: "google_search", label: providerLabels.google_search },
];
const submissions = [];
@ -616,27 +619,25 @@ function visibleProviderStateEntries(providerState) {
function renderOperatorSearchProviderOptions() {
const optionHtml = operatorSearchProviders
.map((provider) => `<option value="${escapeHtml(provider.id)}">${escapeHtml(provider.label)}</option>`)
.map((provider) => {
const runtime = providers.find((item) => item.id === provider.id);
const unavailable = Boolean(runtime) && !runtime.enabled;
const label = unavailable ? `${provider.label} (비활성)` : provider.label;
return `<option value="${escapeHtml(provider.id)}" ${unavailable ? "disabled" : ""}>${escapeHtml(label)}</option>`;
})
.join("");
["manual-query-provider", "collection-provider"].forEach((elementId) => {
const select = document.getElementById(elementId);
if (!select) return;
const current = select.value;
select.innerHTML = optionHtml;
select.value = operatorSearchProviders.some((provider) => provider.id === current) ? current : operatorSearchProviders[0].id;
if (select.selectedOptions[0] && select.selectedOptions[0].disabled) {
select.value = operatorSearchProviders[0].id;
}
});
}

View file

@ -26,7 +26,7 @@
}
function normalizeManualSearchProvider(provider) {
return "naver";
return provider === "google_search" ? "google_search" : "naver";
}
global.OperatorSearch = {