aki-008 commited on
Commit
795dc3a
·
1 Parent(s): 893ff0d

chore : ai interview fix

Browse files
Backend/app/api/v1/api.py CHANGED
@@ -1,5 +1,5 @@
1
  from fastapi import APIRouter
2
- from app.api.v1.endpoints import auth, quiz, notes, vapi_ai
3
 
4
  api_router = APIRouter()
5
 
@@ -10,7 +10,6 @@ api_router.include_router(
10
  tags=["Authentication"]
11
  )
12
 
13
-
14
  # Include quiz routes
15
  api_router.include_router(
16
  quiz.router,
@@ -18,14 +17,16 @@ api_router.include_router(
18
  tags=["quiz"]
19
  )
20
 
 
21
  api_router.include_router(
22
  notes.router,
23
  prefix="/notes",
24
  tags=["notes"]
25
  )
26
 
 
27
  api_router.include_router(
28
  vapi_ai.router,
29
  prefix="/vapi",
30
- tags=["Voice AI"]
31
  )
 
1
  from fastapi import APIRouter
2
+ from app.api.v1.endpoints import auth, quiz, notes, vapi_ai # Add vapi import
3
 
4
  api_router = APIRouter()
5
 
 
10
  tags=["Authentication"]
11
  )
12
 
 
13
  # Include quiz routes
14
  api_router.include_router(
15
  quiz.router,
 
17
  tags=["quiz"]
18
  )
19
 
20
+ # Include notes routes
21
  api_router.include_router(
22
  notes.router,
23
  prefix="/notes",
24
  tags=["notes"]
25
  )
26
 
27
+ # Add Vapi routes
28
  api_router.include_router(
29
  vapi_ai.router,
30
  prefix="/vapi",
31
+ tags=["Voice Interview"]
32
  )
Backend/app/api/v1/endpoints/prompts.py CHANGED
@@ -48,11 +48,10 @@ Strictly follow the JSON structure and generate exactly 10 MCQs.
48
  """
49
 
50
  Interviewer_prompt = """
51
- f"You are an expert technical interviewer conducting an interview for the role of {job_role}. "
52
- f"The candidate has {experience} years of experience. "
53
- f"The difficulty level is {level}. "
54
- f"Start by welcoming {name} and asking a relevant opening question. "
55
- "Keep your responses concise and conversational. Do not output markdown or code blocks, just speak naturally. "
56
- "Assess their skills through follow-up questions."
57
-
58
  """
 
48
  """
49
 
50
  Interviewer_prompt = """
51
+ You are an expert technical interviewer conducting an interview for the role of {job_role}.
52
+ The candidate has {experience} years of experience.
53
+ The difficulty level is {level}.
54
+ Start by welcoming {name} and asking a relevant opening question.
55
+ Keep your responses concise and conversational. Do not output markdown or code blocks, just speak naturally.
56
+ Assess their skills through follow-up questions.
 
57
  """
Backend/app/api/v1/endpoints/vapi_ai.py CHANGED
@@ -1,36 +1,81 @@
1
- from fastapi import APIRouter, HTTPException, Body
2
- from pydantic import BaseModel
3
- from typing import Optional
4
- from .prompts import Interviewer_prompt
5
- from app.schema.models import VapiConfigRequest
6
  from app.config import settings
 
 
7
 
8
  router = APIRouter()
9
 
 
 
 
 
 
10
 
 
 
 
 
11
 
