Deadman922 commited on
Commit
6dba86e
Β·
verified Β·
1 Parent(s): 9d289ba

Initialize Testing Phase

Browse files
Files changed (3) hide show
  1. requirements.txt +30 -30
  2. server/main.py +23 -5
  3. static/index.html +665 -283
requirements.txt CHANGED
@@ -4,92 +4,92 @@
4
  #
5
  # pip-compile --output-file=requirements.txt pyproject.toml
6
  #
7
- annotated-doc==0.0.4
8
  # via fastapi
9
- annotated-types==0.7.0
10
  # via pydantic
11
- anyio==4.13.0
12
  # via
13
  # httpx
14
  # openai
15
  # starlette
16
  # watchfiles
17
- certifi==2026.2.25
18
  # via
19
  # httpcore
20
  # httpx
21
  # requests
22
- charset-normalizer==3.4.6
23
  # via requests
24
- click==8.3.1
25
  # via uvicorn
26
- colorama==0.4.6
27
  # via
28
  # click
29
  # tqdm
30
  # uvicorn
31
- distro==1.9.0
32
  # via openai
33
- fastapi==0.135.2
34
  # via sql-quality-env (pyproject.toml)
35
- h11==0.16.0
36
  # via
37
  # httpcore
38
  # uvicorn
39
- httpcore==1.0.9
40
  # via httpx
41
- httptools==0.7.1
42
  # via uvicorn
43
- httpx==0.28.1
44
  # via openai
45
- idna==3.11
46
  # via
47
  # anyio
48
  # httpx
49
  # requests
50
- jiter==0.13.0
51
  # via openai
52
- openai==2.30.0
53
  # via sql-quality-env (pyproject.toml)
54
- pydantic==2.12.5
55
  # via
56
  # fastapi
57
  # openai
58
  # sql-quality-env (pyproject.toml)
59
- pydantic-core==2.41.5
60
  # via pydantic
61
- python-dotenv==1.2.2
62
  # via
63
  # sql-quality-env (pyproject.toml)
64
  # uvicorn
65
- pyyaml==6.0.3
66
  # via uvicorn
67
- requests==2.33.1
68
  # via sql-quality-env (pyproject.toml)
69
- sniffio==1.3.1
70
  # via openai
71
- starlette==1.0.0
72
  # via fastapi
73
- tqdm==4.67.3
74
  # via openai
75
- typing-extensions==4.15.0
76
  # via
77
  # fastapi
78
  # openai
79
  # pydantic
80
  # pydantic-core
81
  # typing-inspection
82
- typing-inspection==0.4.2
83
  # via
84
  # fastapi
85
  # pydantic
86
- urllib3==2.6.3
87
  # via requests
88
- uvicorn[standard]==0.42.0
89
  # via sql-quality-env (pyproject.toml)
90
- watchfiles==1.1.1
91
  # via uvicorn
92
  websockets==16.0
93
  # via uvicorn
94
- python-multipart==0.0.9
95
  # for FastAPI file uploads (UploadFile)
 
4
  #
5
  # pip-compile --output-file=requirements.txt pyproject.toml
6
  #
7
+ annotated-doc>=0.0.4
8
  # via fastapi
9
+ annotated-types>=0.7.0
10
  # via pydantic
11
+ anyio>=4.13.0
12
  # via
13
  # httpx
14
  # openai
15
  # starlette
16
  # watchfiles
17
+ certifi>=2026.2.25
18
  # via
19
  # httpcore
20
  # httpx
21
  # requests
22
+ charset-normalizer>=3.4.6
23
  # via requests
24
+ click>=8.3.1
25
  # via uvicorn
26
+ colorama>=0.4.6
27
  # via
28
  # click
29
  # tqdm
30
  # uvicorn
31
+ distro>=1.9.0
32
  # via openai
33
+ fastapi>=0.100.0
34
  # via sql-quality-env (pyproject.toml)
35
+ h11>=0.16.0
36
  # via
37
  # httpcore
38
  # uvicorn
39
+ httpcore>=1.0.9
40
  # via httpx
41
+ httptools>=0.7.1
42
  # via uvicorn
43
+ httpx>=0.28.1
44
  # via openai
45
+ idna>=3.11
46
  # via
47
  # anyio
48
  # httpx
49
  # requests
50
+ jiter>=0.13.0
51
  # via openai
52
+ openai>=1.0.0
53
  # via sql-quality-env (pyproject.toml)
54
+ pydantic>=2.0.0
55
  # via
56
  # fastapi
57
  # openai
58
  # sql-quality-env (pyproject.toml)
59
+ pydantic-core>=2.41.5
60
  # via pydantic
61
+ python-dotenv>=1.0.0
62
  # via
63
  # sql-quality-env (pyproject.toml)
64
  # uvicorn
65
+ pyyaml>=6.0.3
66
  # via uvicorn
67
+ requests>=2.31.0
68
  # via sql-quality-env (pyproject.toml)
69
+ sniffio>=1.3.1
70
  # via openai
71
+ starlette>=1.0.0
72
  # via fastapi
73
+ tqdm>=4.67.3
74
  # via openai
75
+ typing-extensions>=4.15.0
76
  # via
77
  # fastapi
78
  # openai
79
  # pydantic
80
  # pydantic-core
81
  # typing-inspection
82
+ typing-inspection>=0.4.2
83
  # via
84
  # fastapi
85
  # pydantic
86
+ urllib3>=2.6.3
87
  # via requests
88
+ uvicorn[standard]>=0.23.0
89
  # via sql-quality-env (pyproject.toml)
90
+ watchfiles>=1.1.1
91
  # via uvicorn
92
  websockets==16.0
93
  # via uvicorn
94
+ python-multipart>=0.0.6
95
  # for FastAPI file uploads (UploadFile)
server/main.py CHANGED
@@ -78,12 +78,12 @@ async def root():
78
  return load_static_html()
79
 
80
 
81
- @app.get("/health", response_model=HealthResponse)
82
- async def health() -> HealthResponse:
83
  """Health check endpoint for monitoring and load balancers.
84
 
85
  Returns:
86
- dict: Status response indicating the server is running
87
  """
88
  # Check LLM availability
89
  llm_available = False
@@ -98,7 +98,21 @@ async def health() -> HealthResponse:
98
  except Exception:
99
  pass
100
 
101
- return HealthResponse(status="ok", llm_available=llm_available)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
 
104
  @app.post("/ping", response_model=HealthResponse)
@@ -758,7 +772,11 @@ def detect_tasks_from_schema(schema: list[dict]) -> list[str]:
758
 
759
 
760
  @app.post("/upload-db")
761
- async def upload_db(file: UploadFile = File(...), use_for_tasks: bool = True, session_id: str = None) -> dict:
 
 
 
 
762
  """Upload a .sqlite, .db, or .csv file for custom exploration and RL tasks.
763
 
764
  Args:
 
78
  return load_static_html()
79
 
80
 
81
+ @app.get("/health")
82
+ async def health() -> dict:
83
  """Health check endpoint for monitoring and load balancers.
84
 
85
  Returns:
