POSA_Copyrighter/web/operator-gui/app-state.js
changukyu 2661af86e5 feat: codex 검토 잔여 ③④⑤ + 밀린 부채 — 투입 충돌 표시, 사용/무관 분리, 측정 불가, VERSION
③ 파일명 stem 충돌: 다른 폴더의 같은 이름 파일이 한 건으로 합쳐져 둘째가 조용히 미심사였다.
   id 는 재유도하지 않고(기존 판정 보존) id_collisions() 로 탐지해 bootstrap.intakeConflicts 와
   파일당 1회 감사로 남기고, 콘솔 투입 상태줄에 경고 목록을 띄운다.
④ 행 버튼 '미사용' 이 false_positive 로 가 대표 근거 하나를 고르면 나머지 점수가 지워졌다 →
   '사용'/'무관', 사용 재클릭은 pending(표시 해제, 점수 유지).
⑤ 분모 0 비율은 None → '측정 불가'. 외부 검색이 꺼진 케이스는 점수 0 유지, 한 줄 요약에
   '외부 검색 꺼짐'.

부채: operator-review-workflow 13px 넘침 해소(겹치지 않는 레인 병합) → 다이어그램 5종 첫
REBUILD OK, v6 이전 카드 문구 교정. tests/conftest.py 로 로컬 Ollama 격리(가동 중에도 통과).
루트 VERSION 0.1.0 단일 출처, /health 와 기동 로그에 버전·스코어러 버전.

verify.ps1 (Ollama 가동 중, override 없음): ruff clean, 680 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BStHFrjQj2VypecSG9ex1V
2026-09-25 18:13:09 +09:00

139 lines
2.5 KiB
JavaScript

// Part 1 of 15 of the operator console app script.
//
// Label imports and the in-memory client state every other part reads.
//
// Load order is significant: these are classic (non-module) scripts sharing one
// global scope, and index.html must keep them in the order listed above/below.
// Splitting is a pure line-range extraction of the former single app.js.
const {
riskLabels,
decisionLabels,
providerLabels,
sourceLabels,
providerStatusLabels,
candidateStatusLabels,
evidenceStatusLabels,
knowledgeStatusLabels,
provenanceLabels,
knowledgeTypeLabels,
} = window.OperatorLabels;
const retiredProviderIds = new Set();
const operatorSearchProviders = [
{ id: "naver", label: providerLabels.naver },
{ id: "google_search", label: providerLabels.google_search },
];
const submissions = [];
const providers = [];
const knowledgeEntries = [];
const searchCoverage = {};
let suggestedQueryRunSummary = null; // { caseId, message } — 현재 케이스의 마지막 추천 쿼리 실행 결과
const knowledgeFilters = { query: "", type: "all", status: "all", active: "all" };
let editingKnowledgeEntryId = "";
let activeRerunAddedIds = new Set(); // 현재 렌더 중인 케이스의 lastRerunDiff.addedEvidenceIds
const DEFAULT_COVERAGE_THRESHOLDS = Object.freeze({
coverageGoodRate: 70,
coverageWarnRate: 40,
queryGoodRate: 70,
queryWarnRate: 40,
});
const coverageThresholds = { ...DEFAULT_COVERAGE_THRESHOLDS };
const collectionCandidates = [];
const corrections = [];
const auditEvents = [];
const decisionMemoPresets = [];
const state = {
currentView: "queue",
workbenchTab: "evidence",
knowledgeTab: "collect",
currentCollectionQuery: "",
currentCollectionProvider: "",
selectedCaseId: submissions[0]?.id || "",
apiError: "",
submissionQueue: null,
intakeConflicts: [],
importStatus: {
tone: "idle",
message: "로컬 제출 저장소 변경 대기 중",
},
filters: {
risk: "all",
source: "all",
decision: "all",
sort: "risk",
query: "",
},
};
const viewNames = new Set(["queue", "workbench", "knowledge", "providers", "stats", "audit"]);
// Bearer token for a server started with COPYRIGHTER_AUTH_TOKEN. Held in memory
// only (never localStorage/sessionStorage): the first authenticated request makes
// the server mint an HttpOnly session cookie, which is what subsequently
// authorizes <img>/media requests that cannot carry a header.
let authToken = "";
let pendingAuthPrompt = null;