12
- @router.post("/get-vapi-config")
13
- async def get_vapi_config(config: VapiConfigRequest):
14
-
15
- system_prompt = Interviewer_prompt.format(name = config.name,
16
- job_role = config.job_role,
17
- experience = config.experience,
18
- level = config.level)
19
-
20
- return{
21
- "assistantId": "1184587d-21d7-48f4-8e82-623a2e574324",
22
- "overrides": {
23
- "variableValues": {
24
- "name": config.name,
25
- "job_role": config.job_role
26
- },
27
- "model": {
28
- "messages": [
29
- {
30
- "role": "system",
31
- "content": system_prompt
32
- }
33
- ]
 
 
 
 
 
 
 
 
 
 
34
  }
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends
2
+ from pydantic import BaseModel, Field
3
+ from app.services.vapi_service import vapi_service
 
 
4
  from app.config import settings
5
+ from app.api.deps import get_current_user
6
+ from app.models import User
7
 
8
  router = APIRouter()
9
 
10
+ class InterviewConfigRequest(BaseModel):
11
+ name: str = Field(..., min_length=2, max_length=100)
12
+ job_role: str = Field(..., min_length=2, max_length=100)
13
+ experience: str = Field(..., min_length=1)
14
+ difficulty: str = Field(default="Medium", pattern="^(Basic|Medium|Hard)$")
15
 
16
+ class VapiConfigResponse(BaseModel):
17
+ assistantId: str
18
+ publicKey: str
19
+ overrides: dict
20
 
21
+ @router.post("/get-vapi-config", response_model=VapiConfigResponse)
22
+ async def get_vapi_config(
23
+ request: InterviewConfigRequest,
24
+ # current_user: User = Depends(get_current_user)
25
+ ):
26
+ """
27
+ Returns Vapi configuration for starting an interview call.
28
+ Protected route - requires authentication.
29
+ """
30
+ try:
31
+ # Create dynamic assistant configuration
32
+ assistant_config = vapi_service.create_interview_assistant_config(
33
+ candidate_name=request.name,
34
+ job_role=request.job_role,
35
+ experience=request.experience,
36
+ difficulty=request.difficulty
37
+ )
38
+
39
+ # Create assistant (you can cache this for reuse)
40
+ # assistant_id = vapi_service.create_assistant(assistant_config)
41
+ assistant_id = settings.VAPI_ASSISTANT_ID
42
+
43
+ # Return config for frontend
44
+ return VapiConfigResponse(
45
+ assistantId=assistant_id,
46
+ publicKey=settings.VAPI_PUBLIC_KEY,
47
+ overrides={
48
+ "recordingEnabled": True,
49
+ "variableValues": {
50
+ "candidateName": request.name,
51
+ "jobRole": request.job_role
52
+ }
53
  }
54
+ )
55
+
56
+ except Exception as e:
57
+ print(f"Vapi Config Error: {e}")
58
+ raise HTTPException(
59
+ status_code=500,
60
+ detail=f"Failed to create interview configuration: {str(e)}"
61
+ )
62
+
63
+ @router.get("/call-recording/{call_id}")
64
+ async def get_call_recording(
65
+ call_id: str,
66
+ # current_user: User = Depends(get_current_user)
67
+ ):
68
+ """
69
+ Retrieves call recording and transcript after interview ends.
70
+ """
71
+ try:
72
+ recording_data = vapi_service.get_call_recording(call_id)
73
+
74
+ if not recording_data:
75
+ raise HTTPException(status_code=404, detail="Call recording not found")
76
+
77
+ return recording_data
78
+
79
+ except Exception as e:
80
+ print(f"Error fetching recording: {e}")
81
+ raise HTTPException(status_code=500, detail=str(e))
Backend/app/config.py CHANGED
@@ -16,13 +16,15 @@ class Settings(BaseSettings):
16
  chroma_port: int
17
  chroma_collection: str
18
 
19
- GROQ_API_KEY:str
20
-
21
- VAPI_ASSISTANT_ID: str
22
  VAPI_PRIVATE_KEY: str
 
 
23
 
24
  class Config:
25
  env_file = ".env"
26
- extra = "ignore" # quiz
27
 
28
- settings = Settings()
 
16
  chroma_port: int
17
  chroma_collection: str
18
 
19
+ GROQ_API_KEY: str
20
+
21
+ # Add Vapi Settings
22
  VAPI_PRIVATE_KEY: str
23
+ VAPI_PUBLIC_KEY: str
24
+ VAPI_ASSISTANT_ID: str
25
 
26
  class Config:
27
  env_file = ".env"
28
+ extra = "ignore"
29
 
30
+ settings = Settings()
Frontend/package-lock.json CHANGED
@@ -12,6 +12,7 @@
12
  "@splinetool/react-spline": "^4.1.0",
13
  "@splinetool/runtime": "^1.11.2",
14
  "@tailwindcss/vite": "^4.1.17",
 
15
  "axios": "^1.13.2",
16
  "clsx": "^2.1.1",
17
  "dayjs": "^1.11.19",
@@ -73,7 +74,6 @@
73
  "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
74
  "dev": true,
75
  "license": "MIT",
76
- "peer": true,
77
  "dependencies": {
78
  "@babel/code-frame": "^7.27.1",
79
  "@babel/generator": "^7.28.5",
@@ -277,6 +277,15 @@
277
  "@babel/core": "^7.0.0-0"
278
  }
279
  },
 
 
 
 
 
 
 
 
 
280
  "node_modules/@babel/template": {
281
  "version": "7.27.2",
282
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -325,6 +334,22 @@
325
  "node": ">=6.9.0"
326
  }
327
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  "node_modules/@esbuild/aix-ppc64": {
329
  "version": "0.25.12",
330
  "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -1537,6 +1562,81 @@
1537
  "win32"
1538
  ]
1539
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1540
  "node_modules/@splinetool/react-spline": {
1541
  "version": "4.1.0",
1542
  "resolved": "https://registry.npmjs.org/@splinetool/react-spline/-/react-spline-4.1.0.tgz",
@@ -1563,7 +1663,6 @@
1563
  "version": "1.11.2",
1564
  "resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-1.11.2.tgz",
1565
  "integrity": "sha512-rFz3KOQQRHQGzWBvPKRZcI7fZe5qxNYX1FmmCqzsbJkAU/hJdifaxpyN4xESpbkdta6s7riSmoz5lmPGIpZRRQ==",
1566
- "peer": true,
1567
  "dependencies": {
1568
  "on-change": "^4.0.0",
1569
  "semver-compare": "^1.0.0"
@@ -2007,7 +2106,6 @@
2007
  "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
2008
  "devOptional": true,
2009
  "license": "MIT",
2010
- "peer": true,
2011
  "dependencies": {
2012
  "undici-types": "~7.16.0"
2013
  }
@@ -2017,7 +2115,6 @@
2017
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
2018
  "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
2019
  "license": "MIT",
2020
- "peer": true,
2021
  "dependencies": {
2022
  "csstype": "^3.0.2"
2023
  }
@@ -2090,7 +2187,6 @@
2090
  "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
2091
  "dev": true,
2092
  "license": "MIT",
2093
- "peer": true,
2094
  "dependencies": {
2095
  "@typescript-eslint/scope-manager": "8.46.3",
2096
  "@typescript-eslint/types": "8.46.3",
@@ -2322,6 +2418,19 @@
2322
  "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
2323
  "license": "ISC"
2324
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2325
  "node_modules/@vitejs/plugin-react": {
2326
  "version": "5.1.0",
2327
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz",
@@ -2349,7 +2458,6 @@
2349
  "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
2350
  "dev": true,
2351
  "license": "MIT",
2352
- "peer": true,
2353
  "bin": {
2354
  "acorn": "bin/acorn"
2355
  },
@@ -2457,6 +2565,12 @@
2457
  "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==",
2458
  "license": "MIT"
2459
  },
 
 
 
 
 
 
2460
  "node_modules/brace-expansion": {
2461
  "version": "1.1.12",
2462
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -2501,7 +2615,6 @@
2501
  }
2502
  ],
2503
  "license": "MIT",
2504
- "peer": true,
2505
  "dependencies": {
2506
  "baseline-browser-mapping": "^2.8.19",
2507
  "caniuse-lite": "^1.0.30001751",
@@ -3091,7 +3204,6 @@
3091
  "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
3092
  "dev": true,
3093
  "license": "MIT",
3094
- "peer": true,
3095
  "dependencies": {
3096
  "@eslint-community/eslint-utils": "^4.8.0",
3097
  "@eslint-community/regexpp": "^4.12.1",
@@ -3279,6 +3391,15 @@
3279
  "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
3280
  "license": "MIT"
3281
  },
 
 
 
 
 
 
 
 
 
3282
  "node_modules/extend": {
3283
  "version": "3.0.2",
3284
  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -5483,7 +5604,6 @@
5483
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
5484
  "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
5485
  "license": "MIT",
5486
- "peer": true,
5487
  "engines": {
5488
  "node": ">=0.10.0"
5489
  }
@@ -5493,7 +5613,6 @@
5493
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
5494
  "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
5495
  "license": "MIT",
5496
- "peer": true,
5497
  "dependencies": {
5498
  "scheduler": "^0.27.0"
5499
  },
@@ -5550,7 +5669,6 @@
5550
  "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
5551
  "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
5552
  "license": "MIT",
5553
- "peer": true,
5554
  "dependencies": {
5555
  "@types/use-sync-external-store": "^0.0.6",
5556
  "use-sync-external-store": "^1.4.0"
@@ -5651,8 +5769,7 @@
5651
  "version": "5.0.1",
5652
  "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
5653
  "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
5654
- "license": "MIT",
5655
- "peer": true
5656
  },
5657
  "node_modules/redux-thunk": {
5658
  "version": "3.1.0",
@@ -6028,7 +6145,6 @@
6028
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
6029
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
6030
  "license": "MIT",
6031
- "peer": true,
6032
  "engines": {
6033
  "node": ">=12"
6034
  },
@@ -6107,7 +6223,6 @@
6107
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
6108
  "dev": true,
6109
  "license": "Apache-2.0",
6110
- "peer": true,
6111
  "bin": {
6112
  "tsc": "bin/tsc",
6113
  "tsserver": "bin/tsserver"
@@ -6339,7 +6454,6 @@
6339
  "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
6340
  "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
6341
  "license": "MIT",
6342
- "peer": true,
6343
  "dependencies": {
6344
  "esbuild": "^0.25.0",
6345
  "fdir": "^6.5.0",
@@ -6431,7 +6545,6 @@
6431
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
6432
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
6433
  "license": "MIT",
6434
- "peer": true,
6435
  "engines": {
6436
  "node": ">=12"
6437
  },
 
12
  "@splinetool/react-spline": "^4.1.0",
13
  "@splinetool/runtime": "^1.11.2",
14
  "@tailwindcss/vite": "^4.1.17",
15
+ "@vapi-ai/web": "^2.5.2",
16
  "axios": "^1.13.2",
17
  "clsx": "^2.1.1",
18
  "dayjs": "^1.11.19",
 
74
  "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
75
  "dev": true,
76
  "license": "MIT",
 
77
  "dependencies": {
78
  "@babel/code-frame": "^7.27.1",
79
  "@babel/generator": "^7.28.5",
 
277
  "@babel/core": "^7.0.0-0"
278
  }
279
  },
280
+ "node_modules/@babel/runtime": {
281
+ "version": "7.28.4",
282
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
283
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
284
+ "license": "MIT",
285
+ "engines": {
286
+ "node": ">=6.9.0"
287
+ }
288
+ },
289
  "node_modules/@babel/template": {
290
  "version": "7.27.2",
291
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
 
334
  "node": ">=6.9.0"
335
  }
336
  },
337
+ "node_modules/@daily-co/daily-js": {
338
+ "version": "0.85.0",
339
+ "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.85.0.tgz",
340
+ "integrity": "sha512-lpl111ZWNTUWDnwYcPuNi9PGJPbLCeCw6LzmEY40nG0hv1jg5JLVW8Rq3Cj/+lOCP6W6h4PXm211ss0FFnxITQ==",
341
+ "license": "BSD-2-Clause",
342
+ "dependencies": {
343
+ "@babel/runtime": "^7.12.5",
344
+ "@sentry/browser": "^8.33.1",
345
+ "bowser": "^2.8.1",
346
+ "dequal": "^2.0.3",
347
+ "events": "^3.1.0"
348
+ },
349
+ "engines": {
350
+ "node": ">=10.0.0"
351
+ }
352
+ },
353
  "node_modules/@esbuild/aix-ppc64": {
354
  "version": "0.25.12",
355
  "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
 
1562
  "win32"
1563
  ]
1564
  },
1565
+ "node_modules/@sentry-internal/browser-utils": {
1566
+ "version": "8.55.0",
1567
+ "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz",
1568
+ "integrity": "sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw==",
1569
+ "license": "MIT",
1570
+ "dependencies": {
1571
+ "@sentry/core": "8.55.0"
1572
+ },
1573
+ "engines": {
1574
+ "node": ">=14.18"
1575
+ }
1576
+ },
1577
+ "node_modules/@sentry-internal/feedback": {
1578
+ "version": "8.55.0",
1579
+ "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.55.0.tgz",
1580
+ "integrity": "sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw==",
1581
+ "license": "MIT",
1582
+ "dependencies": {
1583
+ "@sentry/core": "8.55.0"
1584
+ },
1585
+ "engines": {
1586
+ "node": ">=14.18"
1587
+ }
1588
+ },
1589
+ "node_modules/@sentry-internal/replay": {
1590
+ "version": "8.55.0",
1591
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.55.0.tgz",
1592
+ "integrity": "sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw==",
1593
+ "license": "MIT",
1594
+ "dependencies": {
1595
+ "@sentry-internal/browser-utils": "8.55.0",
1596
+ "@sentry/core": "8.55.0"
1597
+ },
1598
+ "engines": {
1599
+ "node": ">=14.18"
1600
+ }
1601
+ },
1602
+ "node_modules/@sentry-internal/replay-canvas": {
1603
+ "version": "8.55.0",
1604
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.55.0.tgz",
1605
+ "integrity": "sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w==",
1606
+ "license": "MIT",
1607
+ "dependencies": {
1608
+ "@sentry-internal/replay": "8.55.0",
1609
+ "@sentry/core": "8.55.0"
1610
+ },
1611
+ "engines": {
1612
+ "node": ">=14.18"
1613
+ }
1614
+ },
1615
+ "node_modules/@sentry/browser": {
1616
+ "version": "8.55.0",
1617
+ "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.55.0.tgz",
1618
+ "integrity": "sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw==",
1619
+ "license": "MIT",
1620
+ "dependencies": {
1621
+ "@sentry-internal/browser-utils": "8.55.0",
1622
+ "@sentry-internal/feedback": "8.55.0",
1623
+ "@sentry-internal/replay": "8.55.0",
1624
+ "@sentry-internal/replay-canvas": "8.55.0",
1625
+ "@sentry/core": "8.55.0"
1626
+ },
1627
+ "engines": {
1628
+ "node": ">=14.18"
1629
+ }
1630
+ },
1631
+ "node_modules/@sentry/core": {
1632
+ "version": "8.55.0",
1633
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.55.0.tgz",
1634
+ "integrity": "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==",
1635
+ "license": "MIT",
1636
+ "engines": {
1637
+ "node": ">=14.18"
1638
+ }
1639
+ },
1640
  "node_modules/@splinetool/react-spline": {
1641
  "version": "4.1.0",
1642
  "resolved": "https://registry.npmjs.org/@splinetool/react-spline/-/react-spline-4.1.0.tgz",
 
1663
  "version": "1.11.2",
1664
  "resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-1.11.2.tgz",
1665
  "integrity": "sha512-rFz3KOQQRHQGzWBvPKRZcI7fZe5qxNYX1FmmCqzsbJkAU/hJdifaxpyN4xESpbkdta6s7riSmoz5lmPGIpZRRQ==",
 
1666
  "dependencies": {
1667
  "on-change": "^4.0.0",
1668
  "semver-compare": "^1.0.0"
 
2106
  "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
2107
  "devOptional": true,
2108
  "license": "MIT",
 
2109
  "dependencies": {
2110
  "undici-types": "~7.16.0"
2111
  }
 
2115
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
2116
  "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
2117
  "license": "MIT",
 
2118
  "dependencies": {
2119
  "csstype": "^3.0.2"
2120
  }
 
2187
  "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
2188
  "dev": true,
2189
  "license": "MIT",
 
2190
  "dependencies": {
2191
  "@typescript-eslint/scope-manager": "8.46.3",
2192
  "@typescript-eslint/types": "8.46.3",
 
2418
  "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
2419
  "license": "ISC"
2420
  },
2421
+ "node_modules/@vapi-ai/web": {
2422
+ "version": "2.5.2",
2423
+ "resolved": "https://registry.npmjs.org/@vapi-ai/web/-/web-2.5.2.tgz",
2424
+ "integrity": "sha512-mT4DjApi0/0+EK77h2xOLq3qVBa7rv3JNQ+gFWuhFE4YdGYxI51+fn8bQI9N535+zU/Z4jFKXotdBHQJ3filHA==",
2425
+ "license": "MIT",
2426
+ "dependencies": {
2427
+ "@daily-co/daily-js": "^0.85.0",
2428
+ "events": "^3.3.0"
2429
+ },
2430
+ "engines": {
2431
+ "node": ">=18.0.0"
2432
+ }
2433
+ },
2434
  "node_modules/@vitejs/plugin-react": {
2435
  "version": "5.1.0",
2436
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz",
 
2458
  "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
2459
  "dev": true,
2460
  "license": "MIT",
 
2461
  "bin": {
2462
  "acorn": "bin/acorn"
2463
  },
 
2565
  "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==",
2566
  "license": "MIT"
2567
  },
2568
+ "node_modules/bowser": {
2569
+ "version": "2.13.1",
2570
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz",
2571
+ "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==",
2572
+ "license": "MIT"
2573
+ },
2574
  "node_modules/brace-expansion": {
2575
  "version": "1.1.12",
2576
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
 
2615
  }
2616
  ],
2617
  "license": "MIT",
 
2618
  "dependencies": {
2619
  "baseline-browser-mapping": "^2.8.19",
2620
  "caniuse-lite": "^1.0.30001751",
 
3204
  "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
3205
  "dev": true,
3206
  "license": "MIT",
 
3207
  "dependencies": {
3208
  "@eslint-community/eslint-utils": "^4.8.0",
3209
  "@eslint-community/regexpp": "^4.12.1",
 
3391
  "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
3392
  "license": "MIT"
3393
  },
3394
+ "node_modules/events": {
3395
+ "version": "3.3.0",
3396
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
3397
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
3398
+ "license": "MIT",
3399
+ "engines": {
3400
+ "node": ">=0.8.x"
3401
+ }
3402
+ },
3403
  "node_modules/extend": {
3404
  "version": "3.0.2",
3405
  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
 
5604
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
5605
  "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
5606
  "license": "MIT",
 
5607
  "engines": {
5608
  "node": ">=0.10.0"
5609
  }
 
5613
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
5614
  "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
5615
  "license": "MIT",
 
5616
  "dependencies": {
5617
  "scheduler": "^0.27.0"
5618
  },
 
5669
  "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
5670
  "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
5671
  "license": "MIT",
 
5672
  "dependencies": {
5673
  "@types/use-sync-external-store": "^0.0.6",
5674
  "use-sync-external-store": "^1.4.0"
 
5769
  "version": "5.0.1",
5770
  "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
5771
  "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
5772
+ "license": "MIT"
 
5773
  },
5774
  "node_modules/redux-thunk": {
5775
  "version": "3.1.0",
 
6145
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
6146
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
6147
  "license": "MIT",
 
6148
  "engines": {
6149
  "node": ">=12"
6150
  },
 
6223
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
6224
  "dev": true,
6225
  "license": "Apache-2.0",
 
6226
  "bin": {
6227
  "tsc": "bin/tsc",
6228
  "tsserver": "bin/tsserver"
 
6454
  "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
6455
  "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
6456
  "license": "MIT",
 
6457
  "dependencies": {
6458
  "esbuild": "^0.25.0",
6459
  "fdir": "^6.5.0",
 
6545
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
6546
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
6547
  "license": "MIT",
 
6548
  "engines": {
6549
  "node": ">=12"
6550
  },
Frontend/package.json CHANGED
@@ -13,6 +13,7 @@
13
  "@splinetool/react-spline": "^4.1.0",
14
  "@splinetool/runtime": "^1.11.2",
15
  "@tailwindcss/vite": "^4.1.17",
 
16
  "axios": "^1.13.2",
17
  "clsx": "^2.1.1",
18
  "dayjs": "^1.11.19",
@@ -47,4 +48,4 @@
47
  "keywords": [],
48
  "author": "",
49
  "license": "ISC"
50
- }
 
13
  "@splinetool/react-spline": "^4.1.0",
14
  "@splinetool/runtime": "^1.11.2",
15
  "@tailwindcss/vite": "^4.1.17",
16
+ "@vapi-ai/web": "^2.5.2",
17
  "axios": "^1.13.2",
18
  "clsx": "^2.1.1",
19
  "dayjs": "^1.11.19",
 
48
  "keywords": [],
49
  "author": "",
50
  "license": "ISC"
51
+ }
Frontend/src/pages/AiInterview.tsx CHANGED
@@ -1,249 +1,47 @@
1
- import React, { useState, useEffect } from "react";
2
- import {
3
- Send,
4
- Settings,
5
- CheckCircle,
6
- Mic,
7
- PhoneOff,
8
- Volume2,
9
- Loader2,
10
- Activity,
11
- } from "lucide-react";
12
- import Vapi from "@vapi-ai/web";
13
- import API from "../api/api"; // Your Axios instance
14
- import { useAuth } from "../components/context/AuthContext";
15
 
16
- // --- CONFIG ---
17
- const VAPI_PUBLIC_KEY = "6e393730-74a2-4690-8cb7-845ed3880488"; // Replace with your key
18
- const vapi = new Vapi(VAPI_PUBLIC_KEY);
 
19
 
20
- type InterviewState = "config" | "chat" | "results";
21
-
22
- const AIInterview: React.FC = () => {
23
- const { username } = useAuth(); // Get logged in user name
24
- const [interviewState, setInterviewState] =
25
- useState<InterviewState>("config");
26
-
27
- // Form State
28
- const [jobRole, setJobRole] = useState("");
29
- const [experience, setExperience] = useState("");
30
- const [level, setLevel] = useState("Medium");
31
-
32
- // Vapi State
33
- const [isSessionActive, setIsSessionActive] = useState(false);
34
- const [isSpeaking, setIsSpeaking] = useState(false);
35
- const [status, setStatus] = useState("Idle");
36
- const [volumeLevel, setVolumeLevel] = useState(0);
37
-
38
- // --- VAPI EVENTS ---
39
- useEffect(() => {
40
- vapi.on("call-start", () => {
41
- setStatus("Connected");
42
- setIsSessionActive(true);
43
- setInterviewState("chat"); // Switch UI to chat when call starts
44
- });
45
-
46
- vapi.on("call-end", () => {
47
- setStatus("Call Ended");
48
- setIsSessionActive(false);
49
- setIsSpeaking(false);
50
- setInterviewState("results"); // Switch UI to results when call ends
51
- });
52
-
53
- vapi.on("speech-start", () => setIsSpeaking(true));
54
- vapi.on("speech-end", () => setIsSpeaking(false));
55
-
56
- vapi.on("volume-level", (level) => setVolumeLevel(level)); // Optional: for animation
57
-
58
- vapi.on("error", (e) => {
59
- console.error("Vapi Error:", e);
60
- setStatus("Error connecting");
61
- setIsSessionActive(false);
62
- });
63
-
64
- // Cleanup
65
- return () => {
66
- vapi.stop();
67
- vapi.removeAllListeners();
68
- };
69
- }, []);
70
-
71
- // --- ACTIONS ---
72
-
73
- const startInterview = async () => {
74
- if (!jobRole || !experience) {
75
- alert("Please fill in Job Role and Experience.");
76
- return;
77
- }
78
 
79
- setStatus("Configuring AI...");
 
80
 
81
- try {
82
- // 1. Get dynamic config from YOUR backend
83
- const response = await API.post("/vapi/get-vapi-config", {
84
- name: username,
85
- job_role: jobRole,
86
- experience: experience,
87
- level: level,
88
- });
 
 
89
 
90
- const { assistantId, overrides } = response.data;
91
 
92
- setStatus("Connecting...");
 
93
 
94
- // 2. Start Vapi Call
95
- await vapi.start(assistantId, overrides);
96
  } catch (err) {
97
  console.error("Failed to start interview:", err);
98
- setStatus("Failed to start");
99
- alert("Could not start interview. Check backend connection.");
100
  }
101
  };
102
 
103
- const endInterview = () => {
104
- vapi.stop();
105
- // State change to 'results' happens in 'call-end' listener
106
- };
107
-
108
- // --- RENDERERS ---
109
-
110
- // 1. Configuration Phase (Kept mostly same as original)
111
- const renderConfig = () => (
112
- <div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
113
- <h3 className="text-2xl font-semibold mb-6 flex items-center gap-2 text-blue-700">
114
- <Settings size={24} /> Configure Your Interview
115
- </h3>
116
-
117
- <div className="space-y-6">
118
- <label className="block">
119
- <span className="text-gray-700 font-medium">
120
- 1. Job Role/Position
121
- </span>
122
- <input
123
- type="text"
124
- value={jobRole}
125
- onChange={(e) => setJobRole(e.target.value)}
126
- placeholder="e.g., Senior Frontend Developer"
127
- className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500"
128
- />
129
- </label>
130
-
131
- <label className="block">
132
- <span className="text-gray-700 font-medium">
133
- 2. Years of Experience
134
- </span>
135
- <input
136
- type="number"
137
- value={experience}
138
- onChange={(e) => setExperience(e.target.value)}
139
- placeholder="e.g., 5"
140
- className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500"
141
- />
142
- </label>
143
-
144
- <label className="block">
145
- <span className="text-gray-700 font-medium">3. Difficulty Level</span>
146
- <select
147
- value={level}
148
- onChange={(e) => setLevel(e.target.value)}
149
- className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 bg-white"
150
- >
151
- <option value="Basic">Basic</option>
152
- <option value="Medium">Medium</option>
153
- <option value="Hard">Hard</option>
154
- </select>
155
- </label>
156
- </div>
157
-
158
- <button
159
- onClick={startInterview}
160
- disabled={status === "Configuring AI..." || status === "Connecting..."}
161
- className="mt-8 px-8 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition duration-150 flex items-center gap-2 disabled:opacity-70"
162
- >
163
- {status === "Configuring AI..." || status === "Connecting..." ? (
164
- <Loader2 className="animate-spin" />
165
- ) : (
166
- <Mic size={20} />
167
- )}
168
- {status === "Idle" || status === "Error"
169
- ? "Start Voice Interview"
170
- : status}
171
- </button>
172
- </div>
173
- );
174
-
175
- // 2. Active Chat Phase (Modified for Voice UI)
176
- const renderChat = () => (
177
- <div className="flex flex-col items-center justify-center bg-white p-8 rounded-xl shadow-lg min-h-[500px] relative">
178
- <h3 className="text-xl font-bold mb-2">Live Interview</h3>
179
- <p className="text-gray-600 mb-8">
180
- {jobRole} • {level} Level
181
- </p>
182
-
183
- {/* Dynamic AI Ball based on Speaking State */}
184
- <div className="relative mb-10">
185
- <div
186
- className={`flex items-center justify-center w-32 h-32 rounded-full shadow-2xl transition-all duration-300 ${
187
- isSpeaking ? "bg-purple-600 scale-110" : "bg-blue-600 scale-100"
188
- }`}
189
- style={{
190
- boxShadow: isSpeaking
191
- ? `0 0 ${30 + volumeLevel * 50}px rgba(147, 51, 234, 0.6)`
192
- : "0 0 20px rgba(37, 99, 235, 0.3)",
193
- }}
194
- >
195
- {isSpeaking ? (
196
- <Volume2 className="w-12 h-12 text-white animate-pulse" />
197
- ) : (
198
- <Activity className="w-12 h-12 text-white" />
199
- )}
200
- </div>
201
-
202
- {/* Status Indicator */}
203
- <div className="absolute -bottom-10 left-1/2 -translate-x-1/2 whitespace-nowrap text-gray-500 font-medium animate-pulse">
204
- {isSpeaking ? "AI is speaking..." : "Listening to you..."}
205
- </div>
206
- </div>
207
-
208
- <button
209
- onClick={endInterview}
210
- className="mt-8 flex items-center gap-2 px-6 py-3 bg-red-100 text-red-600 rounded-full hover:bg-red-200 transition font-semibold"
211
- >
212
- <PhoneOff size={20} /> End Interview
213
- </button>
214
- </div>
215
- );
216
-
217
- // 3. Results Phase
218
- const renderResults = () => (
219
- <div className="bg-green-50 p-8 rounded-xl shadow-xl text-center">
220
- <CheckCircle size={48} className="text-green-600 mx-auto mb-4" />
221
- <h3 className="text-3xl font-bold text-green-700 mb-2">
222
- Interview Completed
223
- </h3>
224
- <p className="text-gray-700 mb-6">
225
- The AI interviewer has finished assessing your responses. (Integration
226
- with transcript analysis would go here).
227
- </p>
228
- <button
229
- onClick={() => setInterviewState("config")}
230
- className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition duration-150"
231
- >
232
- Start New Interview
233
- </button>
234
- </div>
235
- );
236
-
237
  return (
238
- <div className="p-8 max-w-4xl mx-auto font-opensans">
239
- <h1 className="text-4xl font-extrabold text-gray-800 mb-6">
240
- AI Voice Interview 🎙️
241
- </h1>
242
- {interviewState === "config" && renderConfig()}
243
- {interviewState === "chat" && renderChat()}
244
- {interviewState === "results" && renderResults()}
245
  </div>
246
  );
247
- };
248
-
249
- export default AIInterview;
 
1
+ // AiInterview.tsx
2
+ import { useEffect, useState } from "react";
3
+ import { useVapi } from "../hooks/useVapi";
4
+ import { getVapiConfig } from "../api/vapiService";
 
 
 
 
 
 
 
 
 
 
5
 
6
+ export default function AiInterview() {
7
+ const [interviewConfig, setInterviewConfig] = useState(null);
8
+ const [vapiKey, setVapiKey] = useState<string | null>(null);
9
+ const { vapiClient, start, stop } = useVapi(vapiKey);
10
 
11
+ const handleStartInterview = async () => {
12
+ try {
13
+ // 1. Ask backend for config
14
+ const cfg = await getVapiConfig(interviewConfig);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ // 2. Save publicKey → this triggers useVapi to create the client
17
+ setVapiKey(cfg.publicKey);
18
 
19
+ // 3. Wait until client is created
20
+ const waitForClient = () =>
21
+ new Promise<void>((resolve) => {
22
+ const interval = setInterval(() => {
23
+ if (vapiClient) {
24
+ clearInterval(interval);
25
+ resolve();
26
+ }
27
+ }, 50);
28
+ });
29
 
30
+ await waitForClient();
31
 
32
+ // 4. Start the actual voice session
33
+ await start(cfg.assistantId, cfg.overrides);
34
 
35
+ console.log("Interview started!");
 
36
  } catch (err) {
37
  console.error("Failed to start interview:", err);
 
 
38
  }
39
  };
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  return (
42
+ <div>
43
+ <button onClick={handleStartInterview}>Start Interview</button>
44
+ <button onClick={stop}>Stop</button>
 
 
 
 
45
  </div>
46
  );
47
+ }
 
 
Frontend/tsconfig.app.json CHANGED
@@ -22,7 +22,11 @@
22
  "noUnusedParameters": true,
23
  "erasableSyntaxOnly": true,
24
  "noFallthroughCasesInSwitch": true,
25
- "noUncheckedSideEffectImports": true
 
 
 
 
26
  },
27
  "include": ["src"]
28
  }
 
22
  "noUnusedParameters": true,
23
  "erasableSyntaxOnly": true,
24
  "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true,
26
+ "baseUrl": ".",
27
+ "paths": {
28
+ "@/*": ["./src/*"]
29
+ }
30
  },
31
  "include": ["src"]
32
  }