86
+ dict: Status response with server and LLM connection status
87
  """
88
  # Check LLM availability
89
  llm_available = False
 
98
  except Exception:
99
  pass
100
 
101
+ return {
102
+ "status": "online",
103
+ "llm": "connected" if llm_available else "disconnected",
104
+ "llm_available": llm_available
105
+ }
106
+
107
+
108
+ @app.get("/status")
109
+ async def status() -> dict:
110
+ """Status endpoint for Hugging Face Spaces compatibility.
111
+
112
+ Returns:
113
+ dict: Simple status response
114
+ """
115
+ return {"status": "online", "llm": "connected"}
116
 
117
 
118
  @app.post("/ping", response_model=HealthResponse)
 
772
 
773
 
774
  @app.post("/upload-db")
775
+ async def upload_db(
776
+ file: UploadFile = File(...),
777
+ use_for_tasks: bool = True,
778
+ session_id: str = None
779
+ ) -> dict:
780
  """Upload a .sqlite, .db, or .csv file for custom exploration and RL tasks.
781
 
782
  Args:
static/index.html CHANGED
@@ -135,14 +135,251 @@
135
  .health-dot.online { background: var(--success); }
136
  .health-dot.offline { background: var(--error); }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  /* ─── MAIN CONTENT ─────────────────────────────────────────────────── */
139
  .main {
140
- padding: 20px;
141
- max-width: 1600px;
142
- margin: 0 auto;
 
 
 
143
  }
144
-
145
- /* ─── BENTO GRID ───────────────────────────────────────────────────── */
146
  .bento-row {
147
  display: grid;
148
  gap: 16px;
@@ -683,289 +920,328 @@
683
  </div>
684
  </header>
685
 
686
- <!-- MAIN CONTENT -->
687
- <main class="main">
688
-
689
- <!-- ─── UPLOAD SECTION ──────────────────────────────────────────────── -->
690
- <div class="help-section" style="background: linear-gradient(135deg, #E3F2FD 0%, #F3E5F5 100%); border-color: #90CAF9; margin-bottom: 16px;" id="upload-section">
691
- <div class="help-title" style="color: #1565C0;">
692
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
693
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
694
- <polyline points="17 8 12 3 7 8"/>
695
- <line x1="12" y1="3" x2="12" y2="15"/>
696
- </svg>
697
- Custom Database Upload
698
- </div>
699
- <div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
700
- <span style="font-size: 13px; color: var(--text-secondary);">Or use your own database:</span>
701
- <input type="file" id="db-file-input" accept=".sqlite,.db,.csv" style="font-size: 13px; padding: 6px; border: 1px solid var(--card-border); border-radius: var(--radius);">
702
- <button class="btn btn-primary" onclick="uploadDatabase()" id="upload-btn">
703
- <span class="spinner" id="upload-spinner"></span>
704
- <span id="upload-btn-text">Upload</span>
705
- </button>
706
- <span id="upload-status" style="font-size: 12px; color: var(--text-secondary);">No file loaded</span>
707
- <button class="btn btn-secondary" onclick="backToTasks()" id="back-to-tasks-btn" style="display: none;">Back to Tasks</button>
708
- </div>
709
- <div id="upload-error" class="output-error" style="display: none; margin-top: 12px;"></div>
710
  </div>
711
- <!-- HOW TO USE SECTION -->
712
- <div class="help-section" id="help-section">
713
- <div class="help-title">
714
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
715
- <circle cx="12" cy="12" r="10"/>
716
- <path d="M12 16v-4M12 8h.01"/>
717
- </svg>
718
- How to Use This Application
719
- </div>
720
- <div class="help-steps">
721
- <div class="help-step">
722
- <span class="help-step-num">1</span>
723
- <div class="help-step-title">Select Task</div>
724
- <div class="help-step-desc">Choose a data quality task from the dropdown. Each has a specific data issue to investigate.</div>
725
- </div>
726
- <div class="help-step">
727
- <span class="help-step-num">2</span>
728
- <div class="help-step-title">Investigate</div>
729
- <div class="help-step-desc">Use <strong>Query</strong> (SELECT only) to explore data. Use <strong>Profile</strong> to check column stats. Review the Schema panel.</div>
730
- </div>
731
- <div class="help-step">
732
- <span class="help-step-num">3</span>
733
- <div class="help-step-title">Identify Issue</div>
734
- <div class="help-step-desc">Find the data quality problem: null values, type mismatches, duplicates, or orphans.</div>
735
- </div>
736
- <div class="help-step">
737
- <span class="help-step-num">4</span>
738
- <div class="help-step-title">Submit Fix</div>
739
- <div class="help-step-desc">Use the appropriate tab: <strong>Text Answer</strong> for null_audit, <strong>SQL Fix</strong> for repairs, <strong>Dedup</strong> for duplicates.</div>
740
- </div>
741
- </div>
742
- <div class="help-actions">
743
- <span class="help-badge">Query = SELECT only</span>
744
- <span class="help-badge">Fix = UPDATE/INSERT/DELETE allowed</span>
745
- <span class="help-badge">Submit = Final answer</span>
746
- </div>
747
  </div>
 
 
 
 
 
 
748
 
749
- <!-- ROW 1: Bento Grid (3 columns) -->
750
- <div class="bento-row bento-3">
751
-
752
- <!-- LEFT: Task Info -->
753
- <div class="card">
754
- <div class="card-header">
755
- <span class="card-title">Task Info</span>
756
- <span class="badge badge-easy" id="difficulty-badge">β€”</span>
757
- </div>
758
- <div class="card-body">
759
- <div class="task-name" id="task-name">No task selected</div>
760
- <div class="task-desc" id="task-desc">Select a task from the dropdown above to begin.</div>
761
- <div class="meta-row">
762
- <div class="meta-item">
763
- <span class="meta-label">Episode ID</span>
764
- <span class="meta-value" id="episode-id">β€”</span>
765
- </div>
766
- <div class="meta-item">
767
- <span class="meta-label">Step</span>
768
- <span class="meta-value" id="step-count">0</span>
769
- </div>
770
- <div class="meta-item">
771
- <span class="meta-label">Max Attempts</span>
772
- <span class="meta-value" id="max-attempts">β€”</span>
773
- </div>
774
  </div>
775
- <div class="meta-row" style="margin-top: 8px;">
776
- <div class="meta-item">
777
- <span class="meta-label">Server</span>
778
- <span class="status-dot" id="health-status">Checking...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
  </div>
780
- <div class="meta-item">
781
- <span class="meta-label">LLM</span>
782
- <span class="status-dot" id="llm-status">Checking...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  </div>
 
 
 
 
784
  </div>
785
  </div>
786
- </div>
787
-
788
- <!-- MIDDLE: Investigate -->
789
- <div class="card">
790
- <div class="tabs">
791
- <div class="tab active" onclick="switchTab(this, 'investigate', 'query')">Query</div>
792
- <div class="tab" onclick="switchTab(this, 'investigate', 'profile')">Profile</div>
 
 
 
 
 
 
 
 
793
  </div>
794
- <div class="tab-panel active" id="investigate-panel-query">
795
- <div class="field">
796
- <label>SQL Query (SELECT only)</label>
797
- <textarea id="query-sql" placeholder="SELECT * FROM users LIMIT 10"></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  </div>
799
- <button class="btn btn-primary btn-full" onclick="runQuery()">
800
- <span class="spinner" id="query-spinner"></span>
801
- <span id="query-btn-text">Run Query</span>
802
- </button>
803
- <div class="output-panel" id="query-output-container" style="display:none;">
804
- <div class="output-header">
805
- <span class="output-title">Result</span>
 
 
 
 
806
  </div>
807
- <div class="output-body" id="query-output"></div>
808
  </div>
809
  </div>
810
- <div class="tab-panel" id="investigate-panel-profile">
811
- <div class="field">
812
- <label>Column Name</label>
813
- <input type="text" id="profile-col" placeholder="e.g., email">
 
 
814
  </div>
815
- <button class="btn btn-primary btn-full" onclick="profileColumn()">
816
- <span class="spinner" id="profile-spinner"></span>
817
- <span id="profile-btn-text">Profile Column</span>
818
- </button>
819
- <div class="output-panel" id="profile-output-container" style="display:none;">
820
- <div class="output-header">
821
- <span class="output-title">Profile Result</span>
822
  </div>
823
- <div class="output-body" id="profile-output"></div>
824
  </div>
825
  </div>
826
- </div>
827
-
828
- <!-- RIGHT: Schema -->
829
- <div class="card">
830
- <div class="card-header">
831
- Schema Info
832
- <button class="btn btn-xs btn-secondary" onclick="refreshSchema()" style="margin-left: auto;">Refresh</button>
833
- </div>
834
- <div class="card-body" id="schema-panel">
835
- <div class="output-body empty">Select a task to view schema</div>
836
- </div>
837
- </div>
838
-
839
- </div>
840
-
841
- <!-- ROW 2: Bento Grid (2 columns) -->
842
- <div class="bento-row bento-2">
843
-
844
- <!-- LEFT: Fix & Submit -->
845
- <div class="card">
846
- <div class="tabs" id="fix-tabs">
847
- <div class="tab active" id="tab-sql-fix" onclick="switchTab(this, 'fix', 'sql')">SQL Fix</div>
848
- <div class="tab" id="tab-text-answer" onclick="switchTab(this, 'fix', 'answer')">Text Answer</div>
849
- <div class="tab" id="tab-dedup" onclick="switchTab(this, 'fix', 'dedup')">Dedup</div>
850
- </div>
851
-
852
- <!-- SQL Fix Tab -->
853
- <div class="tab-panel active" id="fix-panel-sql">
854
- <div class="field">
855
- <label>Fix Query</label>
856
- <textarea id="fix-sql" placeholder="-- Enter your fix SQL here"></textarea>
857
  </div>
858
- <div style="display:flex; gap:8px;">
859
- <button class="btn btn-secondary" onclick="verifyFix()">Verify Query</button>
860
- <button class="btn btn-primary" onclick="applyFix()">
861
- <span class="spinner" id="fix-spinner"></span>
862
- <span id="fix-btn-text">Apply Fix</span>
863
- </button>
 
 
 
 
 
 
 
 
 
864
  </div>
865
- </div>
866
-
867
- <!-- Text Answer Tab -->
868
- <div class="tab-panel" id="fix-panel-answer">
869
- <div class="field">
870
- <label>Answer</label>
871
- <input type="text" id="fix-answer" placeholder="e.g., email">
 
872
  </div>
873
- <div class="field" id="root-cause-field" style="display:none;">
874
- <label>Root Cause Table (if dedup)</label>
875
- <input type="text" id="root-cause-table" placeholder="order_items">
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  </div>
877
- <div class="field" id="offending-key-field" style="display:none;">
878
- <label>Offending Key (if dedup)</label>
879
- <input type="text" id="offending-key" placeholder="order_id">
 
 
 
880
  </div>
881
  </div>
882
-
883
- <!-- Dedup Tab -->
884
- <div class="tab-panel" id="fix-panel-dedup">
885
- <div class="field">
886
- <label>Root Cause Table</label>
887
- <input type="text" id="dedup-table" placeholder="order_items">
888
- </div>
889
- <div class="field">
890
- <label>Offending Key</label>
891
- <input type="text" id="dedup-key" placeholder="order_id">
 
 
 
892
  </div>
893
- <div class="field">
894
- <label>Fix Query</label>
895
- <textarea id="dedup-sql" placeholder="DELETE FROM order_items WHERE rowid NOT IN (SELECT MIN(rowid) FROM order_items GROUP BY order_id)"></textarea>
 
896
  </div>
897
  </div>
898
-
899
- <div style="padding: 0 16px 16px;">
900
- <button class="btn btn-primary btn-full" onclick="submitEpisode()" style="background:#1565C0;">
901
- <span class="spinner" id="submit-spinner"></span>
902
- <span id="submit-btn-text">Submit Final Answer</span>
903
- </button>
904
- <div class="output-panel" id="submit-output-container" style="display:none; margin-top:12px;">
905
- <div class="output-header">
906
- <span class="output-title">Submission Result</span>
 
 
 
 
907
  </div>
908
- <div class="output-body" id="submit-output"></div>
909
  </div>
910
  </div>
911
- </div>
912
-
913
- <!-- RIGHT: Results -->
914
- <div class="card">
915
- <div class="card-header">
916
- <span class="card-title">Results</span>
917
- </div>
918
- <div class="card-body">
919
- <div class="score-display">
920
- <div class="score-value" id="score-value">β€”</div>
921
- <div class="score-label">Final Score</div>
922
- <div class="severity-badge severity-acceptable" id="severity-badge" style="display:none;">β€”</div>
923
  </div>
924
-
925
- <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--card-border);">
926
- <div class="dim-row">
927
- <span class="dim-label">D1</span>
928
- <span class="dim-name">Diag. Efficiency</span>
929
- <div class="dim-bar"><div class="dim-bar-fill" id="d1-bar" style="width:0%"></div></div>
930
- <span class="dim-val" id="d1-val">β€”</span>
931
- </div>
932
- <div class="dim-row">
933
- <span class="dim-label">D2</span>
934
- <span class="dim-name">Hypothesis Quality</span>
935
- <div class="dim-bar"><div class="dim-bar-fill" id="d2-bar" style="width:0%"></div></div>
936
- <span class="dim-val" id="d2-val">β€”</span>
937
- </div>
938
- <div class="dim-row">
939
- <span class="dim-label">D3</span>
940
- <span class="dim-name">Fix Correctness</span>
941
- <div class="dim-bar"><div class="dim-bar-fill" id="d3-bar" style="width:0%"></div></div>
942
- <span class="dim-val" id="d3-val">β€”</span>
943
  </div>
