ketannnn commited on
Commit
6eefb10
·
1 Parent(s): d61d4bf

feat: complete rewrite of UI/UX for streamlining and pruning

Browse files
backend/clean_all.py DELETED
@@ -1,27 +0,0 @@
1
- import asyncio
2
- from src.database import engine
3
- from sqlalchemy import text
4
- from src.config import get_settings
5
- from qdrant_client import QdrantClient
6
- from qdrant_client.models import Distance, VectorParams
7
-
8
- async def main():
9
- async with engine.begin() as conn:
10
- print('Wiping Postgres...')
11
- await conn.execute(text('DROP SCHEMA public CASCADE'))
12
- await conn.execute(text('CREATE SCHEMA public'))
13
- print('Postgres wiped.')
14
-
15
- import qdrant_client
16
- settings = get_settings()
17
- try:
18
- q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
19
- q.delete_collection(settings.collection_name)
20
- q.create_collection(settings.collection_name, vectors_config=VectorParams(size=384, distance=Distance.COSINE))
21
- print('Qdrant wiped and recreated.')
22
- except Exception as e:
23
- print('Qdrant error:', e)
24
-
25
- if __name__ == '__main__':
26
- asyncio.run(main())
27
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/fix_qdrant.py DELETED
@@ -1,17 +0,0 @@
1
-
2
- from qdrant_client import QdrantClient
3
- from src.config import get_settings
4
-
5
- settings = get_settings()
6
- q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
7
-
8
- try:
9
- q.create_payload_index(
10
- collection_name=settings.collection_name,
11
- field_name='session_id',
12
- field_schema='keyword'
13
- )
14
- print('Payload index created successfully!')
15
- except Exception as e:
16
- print('Error creating payload index:', e)
17
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/fix_qdrant2.py DELETED
@@ -1,18 +0,0 @@
1
-
2
- from qdrant_client import QdrantClient
3
- from qdrant_client.models import PayloadSchemaType
4
- from src.config import get_settings
5
-
6
- settings = get_settings()
7
- q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
8
-
9
- try:
10
- q.create_payload_index(
11
- collection_name=settings.collection_name,
12
- field_name='years_of_experience',
13
- field_schema=PayloadSchemaType.FLOAT
14
- )
15
- print('Payload index for years_of_experience created successfully!')
16
- except Exception as e:
17
- print('Error creating payload index:', e)
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/fix_qdrant_yoe.py DELETED
@@ -1,17 +0,0 @@
1
-
2
- from qdrant_client import QdrantClient
3
- from src.config import get_settings
4
-
5
- settings = get_settings()
6
- q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
7
-
8
- try:
9
- q.create_payload_index(
10
- collection_name=settings.collection_name,
11
- field_name='years_of_experience',
12
- field_schema='integer'
13
- )
14
- print('Years of Experience index created successfully!')
15
- except Exception as e:
16
- print('Error creating YOE index:', e)
17
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/test_db.py DELETED
@@ -1,8 +0,0 @@
1
- import asyncio
2
- from src.database import engine
3
-
4
- async def test():
5
- async with engine.begin() as conn:
6
- await conn.run_sync(lambda *args: print('DB Connection OK'))
7
-
8
- asyncio.run(test())
 
 
 
 
 
 
 
 
 
