ketannnn commited on
Commit
5aed951
·
1 Parent(s): 6434bb7

fix(ui): add navbar link to API Docs alongside system reset

Browse files
backend/src/models/jd.py CHANGED
@@ -25,5 +25,6 @@ class JobDescription(Base):
25
  embedding_text: Mapped[str | None] = mapped_column(Text, nullable=True)
26
  qdrant_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
27
  status: Mapped[str] = mapped_column(String(32), default="pending")
 
28
  created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
29
  updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
 
25
  embedding_text: Mapped[str | None] = mapped_column(Text, nullable=True)
26
  qdrant_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
27
  status: Mapped[str] = mapped_column(String(32), default="pending")
28
+ session_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
29
  created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
30
  updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
backend/src/routers/jds.py CHANGED
@@ -17,6 +17,7 @@ async def create_jd(payload: JDCreate, db: AsyncSession = Depends(get_db)):
17
  id=uuid.uuid4(),
18
  title=payload.title,
19
  raw_text=payload.raw_text,
 
20
  status="processing",
21
  )
22
  db.add(jd)
@@ -29,8 +30,11 @@ async def create_jd(payload: JDCreate, db: AsyncSession = Depends(get_db)):
29
 
30
 
31
  @router.get("", response_model=list[JDListItem])
32
- async def list_jds(db: AsyncSession = Depends(get_db)):
33
- result = await db.execute(select(JobDescription).order_by(JobDescription.created_at.desc()).limit(50))
 
 
 
34
  return result.scalars().all()
35
 
36
 
 
17
  id=uuid.uuid4(),
18
  title=payload.title,
19
  raw_text=payload.raw_text,
20
+ session_id=payload.session_id,
21
  status="processing",
22
  )
23
  db.add(jd)
 
30
 
31
 
32
  @router.get("", response_model=list[JDListItem])
33
+ async def list_jds(session_id: uuid.UUID | None = None, db: AsyncSession = Depends(get_db)):
34
+ stmt = select(JobDescription).order_by(JobDescription.created_at.desc())
35
+ if session_id:
36
+ stmt = stmt.where(JobDescription.session_id == session_id)
37
+ result = await db.execute(stmt.limit(50))
38
  return result.scalars().all()
39
 
40
 
backend/src/schemas/jd.py CHANGED
@@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
7
  class JDCreate(BaseModel):
8
  title: str
9
  raw_text: str
 
10
 
11
 
12
  class JDResponse(BaseModel):
@@ -30,6 +31,7 @@ class JDListItem(BaseModel):
30
  id: UUID
31
  title: str
32
  status: str
 
33
  jd_quality: dict[str, Any] = {}
34
  created_at: datetime
35
 
 
7
  class JDCreate(BaseModel):
8
  title: str
9
  raw_text: str
10
+ session_id: UUID | None = None
11
 
12
 
13
  class JDResponse(BaseModel):
 
31
  id: UUID
32
  title: str
33
  status: str
34
+ session_id: UUID | None = None
35
  jd_quality: dict[str, Any] = {}
36
  created_at: datetime
37
 
frontend/src/app/layout.tsx CHANGED
@@ -20,6 +20,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
20
  <Link href="/reset" className="px-3 py-1.5 rounded-lg text-xs font-medium text-slate-500 hover:text-red-400 transition-colors">
21
  Reset
22
  </Link>
 
 
 
23
  <Link href="/pipeline" className="ml-2 px-3 py-1.5 rounded-lg text-sm font-semibold text-[var(--color-brand-light)] bg-[var(--color-brand-dim)] border border-[var(--color-brand-glow)] hover:bg-[var(--color-brand)] hover:text-white transition-all">
24
  ⚡ Auto Pipeline
25
  </Link>
 
20
  <Link href="/reset" className="px-3 py-1.5 rounded-lg text-xs font-medium text-slate-500 hover:text-red-400 transition-colors">
21
  Reset
22
  </Link>
23
+ <a href="/docs" target="_blank" rel="noreferrer" className="px-3 py-1.5 rounded-lg text-xs font-medium text-slate-500 hover:text-[var(--color-brand-light)] transition-colors">
24
+ API Docs ↗
25
+ </a>
26
  <Link href="/pipeline" className="ml-2 px-3 py-1.5 rounded-lg text-sm font-semibold text-[var(--color-brand-light)] bg-[var(--color-brand-dim)] border border-[var(--color-brand-glow)] hover:bg-[var(--color-brand)] hover:text-white transition-all">
27
  ⚡ Auto Pipeline
28
  </Link>
frontend/src/app/pipeline/page.tsx CHANGED
@@ -127,17 +127,19 @@ export default function PipelinePage() {
127
  updateState({ status: "uploading", sessionName, jdsInfo: jds, startTime: start, elapsedTime: 0 });
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]);
 
 
135
  const jdIds = createdJDs.map(j => (j as any).id);
136
 
137
- updateState({ sessionId: (session as any).id, jdIds });
138
 
139
- // 2. Upload file
140
- const uploadRes = await api.uploadCandidates(file, (session as any).id);
141
 
142
  updateState({ status: "embedding", taskId: uploadRes.task_id });