944
- <div class="dim-row">
945
- <span class="dim-label">D4</span>
946
- <span class="dim-name">Self-Verification</span>
947
- <div class="dim-bar"><div class="dim-bar-fill" id="d4-bar" style="width:0%"></div></div>
948
- <span class="dim-val" id="d4-val">β€”</span>
 
 
 
 
 
 
 
 
 
 
 
949
  </div>
950
- <div class="dim-row">
951
- <span class="dim-label">D5</span>
952
- <span class="dim-name">Generalization</span>
953
- <div class="dim-bar"><div class="dim-bar-fill" id="d5-bar" style="width:0%"></div></div>
954
- <span class="dim-val" id="d5-val">β€”</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  </div>
956
- </div>
957
-
958
- <div class="output-panel" style="margin-top: 16px;">
959
- <div class="output-header">
960
- <span class="output-title">Grader Feedback</span>
 
961
  </div>
962
- <div class="output-body" id="feedback-output">Submit an episode to see feedback</div>
963
  </div>
964
  </div>
965
- </div>
966
-
 
967
  </div>
968
-
969
  </main>
970
 
971
  <!-- TOAST CONTAINER -->
@@ -973,7 +1249,7 @@
973
 
974
  <script>
975
  // ─── CONFIG ───────────────────────────────────────────────────────────
976
- const BASE = window.location.origin;
977
 
978
  // ─── STATE ────────────────────────────────────────────────────────────
