cathyfu1215 commited on
Commit
62dd132
·
1 Parent(s): 5a619a5

Add Hugging Face Spaces configuration

Browse files
Files changed (4) hide show
  1. Dockerfile +22 -0
  2. README.md +21 -4
  3. app.py +3 -897
  4. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install ffmpeg for audio processing
6
+ RUN apt-get update && apt-get install -y ffmpeg && apt-get clean
7
+
8
+ # Install dependencies
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy application
13
+ COPY . .
14
+
15
+ # Create necessary directories if they don't exist
16
+ RUN mkdir -p /app/templates /app/static
17
+
18
+ # Expose the port the app runs on
19
+ EXPOSE 7860
20
+
21
+ # Start the Flask app
22
+ CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=7860"]
README.md CHANGED
@@ -1,11 +1,28 @@
1
  ---
2
- title: Huskyinterviewprep
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
  ---
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Husky Interview Prep
3
+ emoji: 🐺
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
+ # Husky Interview Prep
12
+
13
+ An AI-powered interview preparation assistant that helps you practice and improve your interview skills.
14
+
15
+ ## Features
16
+
17
+ - Parse job descriptions to identify key skills and requirements
18
+ - Generate relevant interview questions based on job requirements
19
+ - Record and transcribe your practice answers
20
+ - Get detailed feedback on your responses
21
+ - View model answers for comparison
22
+ - Save your practice sessions for later review
23
+
24
+ ## Technology
25
+
26
+ Built with Flask, Together.ai LLM API, and modern web technologies.
27
+
28
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -782,900 +782,6 @@ if __name__ == "__main__":
782
  import shutil
783
  shutil.copy('interviewer.png', 'static/interviewer.png')
784
 