backend/wipe.py DELETED
@@ -1,19 +0,0 @@
1
- import asyncio
2
- import os
3
- from sqlalchemy.ext.asyncio import create_async_engine
4
- from sqlalchemy import text
5
- from dotenv import load_dotenv
6
-
7
- load_dotenv()
8
- url = os.getenv('DATABASE_URL').replace('postgresql:', 'postgresql+asyncpg:').replace('?sslmode=require', '')
9
- if '?sslmode=require' not in url and 'ep-patient-brook' in url: pass # handle if needed
10
-
11
- async def wipe():
12
- engine = create_async_engine(url)
13
- async with engine.begin() as conn:
14
- await conn.execute(text('DROP SCHEMA public CASCADE; CREATE SCHEMA public;'))
15
- print('DB wiped')
16
-
17
- if __name__ == '__main__':
18
- asyncio.run(wipe())
19
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/wipe_api.py DELETED
@@ -1,22 +0,0 @@
1
- from fastapi import FastAPI
2
- from sqlalchemy.ext.asyncio import create_async_engine
3
- from sqlalchemy import text
4
- from dotenv import load_dotenv
5
- import asyncio
6
- import os
7
-
8
- load_dotenv()
9
- url = os.getenv('DATABASE_URL').replace('postgresql:', 'postgresql+asyncpg:').replace('?sslmode=require&channel_binding=require', '')
10
- if '?sslmode=' not in url: url += '?ssl=require'
11
-
12
- engine = create_async_engine(url)
13
-
14
- async def main():
15
- async with engine.begin() as conn:
16
- print('Wiping...')
17
- await conn.execute(text('TRUNCATE table job_descriptions, sessions, candidates, match_results CASCADE;'))
18
- print('Wiped tables!')
19
-
20
- if __name__ == '__main__':
21
- asyncio.run(main())
22
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/app/jds/[id]/candidates/[cid]/page.tsx CHANGED
@@ -59,7 +59,7 @@ interface WorkExp {
59
  export default function CandidateDetailPage() {
60
  const params = useParams<{ id: string; cid: string }>();
61
  const searchParams = useSearchParams();
62
- const sessionId = searchParams.get("session_id") || undefined;
63
  const { id: jdId, cid: candidateId } = params;
64
 
65
  const [detail, setDetail] = useState<CandidateDetail | null>(null);
@@ -118,9 +118,9 @@ export default function CandidateDetailPage() {
118
 
119
  return (
120
  <div className="max-w-7xl mx-auto px-6 py-10">
121
- <Link href={sessionId ? `/sessions/${sessionId}?jd_id=${jdId}` : `/pipeline`}
122
- className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] font-semibold mb-6 inline-block transition-colors">
123
- ← Back to {jdInfo.title ?? "Rankings"}
124
  </Link>
125
 
126
  <div className="grid grid-cols-[1fr_300px] gap-6 items-start">
 
59
  export default function CandidateDetailPage() {
60
  const params = useParams<{ id: string; cid: string }>();
61
  const searchParams = useSearchParams();
62
+ const sessionId = searchParams.get("batch") || undefined;
63
  const { id: jdId, cid: candidateId } = params;
64
 
65
  const [detail, setDetail] = useState<CandidateDetail | null>(null);
 
118
 
119
  return (
120
  <div className="max-w-7xl mx-auto px-6 py-10">
121
+ <Link href={sessionId ? `/sessions/${sessionId}?jd_id=${jdId}` : `/sessions`}
122
+ className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] font-semibold mb-6 inline-block transition-colors uppercase tracking-wider">
123
+ ← Back to Candidate Batch
124
  </Link>
125
 
126
  <div className="grid grid-cols-[1fr_300px] gap-6 items-start">
frontend/src/app/jds/[id]/page.tsx CHANGED
@@ -151,7 +151,7 @@ export default function JDDetailPage() {
151
 
152
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-5 mb-8 flex gap-3 items-end flex-wrap">
153
  <div className="flex-1 min-w-[200px]">
154
- <label className="text-xs text-[var(--color-muted)] mb-1.5 block">Match Against Session</label>
155
  <select
156
  id="session-select"
157
  className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-2.5 text-sm outline-none focus:border-[var(--color-brand)] transition-all text-[var(--color-text)]"
@@ -200,7 +200,7 @@ export default function JDDetailPage() {
200
  {(match.results || []).map((c) => (
201
  <Link
202
  key={c.candidate_id}
203
- href={`/jds/${jdId}/candidates/${c.candidate_id}${selectedSession ? `?session_id=${selectedSession}` : ""}`}
204
  className="flex items-center gap-4 p-4 bg-[var(--color-card)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all group"
205
  >
206
  <div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 text-white ${c.rank <= 3 ? "bg-gradient-to-br from-amber-400 to-amber-600" : "bg-[var(--color-surface-2)] !text-[var(--color-muted)]"}`}>
 
151
 
152
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-5 mb-8 flex gap-3 items-end flex-wrap">
153
  <div className="flex-1 min-w-[200px]">
154
+ <label className="text-xs text-[var(--color-muted)] mb-1.5 block uppercase font-bold tracking-wider">Select Candidate Batch</label>
155
  <select
156
  id="session-select"
157
  className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-2.5 text-sm outline-none focus:border-[var(--color-brand)] transition-all text-[var(--color-text)]"
 
200
  {(match.results || []).map((c) => (
201
  <Link
202
  key={c.candidate_id}
203
+ href={`/jds/${jdId}/candidates/${c.candidate_id}${selectedSession ? `?batch=${selectedSession}` : ""}`}
204
  className="flex items-center gap-4 p-4 bg-[var(--color-card)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all group"
205
  >
206
  <div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 text-white ${c.rank <= 3 ? "bg-gradient-to-br from-amber-400 to-amber-600" : "bg-[var(--color-surface-2)] !text-[var(--color-muted)]"}`}>
frontend/src/app/page.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import { redirect } from 'next/navigation';
2
 
3
  export default function Home() {
4
- redirect('/pipeline');
5
  }
 
1
  import { redirect } from 'next/navigation';
2
 
3
  export default function Home() {
4
+ redirect('/sessions');
5
  }
frontend/src/app/pipeline/page.tsx CHANGED
@@ -128,7 +128,7 @@ export default function PipelinePage() {
128
 
129
  try {
130
  // 1. Create Session & JDs parallel
131
- const sessionPromise = api.createSession(sessionName, "Generated by Auto-Pipeline");
132
  const jdPromises = jds.map(jd => api.createJD(jd.title, jd.desc));
133
 
134
  const [session, ...createdJDs] = await Promise.all([sessionPromise, ...jdPromises]);
@@ -266,7 +266,7 @@ export default function PipelinePage() {
266
  {state.status === "idle" ? (
267
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-8 shadow-xl shadow-black/5">
268
  <div className="mb-6">
269
- <label className="block text-xs font-medium text-[var(--color-muted)] mb-2">Session Name</label>
270
  <input type="text" placeholder="e.g. Q3 Engineering Batch (100k)"
271
  className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-3 text-sm outline-none focus:border-[var(--color-brand)] transition-all"
272
  value={sessionName} onChange={e => setSessionName(e.target.value)} />
 
128
 
129
  try {
130
  // 1. Create Session & JDs parallel
131
+ const sessionPromise = api.createSession(sessionName, "Automated Candidate Batch Ingestion");
132
  const jdPromises = jds.map(jd => api.createJD(jd.title, jd.desc));
133
 
134
  const [session, ...createdJDs] = await Promise.all([sessionPromise, ...jdPromises]);
 
266
  {state.status === "idle" ? (
267
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-8 shadow-xl shadow-black/5">
268
  <div className="mb-6">
269
+ <label className="block text-xs font-bold text-[var(--color-muted)] mb-2 uppercase tracking-wider">Candidate Batch Name</label>
270
  <input type="text" placeholder="e.g. Q3 Engineering Batch (100k)"
271
  className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-3 text-sm outline-none focus:border-[var(--color-brand)] transition-all"
272
  value={sessionName} onChange={e => setSessionName(e.target.value)} />
frontend/src/app/sessions/[id]/page.tsx CHANGED
@@ -178,9 +178,9 @@ export default function SessionDetailPage({ params }: { params: Promise<{ id: st
178
  {/* HEADER */}
179
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-6 mb-6 flex justify-between items-center shadow-sm">
180
  <div>
181
- <Link href="/pipeline" className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] transition-colors mb-2 inline-block">← Back to Pipeline</Link>
182
- <h1 className="text-3xl font-bold tracking-tight">📁 {session.name}</h1>
183
- <p className="text-sm text-[var(--color-muted)] mt-1">{session.description || "Generated Session Sandbox"}</p>
184
  </div>
185
  <div className="text-right">
186
  <div className="text-3xl font-bold text-[var(--color-brand-light)]">{session.candidate_count.toLocaleString()}</div>
@@ -289,7 +289,7 @@ export default function SessionDetailPage({ params }: { params: Promise<{ id: st
289
  {match.results.map((c) => (
290
  <Link
291
  key={c.candidate_id}
292
- href={`/jds/${selectedJD}/candidates/${c.candidate_id}?session_id=${sessionId}`}
293
  className="flex items-center justify-between p-4 bg-[var(--color-surface-2)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand-light)] hover:bg-[var(--color-card)] hover:shadow-lg transition-all group"
294
  >
295
  <div className="flex items-center gap-5">
 
178
  {/* HEADER */}
179
  <div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-6 mb-6 flex justify-between items-center shadow-sm">
180
  <div>
181
+ <Link href="/sessions" className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] transition-colors mb-2 inline-block uppercase font-bold tracking-wider">← Back to Candidate Pools</Link>
182
+ <h1 className="text-3xl font-bold tracking-tight text-[var(--color-brand-light)]">🛡️ {session.name}</h1>
183
+ <p className="text-sm text-[var(--color-muted)] mt-1">{session.description || "Active Candidate Data Pool"}</p>
184
  </div>
185
  <div className="text-right">
186
  <div className="text-3xl font-bold text-[var(--color-brand-light)]">{session.candidate_count.toLocaleString()}</div>
 
289
  {match.results.map((c) => (
290
  <Link
291
  key={c.candidate_id}
292
+ href={`/jds/${selectedJD}/candidates/${c.candidate_id}?batch=${sessionId}`}
293
  className="flex items-center justify-between p-4 bg-[var(--color-surface-2)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand-light)] hover:bg-[var(--color-card)] hover:shadow-lg transition-all group"
294
  >
295
  <div className="flex items-center gap-5">
frontend/src/app/sessions/page.tsx CHANGED
@@ -44,13 +44,12 @@ export default function SessionsListPage() {
44
  if (error) return <div className="p-8 text-[var(--color-danger)] text-center font-semibold">Error: {error}</div>;
45
 
46
  return (
47
- <div className="max-w-7xl mx-auto px-6 py-10">
48
  <div className="flex items-center justify-between mb-8">
49
  <div>
50
- <h1 className="text-3xl font-bold tracking-tight mb-2">Ingestion Sessions</h1>
51
- <p className="text-[var(--color-muted)]">Select an ingestion pool below to interact with its associated candidate vectors.</p>
52
  </div>
53
- <Link href="/pipeline" className="flex items-center gap-2 px-4 py-2.5 bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] hover:border-[var(--color-brand-light)] hover:bg-[var(--color-brand-dim)] transition-all rounded-xl text-sm font-semibold">
54
  <LayoutDashboard className="w-4 h-4" /> Run New Pipeline
55
  </Link>
56
  </div>
@@ -92,8 +91,8 @@ export default function SessionsListPage() {
92
  </div>
93
 
94
  <div className="mt-4 pt-4 border-t border-[var(--color-border)]/50 flex justify-between items-center gap-3">
95
- <div className="text-[10px] font-mono text-[var(--color-dimmer)] truncate flex-1">{s.id}</div>
96
- <button onClick={(e) => handleCopy(e, s.id)} className="p-1.5 rounded hover:bg-[var(--color-surface-2)] text-[var(--color-muted)] hover:text-[var(--color-text)] transition-colors">
97
  {copiedId === s.id ? <CheckCircle2 className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
98
  </button>
99
  </div>
 
44
  if (error) return <div className="p-8 text-[var(--color-danger)] text-center font-semibold">Error: {error}</div>;
45
 
46
  return (
 
47
  <div className="flex items-center justify-between mb-8">
48
  <div>
49
+ <h1 className="text-3xl font-bold tracking-tight mb-2 text-[var(--color-brand-light)]">Candidate Pools</h1>
50
+ <p className="text-[var(--color-muted)] text-sm">Select a candidate batch below to interact with its associated semantic vectors.</p>
51
  </div>
52
+ <Link href="/pipeline" className="flex items-center gap-2 px-5 py-3 bg-[var(--color-brand)] border border-[var(--color-brand-glow)] hover:bg-[var(--color-brand-light)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all rounded-xl text-xs font-bold uppercase tracking-wider text-white">
53
  <LayoutDashboard className="w-4 h-4" /> Run New Pipeline
54
  </Link>
55
  </div>
 
91
  </div>
92
 
93
  <div className="mt-4 pt-4 border-t border-[var(--color-border)]/50 flex justify-between items-center gap-3">
94
+ <div className="text-[10px] uppercase font-bold text-[var(--color-dimmer)] tracking-widest flex-1">Metadata Verified</div>
95
+ <button onClick={(e) => handleCopy(e, s.id)} title="Copy Ingestion ID" className="p-1.5 rounded hover:bg-[var(--color-surface-2)] text-[var(--color-dimmer)] hover:text-[var(--color-text)] transition-colors">
96
  {copiedId === s.id ? <CheckCircle2 className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
97
  </button>
98
  </div>
supervisord.conf CHANGED
@@ -9,8 +9,10 @@ command=uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1
9
  directory=/app/backend
10
  autostart=true
11
  autorestart=true
12
- stdout_logfile=/tmp/backend.log
13
- stderr_logfile=/tmp/backend_err.log
 
 
14
  environment=PYTHONUNBUFFERED="1"
15
 
16
  [program:frontend]
@@ -18,8 +20,10 @@ command=node server.js
18
  directory=/app/frontend
19
  autostart=true
20
  autorestart=true
21
- stdout_logfile=/tmp/frontend.log
22
- stderr_logfile=/tmp/frontend_err.log
 
 
23
  environment=NODE_ENV="production",PORT="3000",HOSTNAME="127.0.0.1"
24
 
25
  [program:worker]
@@ -27,13 +31,17 @@ command=celery -A src.workers.celery_app.celery_app worker --loglevel=info --con
27
  directory=/app/backend
28
  autostart=true
29
  autorestart=true
30
- stdout_logfile=/tmp/worker.log
31
- stderr_logfile=/tmp/worker_err.log
 
 
32
  environment=PYTHONUNBUFFERED="1"
33
 
34
  [program:nginx]
35
  command=nginx -c /app/nginx.conf -g "daemon off;"
36
  autostart=true
37
  autorestart=true
38
- stdout_logfile=/tmp/nginx.log
39
- stderr_logfile=/tmp/nginx_err.log
 
 
 
9
  directory=/app/backend
10
  autostart=true
11
  autorestart=true
12
+ stdout_logfile=/dev/stdout
13
+ stdout_logfile_maxbytes=0
14
+ stderr_logfile=/dev/stderr
15
+ stderr_logfile_maxbytes=0
16
  environment=PYTHONUNBUFFERED="1"
17
 
18
  [program:frontend]
 
20
  directory=/app/frontend
21
  autostart=true
22
  autorestart=true
23
+ stdout_logfile=/dev/stdout
24
+ stdout_logfile_maxbytes=0
25
+ stderr_logfile=/dev/stderr
26
+ stderr_logfile_maxbytes=0
27
  environment=NODE_ENV="production",PORT="3000",HOSTNAME="127.0.0.1"
28
 
29
  [program:worker]
 
31
  directory=/app/backend
32
  autostart=true
33
  autorestart=true
34
+ stdout_logfile=/dev/stdout
35
+ stdout_logfile_maxbytes=0
36
+ stderr_logfile=/dev/stderr
37
+ stderr_logfile_maxbytes=0
38
  environment=PYTHONUNBUFFERED="1"
39
 
40
  [program:nginx]
41
  command=nginx -c /app/nginx.conf -g "daemon off;"
42
  autostart=true
43
  autorestart=true
44
+ stdout_logfile=/dev/stdout
45
+ stdout_logfile_maxbytes=0
46
+ stderr_logfile=/dev/stderr
47
+ stderr_logfile_maxbytes=0