979
  let state = {
@@ -1471,6 +1747,10 @@ async function uploadDatabase() {
1471
  customDbFilename = data.filename;
1472
  firstCustomTable = data.tables[0]?.table_name || 'table';
1473
 
 
 
 
 
1474
  // Store compatible tasks and semantic suggestions
1475
  const hasLegacyTasks = data.compatible_tasks && data.compatible_tasks.length > 0;
1476
  const hasSemanticTasks = data.semantic_tasks && data.semantic_tasks.length > 0;
@@ -1481,24 +1761,29 @@ async function uploadDatabase() {
1481
  window.usingUploadedDbForTasks = true;
1482
 
1483
  // Enable task selection for uploaded DB
1484
- mode = 'task'; // Stay in task mode, not custom mode
1485
 
1486
- // Update UI with smart message
1487
  const totalTasks = (data.compatible_tasks?.length || 0) + (data.semantic_tasks?.length || 0);
1488
- document.getElementById('upload-status').innerHTML = 'βœ“ ' + data.filename + ' loaded (' + data.tables.length + ' tables) - <strong>' + totalTasks + ' smart task suggestions</strong>';
1489
- document.getElementById('upload-status').style.color = 'var(--success)';
1490
  document.getElementById('back-to-tasks-btn').style.display = 'inline-flex';
1491
 
1492
  // Update schema panel with health data
1493
  renderSchema(data.tables, data.health_audits);
1494
 
1495
- // Show smart task selection
1496
- showSmartTaskSelector(data.compatible_tasks, data.semantic_tasks, data.fingerprints);
 
 
 
 
 
1497
 
1498
  // Ensure task selector is enabled
1499
  updateUIForMode();
1500
 
1501
- toast('Database uploaded! ' + totalTasks + ' smart task suggestions available', 'success');
1502
  } else {
1503
  // No compatible tasks - go to custom exploration mode
1504
  mode = 'custom';
@@ -2249,45 +2534,142 @@ async function checkHealth() {
2249
  const r = await api('GET', '/health');
2250
  if (r.ok) {
2251
  updateStatusPill('active');
2252
- document.getElementById('health-status').textContent = 'Connected';
2253
- document.getElementById('health-status').className = 'status-dot online';
2254
 
2255
  // Check LLM connection
2256
- checkLLMConnection();
 
 
 
 
 
 
2257
  } else {
2258
  updateStatusPill('error');
2259
- document.getElementById('health-status').textContent = 'Offline';
2260
- document.getElementById('health-status').className = 'status-dot offline';
 
 
2261
  }
2262
  } catch (e) {
2263
  updateStatusPill('error');
2264
- document.getElementById('health-status').textContent = 'Error';
2265
- document.getElementById('health-status').className = 'status-dot offline';
 
 
2266
  }
2267
  }
2268
 
2269
- // Check LLM connection status
2270
- async function checkLLMConnection() {
2271
- try {
2272
- // Test LLM connection by checking if inference.py can be imported and has OpenAI client
2273
- const r = await api('GET', '/health');
 
 
 
 
 
 
 
 
 
 
2274
 
2275
- if (r.ok && r.data.llm_available) {
2276
- document.getElementById('llm-status').textContent = 'LLM Connected';
2277
- document.getElementById('llm-status').className = 'status-dot online';
2278
- } else if (r.ok) {
2279
- document.getElementById('llm-status').textContent = 'LLM Not Configured';
2280
- document.getElementById('llm-status').className = 'status-dot offline';
2281
- } else {
2282
- document.getElementById('llm-status').textContent = 'LLM Error';
2283
- document.getElementById('llm-status').className = 'status-dot offline';
 
2284
  }
2285
- } catch (e) {
2286
- document.getElementById('llm-status').textContent = 'LLM Offline';
2287
- document.getElementById('llm-status').className = 'status-dot offline';
 
 
 
 
 
 
 
 
 
 
 
 
2288
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2289
  }
2290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2291
  // ─── INIT ────���────────────────────────────────────────────────────────
2292
  // Initialize event delegation for dynamic elements
2293
  document.addEventListener('DOMContentLoaded', function() {
 
135
  .health-dot.online { background: var(--success); }
136
  .health-dot.offline { background: var(--error); }
137
 
138
+ /* ─── THREE-COLUMN DASHBOARD LAYOUT ─────────────────────────────────── */
139
+ .dashboard-container {
140
+ display: grid;
141
+ grid-template-columns: 280px 1fr 320px;
142
+ gap: 16px;
143
+ padding: 16px;
144
+ max-width: 100%;
145
+ height: calc(100vh - 48px);
146
+ overflow: hidden;
147
+ }
148
+
149
+ @media (max-width: 1200px) {
150
+ .dashboard-container {
151
+ grid-template-columns: 1fr;
152
+ height: auto;
153
+ overflow: visible;
154
+ }
155
+ }
156
+
157
+ /* ─── LEFT SIDEBAR ──────────────────────────────────────────────────── */
158
+ .left-sidebar {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: 12px;
162
+ overflow-y: auto;
163
+ }
164
+
165
+ .sidebar-card {
166
+ background: var(--surface);
167
+ border: 1px solid var(--card-border);
168
+ border-radius: var(--radius);
169
+ box-shadow: var(--shadow);
170
+ overflow: hidden;
171
+ }
172
+
173
+ .sidebar-card-header {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 8px;
177
+ padding: 12px 14px;
178
+ border-bottom: 1px solid var(--card-border);
179
+ background: var(--bg);
180
+ font-size: 12px;
181
+ font-weight: 500;
182
+ color: var(--text-secondary);
183
+ text-transform: uppercase;
184
+ letter-spacing: 0.05em;
185
+ }
186
+
187
+ .sidebar-card-body {
188
+ padding: 14px;
189
+ }
190
+
191
+ /* ─── CENTER PANEL ──────────────────────────────────────────────────── */
192
+ .center-panel {
193
+ display: flex;
194
+ flex-direction: column;
195
+ gap: 12px;
196
+ overflow-y: auto;
197
+ min-height: 0;
198
+ }
199
+
200
+ /* ─── RIGHT SIDEBAR ─────────────────────────────────────────────────── */
201
+ .right-sidebar {
202
+ display: flex;
203
+ flex-direction: column;
204
+ gap: 12px;
205
+ overflow-y: auto;
206
+ }
207
+
208
+ /* ─── STEP-BY-STEP HEADER ─────────────────────────────────────────────── */
209
+ .workflow-steps {
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ gap: 8px;
214
+ padding: 8px 20px;
215
+ background: linear-gradient(135deg, #E8F5E9 0%, #F1F8E9 100%);
216
+ border-bottom: 1px solid #A5D6A7;
217
+ }
218
+
219
+ .workflow-step {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 6px;
223
+ font-size: 12px;
224
+ color: var(--text-secondary);
225
+ }
226
+
227
+ .workflow-step.active {
228
+ color: var(--primary);
229
+ font-weight: 500;
230
+ }
231
+
232
+ .workflow-step-num {
233
+ display: flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ width: 20px;
237
+ height: 20px;
238
+ background: var(--primary);
239
+ color: white;
240
+ font-size: 11px;
241
+ font-weight: 600;
242
+ border-radius: 50%;
243
+ }
244
+
245
+ .workflow-step.active .workflow-step-num {
246
+ background: var(--success);
247
+ }
248
+
249
+ .workflow-arrow {
250
+ color: var(--text-tertiary);
251
+ font-size: 14px;
252
+ }
253
+
254
+ /* ─── AI INSIGHTS PANEL ──────────────────────────────────────────────── */
255
+ .ai-insights-card {
256
+ background: linear-gradient(135deg, #E3F2FD 0%, #F3E5F5 100%);
257
+ border: 1px solid #90CAF9;
258
+ }
259
+
260
+ .ai-insights-header {
261
+ background: linear-gradient(135deg, #1565C0 0%, #7B1FA2 100%);
262
+ color: white;
263
+ border-bottom: none;
264
+ }
265
+
266
+ .confidence-log {
267
+ background: var(--bg);
268
+ border: 1px solid var(--card-border);
269
+ border-radius: var(--radius);
270
+ padding: 10px;
271
+ margin-bottom: 10px;
272
+ font-size: 11px;
273
+ }
274
+
275
+ .confidence-log-header {
276
+ display: flex;
277
+ justify-content: space-between;
278
+ align-items: center;
279
+ margin-bottom: 6px;
280
+ }
281
+
282
+ .confidence-score {
283
+ background: var(--success);
284
+ color: white;
285
+ padding: 2px 8px;
286
+ border-radius: 12px;
287
+ font-size: 11px;
288
+ font-weight: 600;
289
+ }
290
+
291
+ .confidence-reasoning {
292
+ color: var(--text-secondary);
293
+ line-height: 1.4;
294
+ }
295
+
296
+ /* ─── TASK SUGGESTION CARDS ─────────────────────────────────────────── */
297
+ .task-suggestion {
298
+ background: var(--bg);
299
+ border: 1px solid var(--card-border);
300
+ border-radius: var(--radius);
301
+ padding: 12px;
302
+ margin-bottom: 10px;
303
+ cursor: pointer;
304
+ transition: all 0.15s;
305
+ }
306
+
307
+ .task-suggestion:hover {
308
+ border-color: var(--primary);
309
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
310
+ }
311
+
312
+ .task-suggestion-header {
313
+ display: flex;
314
+ justify-content: space-between;
315
+ align-items: center;
316
+ margin-bottom: 6px;
317
+ }
318
+
319
+ .task-suggestion-name {
320
+ font-size: 13px;
321
+ font-weight: 500;
322
+ color: var(--text-primary);
323
+ }
324
+
325
+ .task-match-badge {
326
+ background: #4CAF50;
327
+ color: white;
328
+ padding: 2px 8px;
329
+ border-radius: 10px;
330
+ font-size: 10px;
331
+ font-weight: 600;
332
+ }
333
+
334
+ .task-suggestion-reason {
335
+ font-size: 11px;
336
+ color: var(--text-secondary);
337
+ line-height: 1.4;
338
+ }
339
+
340
+ /* ─── UPLOAD AREA ───────────────────────────────────────────────────── */
341
+ .upload-area {
342
+ border: 2px dashed var(--card-border);
343
+ border-radius: var(--radius);
344
+ padding: 16px;
345
+ text-align: center;
346
+ transition: all 0.15s;
347
+ background: var(--bg);
348
+ }
349
+
350
+ .upload-area:hover {
351
+ border-color: var(--primary);
352
+ background: #F1F8E9;
353
+ }
354
+
355
+ .upload-status {
356
+ font-size: 12px;
357
+ color: var(--text-secondary);
358
+ margin-top: 8px;
359
+ }
360
+
361
+ .upload-status.success {
362
+ color: var(--success);
363
+ font-weight: 500;
364
+ }
365
+
366
+ /* ─── SCHEMA PANEL COMPACT ──────────────────────────────────────────── */
367
+ .schema-compact {
368
+ max-height: 300px;
369
+ overflow-y: auto;
370
+ }
371
+
372
  /* ─── MAIN CONTENT ─────────────────────────────────────────────────── */
373
  .main {
374
+ padding: 0;
375
+ max-width: 100%;
376
+ margin: 0;
377
+ height: 100vh;
378
+ display: flex;
379
+ flex-direction: column;
380
  }
381
+
382
+ /* ─── BENTO GRID (DEPRECATED - keep for compatibility) ──────────────── */
383
  .bento-row {
384
  display: grid;
385
  gap: 16px;
 
920
  </div>
921
  </header>
922
 
923
+ <!-- WORKFLOW STEPS HEADER -->
924
+ <div class="workflow-steps">
925
+ <div class="workflow-step active" id="step-1">
926
+ <span class="workflow-step-num">1</span>
927
+ <span>Upload Data</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
  </div>
929
+ <span class="workflow-arrow">β†’</span>
930
+ <div class="workflow-step" id="step-2">
931
+ <span class="workflow-step-num">2</span>
932
+ <span>Map Alias</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
933
  </div>
934
+ <span class="workflow-arrow">β†’</span>
935
+ <div class="workflow-step" id="step-3">
936
+ <span class="workflow-step-num">3</span>
937
+ <span>AI Audit</span>
938
+ </div>
939
+ </div>
940
 
941
+ <!-- MAIN DASHBOARD -->
942
+ <main class="main">
943
+ <div class="dashboard-container">
944
+
945
+ <!-- LEFT SIDEBAR -->
946
+ <aside class="left-sidebar">
947
+
948
+ <!-- Connection Status -->
949
+ <div class="sidebar-card">
950
+ <div class="sidebar-card-header">
951
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
952
+ <path d="M5 12h14M12 5l7 7-7 7"/>
953
+ </svg>
954
+ Connection Status
 
 
 
 
 
 
 
 
 
 
 
955
  </div>
956
+ <div class="sidebar-card-body">
957
+ <div style="display: flex; flex-direction: column; gap: 10px;">
958
+ <div style="display: flex; align-items: center; gap: 8px;">
959
+ <span class="status-dot online" id="health-status-dot"></span>
960
+ <span style="font-size: 12px; color: var(--text-secondary);" id="health-status-text">Server Online</span>
961
+ </div>
962
+ <div style="display: flex; align-items: center; gap: 8px;">
963
+ <span class="status-dot offline" id="llm-status-dot"></span>
964
+ <span style="font-size: 12px; color: var(--text-secondary);" id="llm-status-text">LLM Checking...</span>
965
+ </div>
966
+ <div style="margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--card-border);">
967
+ <select class="task-select" id="task-select" style="width: 100%; margin-bottom: 8px;">
968
+ <option value="">Select a task...</option>
969
+ <option value="null_audit">null_audit β€” Find column with >50% nulls</option>
970
+ <option value="type_repair">type_repair β€” Fix numeric type mismatch</option>
971
+ <option value="dedup_bug">dedup_bug β€” Diagnose JOIN duplication</option>
972
+ <option value="fk_integrity">fk_integrity β€” Remove orphan FK rows</option>
973
+ <option value="schema_drift">schema_drift β€” Canonicalize drifted schema</option>
974
+ </select>
975
+ <button class="btn btn-primary btn-full" id="reset-btn" onclick="resetEpisode()">
976
+ <span class="spinner" id="reset-spinner"></span>
977
+ <span id="reset-btn-text">Initialize Episode</span>
978
+ </button>
979
+ </div>
980
  </div>
981
+ </div>
982
+ </div>
983
+
984
+ <!-- File Upload -->
985
+ <div class="sidebar-card">
986
+ <div class="sidebar-card-header">
987
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
988
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
989
+ <polyline points="17 8 12 3 7 8"/>
990
+ <line x1="12" y1="3" x2="12" y2="15"/>
991
+ </svg>
992
+ Upload Database
993
+ </div>
994
+ <div class="sidebar-card-body">
995
+ <div class="upload-area" id="upload-section">
996
+ <input type="file" id="db-file-input" accept=".sqlite,.db,.csv" style="display: none;">
997
+ <button class="btn btn-secondary btn-full" onclick="document.getElementById('db-file-input').click()">
998
+ Choose File
999
+ </button>
1000
+ <div class="upload-status" id="upload-status">No file loaded</div>
1001
+ <button class="btn btn-primary btn-full" onclick="uploadDatabase()" id="upload-btn" style="margin-top: 10px;">
1002
+ <span class="spinner" id="upload-spinner"></span>
1003
+ <span id="upload-btn-text">Upload & Analyze</span>
1004
+ </button>
1005
  </div>
1006
+ <div id="upload-error" class="output-error" style="display: none; margin-top: 10px;"></div>
1007
+ <button class="btn btn-secondary btn-full" onclick="backToTasks()" id="back-to-tasks-btn" style="display: none; margin-top: 8px;">
1008
+ Back to Tasks
1009
+ </button>
1010
  </div>
1011
  </div>
1012
+
1013
+ <!-- Table Schema -->
1014
+ <div class="sidebar-card">
1015
+ <div class="sidebar-card-header">
1016
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1017
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
1018
+ <line x1="3" y1="9" x2="21" y2="9"/>
1019
+ <line x1="9" y1="21" x2="9" y2="9"/>
1020
+ </svg>
1021
+ Table Schema
1022
+ <button class="btn btn-xs btn-secondary" onclick="refreshSchema()" style="margin-left: auto; font-size: 10px; padding: 2px 8px;">Refresh</button>
1023
+ </div>
1024
+ <div class="sidebar-card-body schema-compact" id="schema-panel">
1025
+ <div class="output-body empty">Select a task to view schema</div>
1026
+ </div>
1027
  </div>
1028
+
1029
+ </aside>
1030
+
1031
+ <!-- CENTER PANEL -->
1032
+ <section class="center-panel">
1033
+
1034
+ <!-- SQL Editor Card -->
1035
+ <div class="card" style="flex: 0 0 auto;">
1036
+ <div class="tabs">
1037
+ <div class="tab active" onclick="switchTab(this, 'investigate', 'query')">Query</div>
1038
+ <div class="tab" onclick="switchTab(this, 'investigate', 'profile')">Profile</div>
1039
+ </div>
1040
+
1041
+ <div class="tab-panel active" id="investigate-panel-query">
1042
+ <div class="card-body">
1043
+ <div class="field">
1044
+ <label>SQL Query (SELECT only)</label>
1045
+ <textarea id="query-sql" placeholder="SELECT * FROM users LIMIT 10" style="min-height: 140px;"></textarea>
1046
+ </div>
1047
+ <button class="btn btn-primary btn-full" onclick="runQuery()">
1048
+ <span class="spinner" id="query-spinner"></span>
1049
+ <span id="query-btn-text">Run Query</span>
1050
+ </button>
1051
+ </div>
1052
  </div>
1053
+
1054
+ <div class="tab-panel" id="investigate-panel-profile">
1055
+ <div class="card-body">
1056
+ <div class="field">
1057
+ <label>Column Name</label>
1058
+ <input type="text" id="profile-col" placeholder="e.g., email">
1059
+ </div>
1060
+ <button class="btn btn-primary btn-full" onclick="profileColumn()">
1061
+ <span class="spinner" id="profile-spinner"></span>
1062
+ <span id="profile-btn-text">Profile Column</span>
1063
+ </button>
1064
  </div>
 
1065
  </div>
1066
  </div>
1067
+
1068
+ <!-- Query Results -->
1069
+ <div class="card" style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
1070
+ <div class="card-header">
1071
+ <span class="card-title">Query Results</span>
1072
+ <span id="query-result-count" style="font-size: 11px; color: var(--text-tertiary);"></span>
1073
  </div>
1074
+ <div class="card-body" style="flex: 1; overflow: auto; padding: 0;">
1075
+ <div id="query-output-container" style="display: none;">
1076
+ <div id="query-output" style="padding: 12px;"></div>
1077
+ </div>
1078
+ <div id="query-empty" class="output-body empty" style="padding: 40px; text-align: center;">
1079
+ Run a query to see results here
 
1080
  </div>
 
1081
  </div>
1082
  </div>
1083
+
1084
+ <!-- Fix Panel -->
1085
+ <div class="card" style="flex: 0 0 auto;">
1086
+ <div class="tabs" id="fix-tabs">
1087
+ <div class="tab active" id="tab-sql-fix" onclick="switchTab(this, 'fix', 'sql')">SQL Fix</div>
1088
+ <div class="tab" id="tab-text-answer" onclick="switchTab(this, 'fix', 'answer')">Text Answer</div>
1089
+ <div class="tab" id="tab-dedup" onclick="switchTab(this, 'fix', 'dedup')">Dedup</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1090
  </div>
1091
+
1092
+ <div class="tab-panel active" id="fix-panel-sql">
1093
+ <div class="card-body">
1094
+ <div class="field">
1095
+ <label>Fix Query</label>
1096
+ <textarea id="fix-sql" placeholder="-- Enter your fix SQL here" style="min-height: 80px;"></textarea>
1097
+ </div>
1098
+ <div style="display:flex; gap:8px;">
1099
+ <button class="btn btn-secondary" onclick="verifyFix()">Verify Query</button>
1100
+ <button class="btn btn-primary" onclick="applyFix()">
1101
+ <span class="spinner" id="fix-spinner"></span>
1102
+ <span id="fix-btn-text">Apply Fix</span>
1103
+ </button>
1104
+ </div>
1105
+ </div>
1106
  </div>
1107
+
1108
+ <div class="tab-panel" id="fix-panel-answer">
1109
+ <div class="card-body">
1110
+ <div class="field">
1111
+ <label>Answer</label>
1112
+ <input type="text" id="fix-answer" placeholder="e.g., email">
1113
+ </div>
1114
+ </div>
1115
  </div>
1116
+
1117
+ <div class="tab-panel" id="fix-panel-dedup">
1118
+ <div class="card-body">
1119
+ <div class="field">
1120
+ <label>Root Cause Table</label>
1121
+ <input type="text" id="dedup-table" placeholder="order_items">
1122
+ </div>
1123
+ <div class="field">
1124
+ <label>Offending Key</label>
1125
+ <input type="text" id="dedup-key" placeholder="order_id">
1126
+ </div>
1127
+ <div class="field">
1128
+ <label>Fix Query</label>
1129
+ <textarea id="dedup-sql" placeholder="DELETE FROM order_items WHERE rowid NOT IN (SELECT MIN(rowid) FROM order_items GROUP BY order_id)" style="min-height: 60px;"></textarea>
1130
+ </div>
1131
+ </div>
1132
  </div>
1133
+
1134
+ <div style="padding: 0 16px 16px;">
1135
+ <button class="btn btn-primary btn-full" onclick="submitEpisode()" style="background:#1565C0;">
1136
+ <span class="spinner" id="submit-spinner"></span>
1137
+ <span id="submit-btn-text">Submit Final Answer</span>
1138
+ </button>
1139
  </div>
1140
  </div>
1141
+
1142
+ </section>
1143
+
1144
+ <!-- RIGHT SIDEBAR -->
1145
+ <aside class="right-sidebar">
1146
+
1147
+ <!-- AI Insights -->
1148
+ <div class="sidebar-card ai-insights-card">
1149
+ <div class="sidebar-card-header ai-insights-header">
1150
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1151
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
1152
+ </svg>
1153
+ AI Insights
1154
  </div>
1155
+ <div class="sidebar-card-body" id="ai-insights-panel">
1156
+ <div style="font-size: 12px; color: var(--text-secondary); text-align: center; padding: 20px;">
1157
+ Upload a database to see AI-powered task suggestions
1158
+ </div>
1159
  </div>
1160
  </div>
1161
+
1162
+ <!-- Task Suggestions -->
1163
+ <div class="sidebar-card">
1164
+ <div class="sidebar-card-header">
1165
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1166
+ <path d="M9 11l3 3L22 4"/>
1167
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
1168
+ </svg>
1169
+ Smart Task Suggestions
1170
+ </div>
1171
+ <div class="sidebar-card-body" id="task-suggestions-panel">
1172
+ <div style="font-size: 12px; color: var(--text-secondary); text-align: center; padding: 20px;">
1173
+ No suggestions yet. Upload and analyze your data first.
1174
  </div>
 
1175
  </div>
1176
  </div>
1177
+
1178
+ <!-- Confidence Logs -->
1179
+ <div class="sidebar-card">
1180
+ <div class="sidebar-card-header">
1181
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1182
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
1183
+ <polyline points="14 2 14 8 20 8"/>
1184
+ <line x1="16" y1="13" x2="8" y2="13"/>
1185
+ <line x1="16" y1="17" x2="8" y2="17"/>
1186
+ <polyline points="10 9 9 9 8 9"/>
1187
+ </svg>
1188
+ Confidence Logs
1189
  </div>
1190
+ <div class="sidebar-card-body" id="confidence-logs-panel">
1191
+ <div style="font-size: 11px; color: var(--text-tertiary); text-align: center; padding: 10px;">
1192
+ AI reasoning logs will appear here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  </div>
1194
+ </div>
1195
+ </div>
1196
+
1197
+ <!-- Results Summary -->
1198
+ <div class="sidebar-card">
1199
+ <div class="sidebar-card-header">
1200
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1201
+ <path d="M12 20V10M18 20V4M6 20v-6"/>
1202
+ </svg>
1203
+ Results
1204
+ </div>
1205
+ <div class="sidebar-card-body">
1206
+ <div class="score-display" style="padding: 10px;">
1207
+ <div class="score-value" id="score-value" style="font-size: 36px;">β€”</div>
1208
+ <div class="score-label">Final Score</div>
1209
+ <div class="severity-badge severity-acceptable" id="severity-badge" style="display:none; margin-top: 8px;">β€”</div>
1210
  </div>
1211
+
1212
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--card-border);">
1213
+ <div class="dim-row" style="margin-bottom: 8px;">
1214
+ <span class="dim-label" style="width: 20px;">D1</span>
1215
+ <span class="dim-name" style="width: 100px; font-size: 11px;">Diagnostic</span>
1216
+ <div class="dim-bar" style="height: 3px;"><div class="dim-bar-fill" id="d1-bar" style="width:0%"></div></div>
1217
+ <span class="dim-val" id="d1-val" style="width: 30px; font-size: 10px;">β€”</span>
1218
+ </div>
1219
+ <div class="dim-row" style="margin-bottom: 8px;">
1220
+ <span class="dim-label" style="width: 20px;">D2</span>
1221
+ <span class="dim-name" style="width: 100px; font-size: 11px;">Hypothesis</span>
1222
+ <div class="dim-bar" style="height: 3px;"><div class="dim-bar-fill" id="d2-bar" style="width:0%"></div></div>
1223
+ <span class="dim-val" id="d2-val" style="width: 30px; font-size: 10px;">β€”</span>
1224
+ </div>
1225
+ <div class="dim-row" style="margin-bottom: 8px;">
1226
+ <span class="dim-label" style="width: 20px;">D3</span>
1227
+ <span class="dim-name" style="width: 100px; font-size: 11px;">Fix Quality</span>
1228
+ <div class="dim-bar" style="height: 3px;"><div class="dim-bar-fill" id="d3-bar" style="width:0%"></div></div>
1229
+ <span class="dim-val" id="d3-val" style="width: 30px; font-size: 10px;">β€”</span>
1230
+ </div>
1231
  </div>
1232
+
1233
+ <div class="output-panel" style="margin-top: 12px;">
1234
+ <div class="output-header">
1235
+ <span class="output-title">Feedback</span>
1236
+ </div>
1237
+ <div class="output-body" id="feedback-output" style="font-size: 11px; max-height: 100px;">Submit to see feedback</div>
1238
  </div>
 
1239
  </div>
1240
  </div>
1241
+
1242
+ </aside>
1243
+
1244
  </div>
 
1245
  </main>
1246
 
1247
  <!-- TOAST CONTAINER -->
 
1249
 
1250
  <script>
1251
  // ─── CONFIG ───────────────────────────────────────────────────────────
1252
+ const BASE = ''; // Use relative paths for HF Spaces compatibility
1253
 
1254
  // ─── STATE ────────────────────────────────────────────────────────────
1255
  let state = {
 
1747
  customDbFilename = data.filename;
1748
  firstCustomTable = data.tables[0]?.table_name || 'table';
1749
 
1750
+ // Update workflow step highlighting
1751
+ document.getElementById('step-1').classList.remove('active');
1752
+ document.getElementById('step-2').classList.add('active');
1753
+
1754
  // Store compatible tasks and semantic suggestions
1755
  const hasLegacyTasks = data.compatible_tasks && data.compatible_tasks.length > 0;
1756
  const hasSemanticTasks = data.semantic_tasks && data.semantic_tasks.length > 0;
 
1761
  window.usingUploadedDbForTasks = true;
1762
 
1763
  // Enable task selection for uploaded DB
1764
+ mode = 'task';
1765
 
1766
+ // Update UI
1767
  const totalTasks = (data.compatible_tasks?.length || 0) + (data.semantic_tasks?.length || 0);
1768
+ document.getElementById('upload-status').innerHTML = 'βœ“ ' + data.filename + ' loaded - <strong>' + totalTasks + ' AI suggestions</strong>';
1769
+ document.getElementById('upload-status').className = 'upload-status success';
1770
  document.getElementById('back-to-tasks-btn').style.display = 'inline-flex';
1771
 
1772
  // Update schema panel with health data
1773
  renderSchema(data.tables, data.health_audits);
1774
 
1775
+ // Update new sidebar panels with AI insights
1776
+ updateAIInsightsPanel(data.semantic_tasks, data.fingerprints);
1777
+ updateTaskSuggestionsPanel(data.semantic_tasks, data.compatible_tasks);
1778
+
1779
+ // Extract log entries from semantic tasks
1780
+ const logEntries = (data.semantic_tasks || []).map(t => t.debug_info).filter(Boolean);
1781
+ updateConfidenceLogsPanel(logEntries);
1782
 
1783
  // Ensure task selector is enabled
1784
  updateUIForMode();
1785
 
1786
+ toast('Database uploaded! ' + totalTasks + ' AI task suggestions available', 'success');
1787
  } else {
1788
  // No compatible tasks - go to custom exploration mode
1789
  mode = 'custom';
 
2534
  const r = await api('GET', '/health');
2535
  if (r.ok) {
2536
  updateStatusPill('active');
2537
+ document.getElementById('health-status-text').textContent = 'Server Online';
2538
+ document.getElementById('health-status-dot').className = 'status-dot online';
2539
 
2540
  // Check LLM connection
2541
+ if (r.data.llm_available) {
2542
+ document.getElementById('llm-status-text').textContent = 'LLM Connected';
2543
+ document.getElementById('llm-status-dot').className = 'status-dot online';
2544
+ } else {
2545
+ document.getElementById('llm-status-text').textContent = 'LLM Not Configured';
2546
+ document.getElementById('llm-status-dot').className = 'status-dot offline';
2547
+ }
2548
  } else {
2549
  updateStatusPill('error');
2550
+ document.getElementById('health-status-text').textContent = 'Server Offline';
2551
+ document.getElementById('health-status-dot').className = 'status-dot offline';
2552
+ document.getElementById('llm-status-text').textContent = 'LLM Unknown';
2553
+ document.getElementById('llm-status-dot').className = 'status-dot offline';
2554
  }
2555
  } catch (e) {
2556
  updateStatusPill('error');
2557
+ document.getElementById('health-status-text').textContent = 'Connection Error';
2558
+ document.getElementById('health-status-dot').className = 'status-dot offline';
2559
+ document.getElementById('llm-status-text').textContent = 'LLM Unknown';
2560
+ document.getElementById('llm-status-dot').className = 'status-dot offline';
2561
  }
2562
  }
2563
 
2564
+ // ─── UPDATE AI INSIGHTS PANEL ─────────────────────────────────────────
2565
+ function updateAIInsightsPanel(semanticTasks, fingerprints) {
2566
+ const panel = document.getElementById('ai-insights-panel');
2567
+
2568
+ if (!semanticTasks || semanticTasks.length === 0) {
2569
+ panel.innerHTML = '<div style="font-size: 12px; color: var(--text-secondary); text-align: center; padding: 20px;">Upload a database to see AI-powered analysis</div>';
2570
+ return;
2571
+ }
2572
+
2573
+ let html = '<div style="font-size: 12px; color: var(--text-primary); margin-bottom: 12px;"><strong>🧠 AI Analysis Complete</strong></div>';
2574
+
2575
+ semanticTasks.forEach((task, index) => {
2576
+ const confidence = Math.round(task.confidence * 100);
2577
+ const fingerprint = fingerprints?.[task.table_name] || {};
2578
+ const nullCount = fingerprint.null_counts ? Object.values(fingerprint.null_counts).reduce((a, b) => a + b, 0) : 0;
2579
 
2580
+ html += '<div class="confidence-log">';
2581
+ html += '<div class="confidence-log-header">';
2582
+ html += '<span style="font-weight: 500;">' + task.task_id.replace('_', ' ') + '</span>';
2583
+ html += '<span class="confidence-score">' + confidence + '%</span>';
2584
+ html += '</div>';
2585
+ html += '<div class="confidence-reasoning">' + task.reason + '</div>';
2586
+ if (task.debug_info) {
2587
+ html += '<div style="margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--card-border); font-size: 10px; color: var(--text-tertiary);">';
2588
+ html += 'Rows: ' + (task.debug_info.fingerprint_analysis?.row_count || 'N/A') + ' | ';
2589
+ html += 'Nulls: ' + nullCount + '</div>';
2590
  }
2591
+ html += '</div>';
2592
+ });
2593
+
2594
+ panel.innerHTML = html;
2595
+ }
2596
+
2597
+ // ─── UPDATE TASK SUGGESTIONS PANEL ─────────────────────────────────────
2598
+ function updateTaskSuggestionsPanel(semanticTasks, legacyTasks) {
2599
+ const panel = document.getElementById('task-suggestions-panel');
2600
+
2601
+ const allTasks = [...(semanticTasks || []), ...(legacyTasks || []).map(id => ({ task_id: id, confidence: 1.0, reason: 'Exact table name match' }))];
2602
+
2603
+ if (allTasks.length === 0) {
2604
+ panel.innerHTML = '<div style="font-size: 12px; color: var(--text-secondary); text-align: center; padding: 20px;">No task suggestions available</div>';
2605
+ return;
2606
  }
2607
+
2608
+ const taskLabels = {
2609
+ 'null_audit': 'Null Audit',
2610
+ 'type_repair': 'Type Repair',
2611
+ 'dedup_bug': 'Duplicate Bug',
2612
+ 'fk_integrity': 'FK Integrity',
2613
+ 'schema_drift': 'Schema Drift',
2614
+ 'job_market_audit': 'Job Market Audit'
2615
+ };
2616
+
2617
+ let html = '';
2618
+ allTasks.forEach((task, index) => {
2619
+ const confidence = Math.round((task.confidence || 0) * 100);
2620
+ html += '<div class="task-suggestion" onclick="selectTaskSuggestion(\'' + task.task_id + '\')">';
2621
+ html += '<div class="task-suggestion-header">';
2622
+ html += '<span class="task-suggestion-name">' + (taskLabels[task.task_id] || task.task_id) + '</span>';
2623
+ html += '<span class="task-match-badge">' + confidence + '% match</span>';
2624
+ html += '</div>';
2625
+ html += '<div class="task-suggestion-reason">' + task.reason + '</div>';
2626
+ html += '</div>';
2627
+ });
2628
+
2629
+ panel.innerHTML = html;
2630
  }
2631
 
2632
+ // ─── UPDATE CONFIDENCE LOGS PANEL ──────────────────────────────────────
2633
+ function updateConfidenceLogsPanel(logEntries) {
2634
+ const panel = document.getElementById('confidence-logs-panel');
2635
+
2636
+ if (!logEntries || logEntries.length === 0) {
2637
+ panel.innerHTML = '<div style="font-size: 11px; color: var(--text-tertiary); text-align: center; padding: 10px;">AI reasoning logs will appear here after analysis</div>';
2638
+ return;
2639
+ }
2640
+
2641
+ let html = '';
2642
+ logEntries.forEach(entry => {
2643
+ html += '<div class="confidence-log" style="margin-bottom: 8px;">';
2644
+ html += '<div class="confidence-log-header">';
2645
+ html += '<span style="font-size: 10px; color: var(--text-tertiary);">' + entry.task_id + '</span>';
2646
+ html += '<span class="confidence-score" style="font-size: 10px; padding: 1px 6px;">' + Math.round(entry.confidence_score * 100) + '%</span>';
2647
+ html += '</div>';
2648
+ html += '<div class="confidence-reasoning" style="font-size: 10px;">' + entry.reasoning + '</div>';
2649
+ html += '</div>';
2650
+ });
2651
+
2652
+ panel.innerHTML = html;
2653
+ }
2654
+
2655
+ // ─── SELECT TASK FROM SUGGESTION ───────────────────────────────────────
2656
+ function selectTaskSuggestion(taskId) {
2657
+ document.getElementById('task-select').value = taskId;
2658
+ onTaskChange();
2659
+ toast('Task selected: ' + taskId, 'success');
2660
+
2661
+ // Highlight the active step
2662
+ document.querySelectorAll('.workflow-step').forEach((step, index) => {
2663
+ if (index === 2) { // Step 3: AI Audit
2664
+ step.classList.add('active');
2665
+ } else {
2666
+ step.classList.remove('active');
2667
+ }
2668
+ });
2669
+ }
2670
+
2671
+ // ─── UPLOAD DATABASE ──────────────────────────────────────────────────
2672
+
2673
  // ─── INIT ────���────────────────────────────────────────────────────────
2674
  // Initialize event delegation for dynamic elements
2675
  document.addEventListener('DOMContentLoaded', function() {