785
- # Create HTML template file
786
- with open('templates/index.html', 'w') as f:
787
- f.write('''
788
- <!DOCTYPE html>
789
- <html lang="en">
790
- <head>
791
- <meta charset="UTF-8">
792
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
793
- <title>Husky Interview Prep</title>
794
- <!-- Tailwind CSS -->
795
- <script src="https://cdn.tailwindcss.com"></script>
796
- <!-- Alpine.js -->
797
- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
798
- <!-- Font Awesome -->
799
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
800
- <style>
801
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
802
-
803
- body {
804
- font-family: 'Inter', sans-serif;
805
- background-color: #f3f4f6;
806
- }
807
-
808
- .gradient-bg {
809
- background: linear-gradient(to right, #4f46e5, #8b5cf6);
810
- }
811
-
812
- .container {
813
- max-width: 1200px;
814
- margin: 0 auto;
815
- }
816
-
817
- .orb {
818
- position: absolute;
819
- border-radius: 50%;
820
- filter: blur(80px);
821
- z-index: 0;
822
- animation: float 6s ease-in-out infinite;
823
- opacity: 0.5;
824
- }
825
-
826
- .orb-1 {
827
- width: 300px;
828
- height: 300px;
829
- background: #4f46e5;
830
- top: -150px;
831
- right: -100px;
832
- animation-delay: 0s;
833
- }
834
-
835
- .orb-2 {
836
- width: 250px;
837
- height: 250px;
838
- background: #8b5cf6;
839
- bottom: -100px;
840
- left: -50px;
841
- animation-delay: 3s;
842
- }
843
-
844
- @keyframes float {
845
- 0%, 100% { transform: translateY(0); }
846
- 50% { transform: translateY(-20px); }
847
- }
848
-
849
- .tab-active {
850
- background-color: #4f46e5;
851
- color: white;
852
- }
853
-
854
- .question-card {
855
- transition: all 0.3s ease;
856
- }
857
-
858
- .question-card:hover {
859
- transform: translateY(-2px);
860
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
861
- }
862
-
863
- .btn-primary {
864
- background-color: #4f46e5;
865
- color: white;
866
- transition: all 0.3s ease;
867
- }
868
-
869
- .btn-primary:hover {
870
- background-color: #4338ca;
871
- transform: translateY(-1px);
872
- }
873
-
874
- .btn-secondary {
875
- background-color: #f3f4f6;
876
- color: #1f2937;
877
- border: 1px solid #d1d5db;
878
- transition: all 0.3s ease;
879
- }
880
-
881
- .btn-secondary:hover {
882
- background-color: #e5e7eb;
883
- transform: translateY(-1px);
884
- }
885
-
886
- .star-rating {
887
- display: inline-block;
888
- }
889
-
890
- .recording-pulse {
891
- position: relative;
892
- }
893
-
894
- .recording-pulse::before {
895
- content: '';
896
- position: absolute;
897
- width: 100%;
898
- height: 100%;
899
- border-radius: 50%;
900
- background-color: rgba(239, 68, 68, 0.7);
901
- animation: pulse 2s infinite;
902
- }
903
-
904
- @keyframes pulse {
905
- 0% { transform: scale(1); opacity: 1; }
906
- 100% { transform: scale(1.5); opacity: 0; }
907
- }
908
-
909
- .section-enter {
910
- transition: all 0.5s ease;
911
- opacity: 0;
912
- transform: translateY(20px);
913
- }
914
-
915
- .section-enter-active {
916
- opacity: 1;
917
- transform: translateY(0);
918
- }
919
-
920
- /* Add these to the existing style section in the head */
921
- .star-1 { color: #FF5252; } /* Red */
922
- .star-2 { color: #FF7F00; } /* Orange */
923
- .star-3 { color: #FFFF00; } /* Yellow */
924
- .star-4 { color: #7FFF00; } /* Chartreuse */
925
- .star-5 { color: #00FF00; } /* Green */
926
- .star-6 { color: #00FFFF; } /* Cyan */
927
- .star-7 { color: #007FFF; } /* Azure */
928
- .star-8 { color: #0000FF; } /* Blue */
929
- .star-9 { color: #7F00FF; } /* Violet */
930
- .star-10 { color: #FF00FF; } /* Magenta */
931
- .star-empty { color: #cccccc; } /* Gray for empty stars */
932
- </style>
933
- </head>
934
- <body>
935
- <div class="min-h-screen" x-data="app()">
936
- <!-- Header -->
937
- <header class="gradient-bg py-12 px-4 relative overflow-hidden">
938
- <div class="orb orb-1"></div>
939
- <div class="orb orb-2"></div>
940
- <div class="container relative z-10">
941
- <div class="text-center">
942
- <div class="inline-block px-4 py-2 rounded-full bg-white/10 backdrop-blur-lg border border-white/20 mb-6">
943
- <span class="inline-block w-2 h-2 rounded-full bg-green-400 mr-2"></span>
944
- <span class="text-white text-sm font-medium">AI-Powered Interview Prep</span>
945
- </div>
946
- <h1 class="text-4xl md:text-5xl font-bold text-white mb-4">Husky Interview Prep</h1>
947
- <p class="text-xl text-white/80 max-w-2xl mx-auto">Master your interview with confidence. AI-powered preparation for job seekers.</p>
948
- </div>
949
- </div>
950
- </header>
951
-
952
- <!-- Main Content -->
953
- <main class="container px-4 py-8">
954
- <!-- Step 1: Enter Information -->
955
- <section class="bg-white rounded-xl shadow-md p-6 mb-8">
956
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
957
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">1</span>
958
- Enter Your Information
959
- </h2>
960
- <div class="flex flex-col gap-4 max-w-3xl mx-auto">
961
- <div>
962
- <label class="block text-sm font-medium text-gray-700 mb-2">Job Description</label>
963
- <textarea x-model="jobDesc" class="w-full h-32 px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Paste the job description here..."></textarea>
964
- </div>
965
- <div>
966
- <label class="block text-sm font-medium text-gray-700 mb-2">Company Information</label>
967
- <textarea x-model="companyInfo" class="w-full h-32 px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Enter information about the company..."></textarea>
968
- </div>
969
- <div>
970
- <label class="block text-sm font-medium text-gray-700 mb-2">Your Resume</label>
971
- <textarea x-model="resume" class="w-full h-32 px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Paste your resume or relevant experience here..."></textarea>
972
- </div>
973
- </div>
974
-
975
- <div class="mt-6 flex justify-center">
976
- <button @click="analyzeInfo()" :disabled="isAnalyzing" class="btn-primary px-6 py-3 rounded-lg font-medium flex items-center">
977
- <span class="spinner" x-show="isAnalyzing"></span>
978
- <i class="fas fa-search mr-2" x-show="!isAnalyzing"></i>
979
- <span x-text="isAnalyzing ? 'Analyzing...' : 'Analyze Information'"></span>
980
- </button>
981
- </div>
982
-
983
- <template x-if="parsedInfo.company_values">
984
- <div class="mt-8">
985
- <div class="grid md:grid-cols-2 gap-6 mb-8">
986
- <div class="bg-gray-50 p-4 rounded-lg">
987
- <h3 class="font-semibold text-gray-800 mb-2">Company Values</h3>
988
- <ul class="list-disc pl-5 space-y-1 text-gray-700">
989
- <template x-for="(line, index) in parsedInfo.company_values.split('- ').filter(item => item.trim().length > 0)" :key="index">
990
- <li x-text="line.trim()"></li>
991
- </template>
992
- </ul>
993
- </div>
994
- <div class="bg-gray-50 p-4 rounded-lg">
995
- <h3 class="font-semibold text-gray-800 mb-2">Job Duties</h3>
996
- <ul class="list-disc pl-5 space-y-1 text-gray-700">
997
- <template x-for="(line, index) in parsedInfo.job_duties.split('- ').filter(item => item.trim().length > 0)" :key="index">
998
- <li x-text="line.trim()"></li>
999
- </template>
1000
- </ul>
1001
- </div>
1002
- <div class="bg-gray-50 p-4 rounded-lg">
1003
- <h3 class="font-semibold text-gray-800 mb-2">Tech Skills</h3>
1004
- <ul class="list-disc pl-5 space-y-1 text-gray-700">
1005
- <template x-for="(line, index) in parsedInfo.tech_skills.split('- ').filter(item => item.trim().length > 0)" :key="index">
1006
- <li x-text="line.trim()"></li>
1007
- </template>
1008
- </ul>
1009
- </div>
1010
- <div class="bg-gray-50 p-4 rounded-lg">
1011
- <h3 class="font-semibold text-gray-800 mb-2">Soft Skills</h3>
1012
- <ul class="list-disc pl-5 space-y-1 text-gray-700">
1013
- <template x-for="(line, index) in parsedInfo.soft_skills.split('- ').filter(item => item.trim().length > 0)" :key="index">
1014
- <li x-text="line.trim()"></li>
1015
- </template>
1016
- </ul>
1017
- </div>
1018
- </div>
1019
-
1020
- <div class="flex justify-center">
1021
- <button @click="generateQuestions()" :disabled="isGeneratingQuestions" class="btn-primary px-6 py-3 rounded-lg font-medium flex items-center">
1022
- <span class="spinner" x-show="isGeneratingQuestions"></span>
1023
- <i class="fas fa-list-ul mr-2" x-show="!isGeneratingQuestions"></i>
1024
- <span x-text="isGeneratingQuestions ? 'Generating...' : 'Generate Interview Questions'"></span>
1025
- </button>
1026
- </div>
1027
- </div>
1028
- </template>
1029
- </section>
1030
-
1031
- <!-- Step 2: Practice Questions -->
1032
- <section x-show="questionsGenerated" class="bg-white rounded-xl shadow-md p-6 mb-8">
1033
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1034
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">2</span>
1035
- Practice Questions
1036
- </h2>
1037
-
1038
- <!-- Question Category Tabs -->
1039
- <div class="mb-6">
1040
- <div class="flex flex-wrap space-x-2 border-b border-gray-200">
1041
- <template x-for="(category, index) in Object.keys(questions)" :key="index">
1042
- <button
1043
- @click="activeCategory = category"
1044
- :class="{'tab-active': activeCategory === category}"
1045
- class="px-4 py-2 rounded-t-lg font-medium transition-all duration-200"
1046
- x-text="category"
1047
- ></button>
1048
- </template>
1049
- </div>
1050
- </div>
1051
-
1052
- <!-- Questions for Selected Category -->
1053
- <div>
1054
- <template x-for="(categoryQuestions, category) in questions" :key="category">
1055
- <div x-show="activeCategory === category">
1056
- <template x-for="(question, qIndex) in categoryQuestions" :key="qIndex">
1057
- <div
1058
- @click="selectQuestion(question)"
1059
- class="question-card p-4 rounded-lg border border-gray-200 bg-gray-50 mb-3 cursor-pointer hover:bg-indigo-50 hover:border-indigo-200"
1060
- :class="{'border-indigo-500 bg-indigo-50': selectedQuestion === question}"
1061
- >
1062
- <p class="text-gray-800" x-text="question"></p>
1063
- </div>
1064
- </template>
1065
- </div>
1066
- </template>
1067
- </div>
1068
- </section>
1069
-
1070
- <!-- Step 3: Record Answer -->
1071
- <section x-show="selectedQuestion" class="bg-white rounded-xl shadow-md p-6 mb-8">
1072
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1073
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">3</span>
1074
- Record Your Answer
1075
- </h2>
1076
-
1077
- <div class="grid md:grid-cols-3 gap-6">
1078
- <!-- Interviewer Column -->
1079
- <div class="md:col-span-1">
1080
- <div class="text-center">
1081
- <div class="rounded-lg overflow-hidden mb-4 mx-auto w-48 h-48 flex items-center justify-center bg-gray-100">
1082
- <img src="/static/interviewer.png" alt="Interviewer" class="max-w-full max-h-full">
1083
- </div>
1084
-
1085
- <div class="mb-4">
1086
- <label class="block text-sm font-medium text-gray-700 mb-2">Voice Accent</label>
1087
- <select x-model="voiceOption" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
1088
- <option value="US English">US English</option>
1089
- <option value="UK English">UK English</option>
1090
- <option value="Australian English">Australian English</option>
1091
- <option value="Indian English">Indian English</option>
1092
- <option value="French">French</option>
1093
- <option value="German">German</option>
1094
- <option value="Spanish">Spanish</option>
1095
- <option value="Italian">Italian</option>
1096
- <option value="Japanese">Japanese</option>
1097
- <option value="Korean">Korean</option>
1098
- </select>
1099
- </div>
1100
-
1101
- <button @click="readQuestionAloud()" :disabled="isReadingAloud" class="btn-secondary w-full px-4 py-2 rounded-lg font-medium flex items-center justify-center">
1102
- <span class="spinner" x-show="isReadingAloud"></span>
1103
- <i class="fas fa-volume-up mr-2" x-show="!isReadingAloud"></i>
1104
- <span x-text="isReadingAloud ? 'Reading...' : 'Read Question Aloud'"></span>
1105
- </button>
1106
-
1107
- <div x-show="audioPlaying" class="mt-4">
1108
- <audio x-ref="audioPlayer" controls class="w-full">
1109
- Your browser does not support the audio element.
1110
- </audio>
1111
- </div>
1112
- </div>
1113
- </div>
1114
-
1115
- <!-- Question and Recording Column -->
1116
- <div class="md:col-span-2">
1117
- <div class="mb-6">
1118
- <label class="block text-sm font-medium text-gray-700 mb-2">Current Question</label>
1119
- <div class="p-4 bg-indigo-50 rounded-lg border border-indigo-100">
1120
- <p class="text-gray-800 font-medium" x-text="selectedQuestion"></p>
1121
- </div>
1122
-
1123
- <div class="mt-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
1124
- <h3 class="font-medium text-gray-800 mb-2">How to Answer</h3>
1125
- <p class="text-gray-700" x-text="questionHints[selectedQuestion] || 'No specific hint available for this question.'"></p>
1126
- </div>
1127
- </div>
1128
-
1129
- <div class="mb-6">
1130
- <label class="block text-sm font-medium text-gray-700 mb-2">Record Your Answer</label>
1131
- <div class="flex flex-col items-center">
1132
- <button @click="toggleRecording()" class="mb-4 relative">
1133
- <div :class="{'recording-pulse': isRecording}" class="w-16 h-16 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center">
1134
- <i :class="isRecording ? 'fa-stop text-red-500' : 'fa-microphone text-gray-700'" class="fas text-2xl"></i>
1135
- </div>
1136
- </button>
1137
- <p x-text="recordingStatus" class="text-sm font-medium text-gray-600"></p>
1138
- </div>
1139
- </div>
1140
-
1141
- <div>
1142
- <label class="block text-sm font-medium text-gray-700 mb-2">Your Answer (Transcribed)</label>
1143
- <textarea
1144
- x-model="answerText"
1145
- class="w-full h-40 px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
1146
- placeholder="Your transcribed answer will appear here..."
1147
- ></textarea>
1148
- </div>
1149
-
1150
- <div class="mt-4">
1151
- <button @click="analyzeAnswer()" :disabled="isAnalyzingAnswer" class="btn-primary px-6 py-3 rounded-lg font-medium flex items-center">
1152
- <span class="spinner" x-show="isAnalyzingAnswer"></span>
1153
- <i class="fas fa-chart-line mr-2" x-show="!isAnalyzingAnswer"></i>
1154
- <span x-text="isAnalyzingAnswer ? 'Analyzing...' : 'Analyze Answer'"></span>
1155
- </button>
1156
- </div>
1157
- </div>
1158
- </div>
1159
- </section>
1160
-
1161
- <!-- Step 4: Review Analysis -->
1162
- <section x-show="feedbackText" class="bg-white rounded-xl shadow-md p-6 mb-8">
1163
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1164
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">4</span>
1165
- Review Analysis
1166
- </h2>
1167
-
1168
- <div x-show="scores" class="grid md:grid-cols-3 gap-4 mb-6">
1169
- <div class="bg-gray-50 p-4 rounded-lg">
1170
- <h3 class="font-semibold text-gray-800 mb-2">Clarity</h3>
1171
- <div class="text-2xl">
1172
- <template x-for="i in 10" :key="i">
1173
- <span :class="i <= scores.clarity ? `star-${i}` : 'star-empty'"
1174
- x-text="i <= scores.clarity ? '★' : '☆'"></span>
1175
- </template>
1176
- </div>
1177
- <p class="text-sm text-gray-600 mt-1">Score: <span x-text="scores.clarity"></span>/10</p>
1178
- </div>
1179
- <div class="bg-gray-50 p-4 rounded-lg">
1180
- <h3 class="font-semibold text-gray-800 mb-2">Relevance</h3>
1181
- <div class="text-2xl">
1182
- <template x-for="i in 10" :key="i">
1183
- <span :class="i <= scores.relevance ? `star-${i}` : 'star-empty'"
1184
- x-text="i <= scores.relevance ? '★' : '☆'"></span>
1185
- </template>
1186
- </div>
1187
- <p class="text-sm text-gray-600 mt-1">Score: <span x-text="scores.relevance"></span>/10</p>
1188
- </div>
1189
- <div class="bg-gray-50 p-4 rounded-lg">
1190
- <h3 class="font-semibold text-gray-800 mb-2">Confidence</h3>
1191
- <div class="text-2xl">
1192
- <template x-for="i in 10" :key="i">
1193
- <span :class="i <= scores.confidence ? `star-${i}` : 'star-empty'"
1194
- x-text="i <= scores.confidence ? '★' : '☆'"></span>
1195
- </template>
1196
- </div>
1197
- <p class="text-sm text-gray-600 mt-1">Score: <span x-text="scores.confidence"></span>/10</p>
1198
- </div>
1199
- </div>
1200
-
1201
- <div class="bg-gray-50 p-6 rounded-lg">
1202
- <h3 class="font-semibold text-gray-800 mb-3">Detailed Feedback</h3>
1203
- <div class="prose max-w-none text-gray-700 whitespace-pre-line leading-relaxed"
1204
- x-text="feedbackText"></div>
1205
- </div>
1206
- </section>
1207
-
1208
- <!-- Step 5: Model Answer -->
1209
- <section x-show="feedbackText" class="bg-white rounded-xl shadow-md p-6 mb-8">
1210
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1211
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">5</span>
1212
- Get Model Answer
1213
- </h2>
1214
-
1215
- <button @click="generateModelAnswer()" :disabled="isGeneratingModel" class="btn-primary px-6 py-3 rounded-lg font-medium flex items-center mb-6">
1216
- <span class="spinner" x-show="isGeneratingModel"></span>
1217
- <i class="fas fa-magic mr-2" x-show="!isGeneratingModel"></i>
1218
- <span x-text="isGeneratingModel ? 'Generating...' : 'Generate Model Answer'"></span>
1219
- </button>
1220
-
1221
- <template x-if="modelAnswer">
1222
- <div class="bg-gray-50 p-6 rounded-lg">
1223
- <h3 class="font-semibold text-gray-800 mb-3">Sample Professional Answer</h3>
1224
- <div class="prose max-w-none text-gray-700 whitespace-pre-line leading-relaxed"
1225
- x-text="modelAnswer"></div>
1226
- </div>
1227
- </template>
1228
- </section>
1229
-
1230
- <!-- Step 6: Save Work -->
1231
- <section x-show="feedbackText && modelAnswer" class="bg-white rounded-xl shadow-md p-6 mb-8">
1232
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1233
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">6</span>
1234
- Save Your Work
1235
- </h2>
1236
-
1237
- <button @click="saveToHTML()" :disabled="isSaving" class="btn-primary px-6 py-3 rounded-lg font-medium flex items-center">
1238
- <span class="spinner" x-show="isSaving"></span>
1239
- <i class="fas fa-download mr-2" x-show="!isSaving"></i>
1240
- <span x-text="isSaving ? 'Saving...' : 'Save as HTML'"></span>
1241
- </button>
1242
-
1243
- <template x-if="downloadLink">
1244
- <div class="mt-6 p-4 bg-green-50 border border-green-200 rounded-lg">
1245
- <p class="text-green-800 mb-3">Your file is ready to download!</p>
1246
- <a :href="downloadLink" class="btn-primary px-6 py-3 rounded-lg font-medium inline-flex items-center" download>
1247
- <i class="fas fa-cloud-download-alt mr-2"></i> Download File
1248
- </a>
1249
- </div>
1250
- </template>
1251
- </section>
1252
-
1253
- <!-- Step 7: Continue or Start New -->
1254
- <section x-show="feedbackText && modelAnswer" class="bg-white rounded-xl shadow-md p-6 mb-8">
1255
- <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1256
- <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 mr-3">7</span>
1257
- Continue Your Practice
1258
- </h2>
1259
-
1260
- <div class="grid md:grid-cols-2 gap-6">
1261
- <div class="bg-gray-50 p-6 rounded-lg text-center">
1262
- <h3 class="font-semibold text-gray-800 mb-4">Practice Another Question</h3>
1263
- <p class="text-gray-600 mb-4">Continue practicing with the same job information and try another interview question.</p>
1264
- <button @click="practiceAnotherQuestion()" class="btn-primary px-6 py-3 rounded-lg font-medium">
1265
- <i class="fas fa-redo mr-2"></i> Try Another Question
1266
- </button>
1267
- </div>
1268
-
1269
- <div class="bg-gray-50 p-6 rounded-lg text-center">
1270
- <h3 class="font-semibold text-gray-800 mb-4">Start Fresh</h3>
1271
- <p class="text-gray-600 mb-4">Clear all data and start over with a different company or position.</p>
1272
- <button @click="startOver()" class="btn-secondary px-6 py-3 rounded-lg font-medium">
1273
- <i class="fas fa-sync mr-2"></i> Start New Practice
1274
- </button>
1275
- </div>
1276
- </div>
1277
- </section>
1278
- </main>
1279
-
1280
- <!-- Footer -->
1281
- <footer class="bg-gray-800 py-8 px-4 text-center">
1282
- <div class="container">
1283
- <p class="text-gray-300">Husky Interview Prep © 2023. All rights reserved.</p>
1284
- <p class="text-gray-400 text-sm mt-2">Powered by AI and created to help you succeed.</p>
1285
- </div>
1286
- </footer>
1287
- </div>
1288
-
1289
- <script>
1290
- function app() {
1291
- return {
1292
- jobDesc: '',
1293
- companyInfo: '',
1294
- resume: '',
1295
- parsedInfo: {},
1296
-
1297
- // Questions
1298
- questions: {},
1299
- questionHints: {},
1300
- activeCategory: 'Introduction',
1301
- selectedQuestion: '',
1302
- questionsGenerated: false,
1303
-
1304
- // Loading states
1305
- isAnalyzing: false,
1306
- isGeneratingQuestions: false,
1307
- isReadingAloud: false,
1308
- isTranscribing: false,
1309
- isAnalyzingAnswer: false,
1310
- isGeneratingModel: false,
1311
- isSaving: false,
1312
-
1313
- // Recording
1314
- isRecording: false,
1315
- recorder: null,
1316
- audioChunks: [],
1317
- recordingStatus: 'Click to start recording',
1318
- answerText: '',
1319
-
1320
- // Analysis
1321
- scores: null,
1322
- feedbackText: '',
1323
- modelAnswer: '',
1324
-
1325
- // Audio playback
1326
- voiceOption: 'US English',
1327
- audioSrc: '',
1328
- audioPlaying: false,
1329
-
1330
- // Download
1331
- downloadLink: '',
1332
-
1333
- async analyzeInfo() {
1334
- if (!this.jobDesc || !this.companyInfo) {
1335
- alert('Please enter job description and company information.');
1336
- return;
1337
- }
1338
-
1339
- this.isAnalyzing = true; // Start loading indicator
1340
-
1341
- try {
1342
- const response = await fetch('/analyze-info', {
1343
- method: 'POST',
1344
- headers: {
1345
- 'Content-Type': 'application/json',
1346
- },
1347
- body: JSON.stringify({
1348
- job_desc: this.jobDesc,
1349
- company_info: this.companyInfo
1350
- }),
1351
- });
1352
-
1353
- this.parsedInfo = await response.json();
1354
- } catch (error) {
1355
- console.error('Error analyzing information:', error);
1356
- alert('Error analyzing information. Please try again.');
1357
- } finally {
1358
- this.isAnalyzing = false; // Stop loading indicator
1359
- }
1360
- },
1361
-
1362
- async generateQuestions() {
1363
- if (!this.jobDesc) {
1364
- alert('Please enter at least the job description.');
1365
- return;
1366
- }
1367
-
1368
- this.isGeneratingQuestions = true; // Start loading indicator
1369
-
1370
- try {
1371
- const response = await fetch('/generate-questions', {
1372
- method: 'POST',
1373
- headers: {
1374
- 'Content-Type': 'application/json',
1375
- },
1376
- body: JSON.stringify({
1377
- job_desc: this.jobDesc,
1378
- company_info: this.companyInfo,
1379
- resume: this.resume
1380
- }),
1381
- });
1382
-
1383
- const data = await response.json();
1384
- this.questions = data.questions;
1385
- this.questionHints = data.hints;
1386
- this.questionsGenerated = true;
1387
-
1388
- // Set default active category to the first one
1389
- if (Object.keys(this.questions).length > 0) {
1390
- this.activeCategory = Object.keys(this.questions)[0];
1391
- }
1392
- } catch (error) {
1393
- console.error('Error generating questions:', error);
1394
- alert('Error generating questions. Please try again.');
1395
- } finally {
1396
- this.isGeneratingQuestions = false; // Stop loading indicator
1397
- }
1398
- },
1399
-
1400
- selectQuestion(question) {
1401
- this.selectedQuestion = question;
1402
- // Reset related data
1403
- this.answerText = '';
1404
- this.scores = null;
1405
- this.feedbackText = '';
1406
- this.modelAnswer = '';
1407
- this.audioSrc = '';
1408
- this.audioPlaying = false;
1409
- },
1410
-
1411
- async readQuestionAloud() {
1412
- if (!this.selectedQuestion) return;
1413
-
1414
- this.isReadingAloud = true; // Start loading indicator
1415
-
1416
- try {
1417
- const response = await fetch('/text-to-speech', {
1418
- method: 'POST',
1419
- headers: {
1420
- 'Content-Type': 'application/json',
1421
- },
1422
- body: JSON.stringify({
1423
- text: this.selectedQuestion,
1424
- voice_option: this.voiceOption
1425
- }),
1426
- });
1427
-
1428
- const data = await response.json();
1429
- if (data.audio) {
1430
- this.audioSrc = data.audio;
1431
- this.audioPlaying = true;
1432
-
1433
- // Give DOM time to update before accessing the audio element
1434
- setTimeout(() => {
1435
- const audio = this.$refs.audioPlayer;
1436
- if (audio) {
1437
- audio.src = data.audio;
1438
- audio.load();
1439
- audio.play().catch(e => console.error('Error playing audio:', e));
1440
- }
1441
- }, 100);
1442
- }
1443
- } catch (error) {
1444
- console.error('Error reading question aloud:', error);
1445
- alert('Error reading question aloud. Please try again.');
1446
- } finally {
1447
- this.isReadingAloud = false; // Stop loading indicator
1448
- }
1449
- },
1450
-
1451
- toggleRecording() {
1452
- if (this.isRecording) {
1453
- this.stopRecording();
1454
- } else {
1455
- this.startRecording();
1456
- }
1457
- },
1458
-
1459
- async startRecording() {
1460
- try {
1461
- // Set audio constraints for better compatibility
1462
- const stream = await navigator.mediaDevices.getUserMedia({
1463
- audio: {
1464
- channelCount: 1,
1465
- sampleRate: 16000,
1466
- sampleSize: 16,
1467
- echoCancellation: true,
1468
- noiseSuppression: true
1469
- }
1470
- });
1471
-
1472
- this.recorder = new MediaRecorder(stream, {
1473
- mimeType: 'audio/webm' // More widely supported in browsers
1474
- });
1475
- this.audioChunks = [];
1476
-
1477
- this.recorder.ondataavailable = (e) => {
1478
- if (e.data.size > 0) {
1479
- this.audioChunks.push(e.data);
1480
- }
1481
- };
1482
-
1483
- this.recorder.onstop = async () => {
1484
- const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
1485
- const reader = new FileReader();
1486
- reader.readAsDataURL(audioBlob);
1487
-
1488
- reader.onload = async () => {
1489
- const base64Audio = reader.result;
1490
-
1491
- try {
1492
- const response = await fetch('/speech-to-text', {
1493
- method: 'POST',
1494
- headers: {
1495
- 'Content-Type': 'application/json',
1496
- },
1497
- body: JSON.stringify({ audio: base64Audio }),
1498
- });
1499
-
1500
- const data = await response.json();
1501
- this.answerText = data.text;
1502
- this.recordingStatus = 'Recording transcribed';
1503
- } catch (error) {
1504
- console.error('Error transcribing audio:', error);
1505
- this.recordingStatus = 'Error transcribing audio';
1506
- } finally {
1507
- this.isTranscribing = false; // Stop transcribing indicator
1508
- }
1509
- };
1510
- };
1511
-
1512
- this.recorder.start();
1513
- this.isRecording = true;
1514
- this.recordingStatus = 'Recording... Click to stop';
1515
- } catch (error) {
1516
- console.error('Error starting recording:', error);
1517
- this.recordingStatus = 'Error accessing microphone';
1518
- }
1519
- },
1520
-
1521
- stopRecording() {
1522
- if (this.recorder && this.recorder.state !== 'inactive') {
1523
- this.recorder.stop();
1524
- this.isRecording = false;
1525
- this.isTranscribing = true; // Start transcribing indicator
1526
- this.recordingStatus = 'Processing...';
1527
-
1528
- // Stop all audio tracks
1529
- this.recorder.stream.getTracks().forEach(track => track.stop());
1530
- }
1531
- },
1532
-
1533
- async analyzeAnswer() {
1534
- if (!this.answerText) {
1535
- alert('Please record or enter an answer to analyze.');
1536
- return;
1537
- }
1538
-
1539
- this.isAnalyzingAnswer = true; // Start loading indicator
1540
-
1541
- try {
1542
- const response = await fetch('/analyze-answer', {
1543
- method: 'POST',
1544
- headers: {
1545
- 'Content-Type': 'application/json',
1546
- },
1547
- body: JSON.stringify({
1548
- answer_text: this.answerText,
1549
- job_desc: this.jobDesc,
1550
- company_values: this.parsedInfo.company_values
1551
- }),
1552
- });
1553
-
1554
- const data = await response.json();
1555
- // Ensure scores are properly initialized with numeric values
1556
- this.scores = {
1557
- clarity: parseInt(data.scores.clarity) || 0,
1558
- relevance: parseInt(data.scores.relevance) || 0,
1559
- confidence: parseInt(data.scores.confidence) || 0
1560
- };
1561
- this.feedbackText = data.feedback;
1562
- } catch (error) {
1563
- console.error('Error analyzing answer:', error);
1564
- alert('Error analyzing answer. Please try again.');
1565
- } finally {
1566
- this.isAnalyzingAnswer = false; // Stop loading indicator
1567
- }
1568
- },
1569
-
1570
- async generateModelAnswer() {
1571
- if (!this.selectedQuestion) {
1572
- alert('Please select a question first.');
1573
- return;
1574
- }
1575
-
1576
- this.isGeneratingModel = true; // Start loading indicator
1577
-
1578
- try {
1579
- const response = await fetch('/generate-model-answer', {
1580
- method: 'POST',
1581
- headers: {
1582
- 'Content-Type': 'application/json',
1583
- },
1584
- body: JSON.stringify({
1585
- question: this.selectedQuestion,
1586
- company_info: this.companyInfo,
1587
- job_desc: this.jobDesc,
1588
- resume: this.resume,
1589
- answer_text: this.answerText
1590
- }),
1591
- });
1592
-
1593
- const data = await response.json();
1594
- this.modelAnswer = data.model_answer;
1595
- } catch (error) {
1596
- console.error('Error generating model answer:', error);
1597
- alert('Error generating model answer. Please try again.');
1598
- } finally {
1599
- this.isGeneratingModel = false; // Stop loading indicator
1600
- }
1601
- },
1602
-
1603
- async saveToHTML() {
1604
- this.isSaving = true; // Start loading indicator
1605
-
1606
- try {
1607
- const response = await fetch('/save-to-html', {
1608
- method: 'POST',
1609
- headers: {
1610
- 'Content-Type': 'application/json',
1611
- },
1612
- body: JSON.stringify({
1613
- job_desc: this.jobDesc,
1614
- company_info: this.companyInfo,
1615
- resume: this.resume,
1616
- company_values: this.parsedInfo.company_values,
1617
- tech_skills: this.parsedInfo.tech_skills,
1618
- soft_skills: this.parsedInfo.soft_skills,
1619
- job_duties: this.parsedInfo.job_duties,
1620
- selected_question: this.selectedQuestion,
1621
- answer_text: this.answerText,
1622
- feedback: this.feedbackText,
1623
- model_answer: this.modelAnswer
1624
- }),
1625
- });
1626
-
1627
- const data = await response.json();
1628
- this.downloadLink = `/download-html/${data.file_id}`;
1629
- } catch (error) {
1630
- console.error('Error saving to HTML:', error);
1631
- alert('Error saving to HTML. Please try again.');
1632
- } finally {
1633
- this.isSaving = false; // Stop loading indicator
1634
- }
1635
- },
1636
-
1637
- practiceAnotherQuestion() {
1638
- // Reset answer-related data but keep job info
1639
- this.selectedQuestion = '';
1640
- this.answerText = '';
1641
- this.scores = null;
1642
- this.feedbackText = '';
1643
- this.modelAnswer = '';
1644
- this.audioSrc = '';
1645
- this.audioPlaying = false;
1646
- this.downloadLink = '';
1647
-
1648
- // Scroll to the questions section
1649
- document.querySelector('section:nth-of-type(2)').scrollIntoView({ behavior: 'smooth' });
1650
- },
1651
-
1652
- startOver() {
1653
- // Reset everything
1654
- this.jobDesc = '';
1655
- this.companyInfo = '';
1656
- this.resume = '';
1657
- this.parsedInfo = {};
1658
- this.questions = {};
1659
- this.questionHints = {};
1660
- this.activeCategory = 'Introduction';
1661
- this.selectedQuestion = '';
1662
- this.questionsGenerated = false;
1663
- this.answerText = '';
1664
- this.scores = null;
1665
- this.feedbackText = '';
1666
- this.modelAnswer = '';
1667
- this.audioSrc = '';
1668
- this.audioPlaying = false;
1669
- this.downloadLink = '';
1670
-
1671
- // Scroll to top
1672
- window.scrollTo({ top: 0, behavior: 'smooth' });
1673
- }
1674
- };
1675
- }
1676
- </script>
1677
- </body>
1678
- </html>
1679
- ''')
1680
-
1681
- app.run(debug=True, host='0.0.0.0', port=5002)
 
782
  import shutil
783
  shutil.copy('interviewer.png', 'static/interviewer.png')
784
 
785
+ # Use environment variables for host and port, with defaults for local development
786
+ port = int(os.environ.get("PORT", 7860))
787
+ app.run(host="0.0.0.0", port=port, debug=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ flask==2.2.3
2
+ numpy==1.24.3
3
+ SpeechRecognition==3.10.0
4
+ sentence-transformers==2.2.2
5
+ scikit-learn==1.2.2
6
+ requests==2.31.0
7
+ together==0.1.5
8
+ python-dotenv==1.0.0
9
+ gtts==2.3.2