143
 
 
127
  updateState({ status: "uploading", sessionName, jdsInfo: jds, startTime: start, elapsedTime: 0 });
128
 
129
  try {
130
+ // 1. Create Session first
131
+ const session = await api.createSession(sessionName, "Automated Candidate Batch Ingestion");
132
+ const sessionIdStr = (session as any).id;
133
+
134
+ // 2. Create JDs scoped to that session
135
+ const jdPromises = jds.map(jd => api.createJD(jd.title, jd.desc, sessionIdStr));
136
+ const createdJDs = await Promise.all(jdPromises);
137
  const jdIds = createdJDs.map(j => (j as any).id);
138
 
139
+ updateState({ sessionId: sessionIdStr, jdIds });
140
 
141
+ // 3. Upload file
142
+ const uploadRes = await api.uploadCandidates(file, sessionIdStr);
143
 
144
  updateState({ status: "embedding", taskId: uploadRes.task_id });
145
 
frontend/src/app/sessions/[id]/page.tsx CHANGED
@@ -37,18 +37,11 @@ export default function SessionDetailPage({ params }: { params: Promise<{ id: st
37
 
38
  const loadData = useCallback(async () => {
39
  try {
40
- const [s, jList] = await Promise.all([api.getSession(sessionId), api.listJDs()]);
41
  setSession(s);
42
-
43
- // Filter JDs to only those created for this specific session
44
- // The pipeline page stores this mapping in localStorage when it finishes
45
- let filteredJDs = jList as JD[];
46
- try {
47
- const mapping = JSON.parse(localStorage.getItem("tp_session_jds") || "{}");
48
- if (mapping[sessionId] && Array.isArray(mapping[sessionId]) && mapping[sessionId].length > 0) {
49
- filteredJDs = filteredJDs.filter(jd => mapping[sessionId].includes(jd.id));
50
- }
51
- } catch (e) { }
52
  setJDs(filteredJDs);
53
 
54
  // If a JD is selected, load its match info automatically
 
37
 
38
  const loadData = useCallback(async () => {
39
  try {
40
+ const [s, jList] = await Promise.all([api.getSession(sessionId), api.listJDs(sessionId)]);
41
  setSession(s);
42
+
43
+ // JDs are now natively scoped to the session id via the backend API
44
+ const filteredJDs = jList as JD[];
 
 
 
 
 
 
 
45
  setJDs(filteredJDs);
46
 
47
  // If a JD is selected, load its match info automatically
frontend/src/lib/api.ts CHANGED
@@ -120,9 +120,9 @@ export const api = {
120
  getSession: (id: string) => request<SessionInfo>(`/api/sessions/${id}`),
121
  deleteSession: (id: string) => request<void>(`/api/sessions/${id}`, { method: "DELETE" }),
122
 
123
- createJD: (title: string, raw_text: string) =>
124
- request<JD>("/api/jds", { method: "POST", body: JSON.stringify({ title, raw_text }) }),
125
- listJDs: () => request<JD[]>("/api/jds"),
126
  getJD: (id: string) => request<JD>(`/api/jds/${id}`),
127
  updateJDWeights: (id: string, weights: Record<string, number>) =>
128
  request<JD>(`/api/jds/${id}/weights`, { method: "PATCH", body: JSON.stringify({ weights }) }),
 
120
  getSession: (id: string) => request<SessionInfo>(`/api/sessions/${id}`),
121
  deleteSession: (id: string) => request<void>(`/api/sessions/${id}`, { method: "DELETE" }),
122
 
123
+ createJD: (title: string, raw_text: string, session_id?: string) =>
124
+ request<JD>("/api/jds", { method: "POST", body: JSON.stringify({ title, raw_text, session_id }) }),
125
+ listJDs: (session_id?: string) => request<JD[]>(session_id ? `/api/jds?session_id=${session_id}` : "/api/jds"),
126
  getJD: (id: string) => request<JD>(`/api/jds/${id}`),
127
  updateJDWeights: (id: string, weights: Record<string, number>) =>
128
  request<JD>(`/api/jds/${id}/weights`, { method: "PATCH", body: JSON.stringify({ weights }) }),
nginx.conf CHANGED
@@ -31,6 +31,20 @@ http {
31
  proxy_pass http://127.0.0.1:8000/health;
32
  }
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  location / {
35
  proxy_pass http://127.0.0.1:3000;
36
  proxy_set_header Host $host;
 
31
  proxy_pass http://127.0.0.1:8000/health;
32
  }
33
 
34
+ # Route FastAPI Swagger Docs correctly
35
+ location /docs {
36
+ proxy_pass http://127.0.0.1:8000/docs;
37
+ proxy_set_header Host $host;
38
+ proxy_set_header X-Real-IP $remote_addr;
39
+ }
40
+
41
+ # Route OpenAPI schema for Swagger
42
+ location /openapi.json {
43
+ proxy_pass http://127.0.0.1:8000/openapi.json;
44
+ proxy_set_header Host $host;
45
+ proxy_set_header X-Real-IP $remote_addr;
46
+ }
47
+
48
  location / {
49
  proxy_pass http://127.0.0.1:3000;
50
  proxy_set_header Host $host;