Spaces:
Configuration error
Configuration error
Upload 18 files
Browse files- ClearPassAIApp.java +10 -0
- ClearPassAIGUI.java +113 -0
- DataModel.java +18 -0
- ESpeakTTSClient.java +77 -0
- GeminiClient.java +64 -0
- GoogleTTSClient.java +83 -0
- InterviewScreen.java +211 -0
- LoginPage.java +145 -0
- MozillaTTSClient.java +63 -0
- QuestionData.java +35 -0
- README.md +76 -11
- ResultScreen.java +63 -0
- TextToSpeechClient.java +65 -0
- WelcomeScreen.java +37 -0
- json-20210307.jar +0 -0
- json-20231013.jar +0 -0
- json-20250107.jar +0 -0
- users.txt +6 -0
ClearPassAIApp.java
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
|
| 3 |
+
public class ClearPassAIApp {
|
| 4 |
+
public static void main(String[] args) {
|
| 5 |
+
SwingUtilities.invokeLater(() -> {
|
| 6 |
+
ClearPassAIGUI gui = new ClearPassAIGUI();
|
| 7 |
+
gui.setVisible(true);
|
| 8 |
+
});
|
| 9 |
+
}
|
| 10 |
+
}
|
ClearPassAIGUI.java
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
import java.awt.*;
|
| 3 |
+
import java.io.FileInputStream;
|
| 4 |
+
import java.io.IOException;
|
| 5 |
+
import java.util.Properties;
|
| 6 |
+
|
| 7 |
+
public class ClearPassAIGUI extends JFrame {
|
| 8 |
+
private CardLayout cardLayout;
|
| 9 |
+
private JPanel mainPanel;
|
| 10 |
+
private LoginPage loginPage;
|
| 11 |
+
private WelcomeScreen welcomeScreen;
|
| 12 |
+
private InterviewScreen interviewScreen;
|
| 13 |
+
private ResultScreen resultScreen;
|
| 14 |
+
private String role;
|
| 15 |
+
private String currentUser;
|
| 16 |
+
private int questionCount = 5;
|
| 17 |
+
private GeminiClient geminiClient;
|
| 18 |
+
private ESpeakTTSClient ttsClient;
|
| 19 |
+
private DataModel dataModel;
|
| 20 |
+
|
| 21 |
+
public ClearPassAIGUI() {
|
| 22 |
+
setTitle("ClearPass AI - Interview Coach");
|
| 23 |
+
setSize(900, 700);
|
| 24 |
+
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
| 25 |
+
|
| 26 |
+
Properties props = loadConfig();
|
| 27 |
+
String geminiApiKey = props.getProperty("GEMINI_API_KEY");
|
| 28 |
+
|
| 29 |
+
geminiClient = new GeminiClient(geminiApiKey, "gemini-1.5-flash");
|
| 30 |
+
ttsClient = new ESpeakTTSClient();
|
| 31 |
+
dataModel = new DataModel();
|
| 32 |
+
|
| 33 |
+
cardLayout = new CardLayout();
|
| 34 |
+
mainPanel = new JPanel(cardLayout);
|
| 35 |
+
|
| 36 |
+
loginPage = new LoginPage(this);
|
| 37 |
+
welcomeScreen = new WelcomeScreen(this);
|
| 38 |
+
resultScreen = new ResultScreen(this);
|
| 39 |
+
|
| 40 |
+
mainPanel.add(loginPage, "Login");
|
| 41 |
+
mainPanel.add(welcomeScreen, "Welcome");
|
| 42 |
+
mainPanel.add(resultScreen, "Results");
|
| 43 |
+
|
| 44 |
+
add(mainPanel);
|
| 45 |
+
showScreen("Login");
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
private Properties loadConfig() {
|
| 49 |
+
Properties props = new Properties();
|
| 50 |
+
try (FileInputStream fis = new FileInputStream("config.properties")) {
|
| 51 |
+
props.load(fis);
|
| 52 |
+
} catch (IOException e) {
|
| 53 |
+
JOptionPane.showMessageDialog(this, "Configuration file not found", "Error", JOptionPane.ERROR_MESSAGE);
|
| 54 |
+
}
|
| 55 |
+
return props;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
public void showScreen(String screenName) {
|
| 59 |
+
cardLayout.show(mainPanel, screenName);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
public void startSession(String role, int questionCount) {
|
| 63 |
+
this.role = role;
|
| 64 |
+
this.questionCount = questionCount;
|
| 65 |
+
this.dataModel = new DataModel();
|
| 66 |
+
interviewScreen = new InterviewScreen(this, questionCount);
|
| 67 |
+
JScrollPane scrollPane = new JScrollPane(interviewScreen);
|
| 68 |
+
mainPanel.add(scrollPane, "Interview");
|
| 69 |
+
showScreen("Interview");
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
public void finishSession() {
|
| 73 |
+
resultScreen = new ResultScreen(this);
|
| 74 |
+
mainPanel.add(resultScreen, "Results");
|
| 75 |
+
showScreen("Results");
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
public GeminiClient getGeminiClient() {
|
| 79 |
+
return geminiClient;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
public String getRole() {
|
| 83 |
+
return role;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
public void setRole(String role) {
|
| 87 |
+
this.role = role;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
public DataModel getDataModel() {
|
| 91 |
+
return dataModel;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
public int getQuestionCount() {
|
| 95 |
+
return questionCount;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
public void setQuestionCount(int questionCount) {
|
| 99 |
+
this.questionCount = questionCount;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
public String getCurrentUser() {
|
| 103 |
+
return currentUser;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
public void setCurrentUser(String currentUser) {
|
| 107 |
+
this.currentUser = currentUser;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
public ESpeakTTSClient getTTSClient() {
|
| 111 |
+
return ttsClient;
|
| 112 |
+
}
|
| 113 |
+
}
|
DataModel.java
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.util.ArrayList;
|
| 2 |
+
import java.util.List;
|
| 3 |
+
|
| 4 |
+
public class DataModel {
|
| 5 |
+
private List<QuestionData> history;
|
| 6 |
+
|
| 7 |
+
public DataModel() {
|
| 8 |
+
history = new ArrayList<>();
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
public void setHistory(QuestionData qd) {
|
| 12 |
+
history.add(qd);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
public List<QuestionData> getHistory() {
|
| 16 |
+
return history;
|
| 17 |
+
}
|
| 18 |
+
}
|
ESpeakTTSClient.java
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.io.*;
|
| 2 |
+
|
| 3 |
+
public class ESpeakTTSClient {
|
| 4 |
+
private Process currentProcess;
|
| 5 |
+
|
| 6 |
+
public ESpeakTTSClient() {
|
| 7 |
+
// Check if espeak is installed
|
| 8 |
+
try {
|
| 9 |
+
Process process = Runtime.getRuntime().exec("espeak --version");
|
| 10 |
+
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
| 11 |
+
String version = reader.readLine();
|
| 12 |
+
System.out.println("eSpeak version: " + version);
|
| 13 |
+
|
| 14 |
+
int exitCode = process.waitFor();
|
| 15 |
+
if (exitCode != 0) {
|
| 16 |
+
throw new RuntimeException("eSpeak is not installed. Please install it first.");
|
| 17 |
+
}
|
| 18 |
+
} catch (IOException | InterruptedException e) {
|
| 19 |
+
throw new RuntimeException("Error checking eSpeak installation: " + e.getMessage());
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
public void speakText(String text) throws IOException {
|
| 24 |
+
try {
|
| 25 |
+
// Stop any existing speech
|
| 26 |
+
stopSpeaking();
|
| 27 |
+
|
| 28 |
+
System.out.println("Attempting to speak text: " + text);
|
| 29 |
+
|
| 30 |
+
// Use espeak to generate speech directly
|
| 31 |
+
ProcessBuilder pb = new ProcessBuilder(
|
| 32 |
+
"espeak",
|
| 33 |
+
"-v", "en-gb", // British English voice
|
| 34 |
+
"-s", "120", // Slower speed for clarity
|
| 35 |
+
"-p", "50", // Medium pitch
|
| 36 |
+
"-a", "100", // Full amplitude
|
| 37 |
+
"-g", "5", // Word gap
|
| 38 |
+
"-k", "5", // Emphasis
|
| 39 |
+
text // Text to speak
|
| 40 |
+
);
|
| 41 |
+
|
| 42 |
+
System.out.println("Starting eSpeak process...");
|
| 43 |
+
currentProcess = pb.start();
|
| 44 |
+
|
| 45 |
+
// Capture any error output
|
| 46 |
+
BufferedReader errorReader = new BufferedReader(new InputStreamReader(currentProcess.getErrorStream()));
|
| 47 |
+
String errorLine;
|
| 48 |
+
while ((errorLine = errorReader.readLine()) != null) {
|
| 49 |
+
System.err.println("eSpeak error: " + errorLine);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
int exitCode = currentProcess.waitFor();
|
| 53 |
+
System.out.println("eSpeak process completed with exit code: " + exitCode);
|
| 54 |
+
|
| 55 |
+
if (exitCode != 0) {
|
| 56 |
+
System.err.println("Error generating speech with exit code: " + exitCode);
|
| 57 |
+
throw new IOException("Error generating speech: " + exitCode);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
} catch (InterruptedException e) {
|
| 61 |
+
System.err.println("Speech generation interrupted: " + e.getMessage());
|
| 62 |
+
throw new IOException("Speech generation interrupted: " + e.getMessage());
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
public void stopSpeaking() {
|
| 67 |
+
if (currentProcess != null && currentProcess.isAlive()) {
|
| 68 |
+
currentProcess.destroy();
|
| 69 |
+
try {
|
| 70 |
+
currentProcess.waitFor();
|
| 71 |
+
} catch (InterruptedException e) {
|
| 72 |
+
System.err.println("Error stopping speech: " + e.getMessage());
|
| 73 |
+
}
|
| 74 |
+
currentProcess = null;
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
GeminiClient.java
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.io.*;
|
| 2 |
+
import java.net.HttpURLConnection;
|
| 3 |
+
import java.net.URL;
|
| 4 |
+
import org.json.JSONArray;
|
| 5 |
+
import org.json.JSONObject;
|
| 6 |
+
|
| 7 |
+
public class GeminiClient {
|
| 8 |
+
private final String apiKey;
|
| 9 |
+
private final String model;
|
| 10 |
+
|
| 11 |
+
public GeminiClient(String apiKey, String model) {
|
| 12 |
+
this.apiKey = apiKey;
|
| 13 |
+
this.model = model;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
public String generateResponse(String prompt) throws IOException {
|
| 17 |
+
String safePrompt = prompt.replace("\"", "\\\"");
|
| 18 |
+
String inputJson = "{\n" +
|
| 19 |
+
" \"contents\": [\n" +
|
| 20 |
+
" {\n" +
|
| 21 |
+
" \"parts\": [\n" +
|
| 22 |
+
" { \"text\": \"" + safePrompt + "\" }\n" +
|
| 23 |
+
" ]\n" +
|
| 24 |
+
" }\n" +
|
| 25 |
+
" ]\n" +
|
| 26 |
+
"}";
|
| 27 |
+
|
| 28 |
+
URL url = new URL(
|
| 29 |
+
"https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent?key=" + apiKey);
|
| 30 |
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
| 31 |
+
conn.setRequestMethod("POST");
|
| 32 |
+
conn.setRequestProperty("Content-Type", "application/json");
|
| 33 |
+
conn.setDoOutput(true);
|
| 34 |
+
|
| 35 |
+
try (OutputStream os = conn.getOutputStream()) {
|
| 36 |
+
os.write(inputJson.getBytes("utf-8"));
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
int status = conn.getResponseCode();
|
| 40 |
+
InputStream stream = (status == 200) ? conn.getInputStream() : conn.getErrorStream();
|
| 41 |
+
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "utf-8"));
|
| 42 |
+
|
| 43 |
+
StringBuilder response = new StringBuilder();
|
| 44 |
+
String line;
|
| 45 |
+
while ((line = br.readLine()) != null) {
|
| 46 |
+
response.append(line.trim());
|
| 47 |
+
}
|
| 48 |
+
br.close();
|
| 49 |
+
|
| 50 |
+
return extractTextFromResponse(response.toString());
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
private String extractTextFromResponse(String raw) {
|
| 54 |
+
try {
|
| 55 |
+
JSONObject json = new JSONObject(raw);
|
| 56 |
+
JSONArray candidates = json.getJSONArray("candidates");
|
| 57 |
+
JSONObject content = candidates.getJSONObject(0).getJSONObject("content");
|
| 58 |
+
JSONArray parts = content.getJSONArray("parts");
|
| 59 |
+
return parts.getJSONObject(0).getString("text");
|
| 60 |
+
} catch (Exception e) {
|
| 61 |
+
return "Error: Could not parse response.";
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
}
|
GoogleTTSClient.java
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.io.*;
|
| 2 |
+
import java.net.HttpURLConnection;
|
| 3 |
+
import java.net.URL;
|
| 4 |
+
import javax.sound.sampled.*;
|
| 5 |
+
import org.json.JSONObject;
|
| 6 |
+
|
| 7 |
+
public class GoogleTTSClient {
|
| 8 |
+
private final String apiKey;
|
| 9 |
+
private final String voiceName = "en-US-Neural2-F"; // Female voice
|
| 10 |
+
private final String languageCode = "en-US";
|
| 11 |
+
|
| 12 |
+
public GoogleTTSClient(String apiKey) {
|
| 13 |
+
this.apiKey = apiKey;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
public void speakText(String text) throws IOException {
|
| 17 |
+
String inputJson = "{\n" +
|
| 18 |
+
" \"input\": {\n" +
|
| 19 |
+
" \"text\": \"" + text.replace("\"", "\\\"") + "\"\n" +
|
| 20 |
+
" },\n" +
|
| 21 |
+
" \"voice\": {\n" +
|
| 22 |
+
" \"languageCode\": \"" + languageCode + "\",\n" +
|
| 23 |
+
" \"name\": \"" + voiceName + "\"\n" +
|
| 24 |
+
" },\n" +
|
| 25 |
+
" \"audioConfig\": {\n" +
|
| 26 |
+
" \"audioEncoding\": \"LINEAR16\",\n" +
|
| 27 |
+
" \"speakingRate\": 1.0,\n" +
|
| 28 |
+
" \"pitch\": 0.0\n" +
|
| 29 |
+
" }\n" +
|
| 30 |
+
"}";
|
| 31 |
+
|
| 32 |
+
URL url = new URL("https://texttospeech.googleapis.com/v1/text:synthesize?key=" + apiKey);
|
| 33 |
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
| 34 |
+
conn.setRequestMethod("POST");
|
| 35 |
+
conn.setRequestProperty("Content-Type", "application/json");
|
| 36 |
+
conn.setDoOutput(true);
|
| 37 |
+
|
| 38 |
+
try (OutputStream os = conn.getOutputStream()) {
|
| 39 |
+
os.write(inputJson.getBytes("utf-8"));
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
int status = conn.getResponseCode();
|
| 43 |
+
if (status == 200) {
|
| 44 |
+
try (InputStream is = conn.getInputStream()) {
|
| 45 |
+
// Parse the response
|
| 46 |
+
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
| 47 |
+
StringBuilder response = new StringBuilder();
|
| 48 |
+
String line;
|
| 49 |
+
while ((line = br.readLine()) != null) {
|
| 50 |
+
response.append(line);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
JSONObject jsonResponse = new JSONObject(response.toString());
|
| 54 |
+
String audioContent = jsonResponse.getString("audioContent");
|
| 55 |
+
|
| 56 |
+
// Decode base64 audio content
|
| 57 |
+
byte[] audioBytes = java.util.Base64.getDecoder().decode(audioContent);
|
| 58 |
+
|
| 59 |
+
// Save to temporary file
|
| 60 |
+
File tempFile = File.createTempFile("speech", ".wav");
|
| 61 |
+
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
| 62 |
+
fos.write(audioBytes);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Play the audio
|
| 66 |
+
try (AudioInputStream audioIn = AudioSystem.getAudioInputStream(tempFile)) {
|
| 67 |
+
Clip clip = AudioSystem.getClip();
|
| 68 |
+
clip.open(audioIn);
|
| 69 |
+
clip.start();
|
| 70 |
+
clip.drain();
|
| 71 |
+
clip.close();
|
| 72 |
+
} catch (UnsupportedAudioFileException | LineUnavailableException e) {
|
| 73 |
+
throw new IOException("Error playing audio: " + e.getMessage());
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Clean up
|
| 77 |
+
tempFile.delete();
|
| 78 |
+
}
|
| 79 |
+
} else {
|
| 80 |
+
throw new IOException("Error: " + status);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
InterviewScreen.java
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
import java.awt.*;
|
| 3 |
+
import java.awt.event.*;
|
| 4 |
+
import java.io.*;
|
| 5 |
+
|
| 6 |
+
public class InterviewScreen extends JPanel {
|
| 7 |
+
private ClearPassAIGUI controller;
|
| 8 |
+
private JTextArea questionArea;
|
| 9 |
+
private JTextArea answerInput;
|
| 10 |
+
private JTextArea feedbackArea;
|
| 11 |
+
private JButton generateBtn, nextBtn, evaluateBtn, submitBtn, readBtn, stopBtn;
|
| 12 |
+
private JLabel timerLabel, counterLabel, welcomeLabel;
|
| 13 |
+
private Timer timer;
|
| 14 |
+
private int timeLeft = 60;
|
| 15 |
+
private int answeredCount = 0;
|
| 16 |
+
private int totalQuestions;
|
| 17 |
+
|
| 18 |
+
public InterviewScreen(ClearPassAIGUI controller, int questionCount) {
|
| 19 |
+
this.controller = controller;
|
| 20 |
+
this.totalQuestions = questionCount;
|
| 21 |
+
setLayout(new BorderLayout(10, 10));
|
| 22 |
+
|
| 23 |
+
String username = controller.getCurrentUser();
|
| 24 |
+
welcomeLabel = new JLabel("Welcome, " + username + "!");
|
| 25 |
+
welcomeLabel.setFont(new Font("Arial", Font.BOLD, 16));
|
| 26 |
+
welcomeLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
| 27 |
+
|
| 28 |
+
questionArea = createTextArea(false);
|
| 29 |
+
answerInput = createTextArea(true);
|
| 30 |
+
feedbackArea = createTextArea(false);
|
| 31 |
+
disableCopyPaste(answerInput);
|
| 32 |
+
|
| 33 |
+
generateBtn = new JButton("Generate Question");
|
| 34 |
+
evaluateBtn = new JButton("Evaluate Answer");
|
| 35 |
+
nextBtn = new JButton("Next Question");
|
| 36 |
+
readBtn = new JButton("Read Question");
|
| 37 |
+
stopBtn = new JButton("Stop Reading");
|
| 38 |
+
submitBtn = new JButton("Submit Interview");
|
| 39 |
+
timerLabel = new JLabel("Time Left: 60s");
|
| 40 |
+
counterLabel = new JLabel(getCounterText());
|
| 41 |
+
|
| 42 |
+
JPanel buttonPanel = new JPanel(new GridBagLayout());
|
| 43 |
+
GridBagConstraints gbc = new GridBagConstraints();
|
| 44 |
+
gbc.insets = new Insets(5, 10, 5, 10);
|
| 45 |
+
|
| 46 |
+
gbc.gridx = 0;
|
| 47 |
+
buttonPanel.add(generateBtn, gbc);
|
| 48 |
+
gbc.gridx = 1;
|
| 49 |
+
buttonPanel.add(evaluateBtn, gbc);
|
| 50 |
+
gbc.gridx = 2;
|
| 51 |
+
buttonPanel.add(nextBtn, gbc);
|
| 52 |
+
gbc.gridx = 3;
|
| 53 |
+
buttonPanel.add(readBtn, gbc);
|
| 54 |
+
gbc.gridx = 4;
|
| 55 |
+
buttonPanel.add(stopBtn, gbc);
|
| 56 |
+
gbc.gridx = 5;
|
| 57 |
+
buttonPanel.add(timerLabel, gbc);
|
| 58 |
+
gbc.gridx = 6;
|
| 59 |
+
buttonPanel.add(counterLabel, gbc);
|
| 60 |
+
|
| 61 |
+
JPanel topPanel = new JPanel(new BorderLayout());
|
| 62 |
+
topPanel.add(welcomeLabel, BorderLayout.NORTH);
|
| 63 |
+
topPanel.add(buttonPanel, BorderLayout.CENTER);
|
| 64 |
+
|
| 65 |
+
JPanel centerPanel = new JPanel();
|
| 66 |
+
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
|
| 67 |
+
centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
| 68 |
+
centerPanel.add(new JLabel("Generated Question:"));
|
| 69 |
+
centerPanel.add(new JScrollPane(questionArea));
|
| 70 |
+
centerPanel.add(Box.createVerticalStrut(10));
|
| 71 |
+
centerPanel.add(new JLabel("Your Answer:"));
|
| 72 |
+
centerPanel.add(new JScrollPane(answerInput));
|
| 73 |
+
centerPanel.add(Box.createVerticalStrut(10));
|
| 74 |
+
centerPanel.add(new JLabel("AI Feedback:"));
|
| 75 |
+
centerPanel.add(new JScrollPane(feedbackArea));
|
| 76 |
+
|
| 77 |
+
// Create bottom panel for submit button
|
| 78 |
+
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
| 79 |
+
submitBtn.setPreferredSize(new Dimension(200, 40)); // Make submit button larger
|
| 80 |
+
bottomPanel.add(submitBtn);
|
| 81 |
+
|
| 82 |
+
add(topPanel, BorderLayout.NORTH);
|
| 83 |
+
add(centerPanel, BorderLayout.CENTER);
|
| 84 |
+
add(bottomPanel, BorderLayout.SOUTH);
|
| 85 |
+
|
| 86 |
+
addListeners();
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
private JTextArea createTextArea(boolean editable) {
|
| 90 |
+
JTextArea ta = new JTextArea(5, 50);
|
| 91 |
+
ta.setLineWrap(true);
|
| 92 |
+
ta.setWrapStyleWord(true);
|
| 93 |
+
ta.setEditable(editable);
|
| 94 |
+
return ta;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
private void disableCopyPaste(JTextArea ta) {
|
| 98 |
+
ta.getInputMap().put(KeyStroke.getKeyStroke("ctrl C"), "none");
|
| 99 |
+
ta.getInputMap().put(KeyStroke.getKeyStroke("ctrl V"), "none");
|
| 100 |
+
ta.getInputMap().put(KeyStroke.getKeyStroke("ctrl X"), "none");
|
| 101 |
+
ta.setComponentPopupMenu(null);
|
| 102 |
+
ta.setTransferHandler(null);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
private void addListeners() {
|
| 106 |
+
generateBtn.addActionListener(e -> generateQuestion());
|
| 107 |
+
evaluateBtn.addActionListener(e -> evaluateAnswer());
|
| 108 |
+
nextBtn.addActionListener(e -> generateBtn.doClick());
|
| 109 |
+
submitBtn.addActionListener(e -> {
|
| 110 |
+
if (answeredCount > 0) {
|
| 111 |
+
controller.finishSession();
|
| 112 |
+
} else {
|
| 113 |
+
JOptionPane.showMessageDialog(this,
|
| 114 |
+
"Please answer at least one question before submitting.",
|
| 115 |
+
"No Answers",
|
| 116 |
+
JOptionPane.WARNING_MESSAGE);
|
| 117 |
+
}
|
| 118 |
+
});
|
| 119 |
+
readBtn.addActionListener(e -> {
|
| 120 |
+
String question = questionArea.getText().trim();
|
| 121 |
+
if (!question.isEmpty()) {
|
| 122 |
+
try {
|
| 123 |
+
controller.getTTSClient().speakText(question);
|
| 124 |
+
} catch (IOException ex) {
|
| 125 |
+
JOptionPane.showMessageDialog(this,
|
| 126 |
+
"Error reading question: " + ex.getMessage(),
|
| 127 |
+
"Error",
|
| 128 |
+
JOptionPane.ERROR_MESSAGE);
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
});
|
| 132 |
+
stopBtn.addActionListener(e -> {
|
| 133 |
+
controller.getTTSClient().stopSpeaking();
|
| 134 |
+
});
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
private void generateQuestion() {
|
| 138 |
+
String role = controller.getRole();
|
| 139 |
+
String username = controller.getCurrentUser();
|
| 140 |
+
if (!role.isEmpty()) {
|
| 141 |
+
String prompt = "Imagine you are an expert interviewer. The candidate is applying for the job role: \""
|
| 142 |
+
+ role + "\". Please create a thoughtful and beginner-friendly interview question.";
|
| 143 |
+
try {
|
| 144 |
+
questionArea.setText(controller.getGeminiClient().generateResponse(prompt));
|
| 145 |
+
answerInput.setText("");
|
| 146 |
+
feedbackArea.setText("");
|
| 147 |
+
startTimer();
|
| 148 |
+
} catch (IOException ex) {
|
| 149 |
+
questionArea.setText("Error: " + ex.getMessage());
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
private void evaluateAnswer() {
|
| 155 |
+
String question = questionArea.getText().trim();
|
| 156 |
+
String answer = answerInput.getText().trim();
|
| 157 |
+
if (!question.isEmpty() && !answer.isEmpty()) {
|
| 158 |
+
String evalPrompt = "Evaluate this answer to the question: \"" + question + "\".\n\nAnswer: \"" + answer
|
| 159 |
+
+ "\"\n\nGive short, clear feedback.";
|
| 160 |
+
try {
|
| 161 |
+
String feedback = controller.getGeminiClient().generateResponse(evalPrompt);
|
| 162 |
+
feedbackArea.setText(feedback);
|
| 163 |
+
recordAnswer(question, answer, feedback);
|
| 164 |
+
} catch (IOException ex) {
|
| 165 |
+
feedbackArea.setText("Error: " + ex.getMessage());
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
private void startTimer() {
|
| 171 |
+
timeLeft = 60;
|
| 172 |
+
if (timer != null)
|
| 173 |
+
timer.stop();
|
| 174 |
+
timer = new Timer(1000, e -> {
|
| 175 |
+
if (timeLeft <= 0) {
|
| 176 |
+
timer.stop();
|
| 177 |
+
nextBtn.doClick();
|
| 178 |
+
} else {
|
| 179 |
+
timeLeft--;
|
| 180 |
+
timerLabel.setText("Time Left: " + timeLeft + "s");
|
| 181 |
+
}
|
| 182 |
+
});
|
| 183 |
+
timer.start();
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
private void recordAnswer(String question, String answer, String feedback) {
|
| 187 |
+
QuestionData qd = new QuestionData();
|
| 188 |
+
qd.setQuestion(question);
|
| 189 |
+
qd.setAnswer(answer);
|
| 190 |
+
qd.setFeedback(feedback);
|
| 191 |
+
controller.getDataModel().setHistory(qd);
|
| 192 |
+
|
| 193 |
+
String filename = controller.getCurrentUser() + "_answers.txt";
|
| 194 |
+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true))) {
|
| 195 |
+
writer.write("Q: " + question + "\nA: " + answer + "\nF: " + feedback + "\n\n");
|
| 196 |
+
} catch (IOException e) {
|
| 197 |
+
e.printStackTrace();
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
answeredCount++;
|
| 201 |
+
counterLabel.setText(getCounterText());
|
| 202 |
+
|
| 203 |
+
if (answeredCount >= totalQuestions) {
|
| 204 |
+
controller.finishSession();
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
private String getCounterText() {
|
| 209 |
+
return "Answered: " + answeredCount + "/" + totalQuestions;
|
| 210 |
+
}
|
| 211 |
+
}
|
LoginPage.java
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
import java.awt.*;
|
| 3 |
+
import java.awt.event.*;
|
| 4 |
+
import java.io.*;
|
| 5 |
+
|
| 6 |
+
public class LoginPage extends JPanel {
|
| 7 |
+
private JTextField usernameField;
|
| 8 |
+
private JPasswordField passwordField;
|
| 9 |
+
private JButton loginButton, signupButton;
|
| 10 |
+
private JLabel statusLabel;
|
| 11 |
+
private boolean isSignUpMode = false;
|
| 12 |
+
private ClearPassAIGUI controller;
|
| 13 |
+
|
| 14 |
+
public LoginPage(ClearPassAIGUI controller) {
|
| 15 |
+
this.controller = controller;
|
| 16 |
+
setLayout(new GridBagLayout());
|
| 17 |
+
GridBagConstraints gbc = new GridBagConstraints();
|
| 18 |
+
gbc.insets = new Insets(10, 10, 10, 10);
|
| 19 |
+
gbc.fill = GridBagConstraints.HORIZONTAL;
|
| 20 |
+
|
| 21 |
+
JLabel titleLabel = new JLabel("Welcome to ClearPass AI", SwingConstants.CENTER);
|
| 22 |
+
titleLabel.setFont(new Font("Arial", Font.BOLD, 24));
|
| 23 |
+
gbc.gridx = 0;
|
| 24 |
+
gbc.gridy = 0;
|
| 25 |
+
gbc.gridwidth = 2;
|
| 26 |
+
add(titleLabel, gbc);
|
| 27 |
+
|
| 28 |
+
gbc.gridy++;
|
| 29 |
+
gbc.gridwidth = 1;
|
| 30 |
+
add(new JLabel("Username:"), gbc);
|
| 31 |
+
gbc.gridx = 1;
|
| 32 |
+
usernameField = new JTextField(15);
|
| 33 |
+
add(usernameField, gbc);
|
| 34 |
+
|
| 35 |
+
gbc.gridx = 0;
|
| 36 |
+
gbc.gridy++;
|
| 37 |
+
add(new JLabel("Password:"), gbc);
|
| 38 |
+
gbc.gridx = 1;
|
| 39 |
+
passwordField = new JPasswordField(15);
|
| 40 |
+
add(passwordField, gbc);
|
| 41 |
+
|
| 42 |
+
gbc.gridx = 0;
|
| 43 |
+
gbc.gridy++;
|
| 44 |
+
gbc.gridwidth = 2;
|
| 45 |
+
loginButton = new JButton("Login");
|
| 46 |
+
signupButton = new JButton("Sign Up");
|
| 47 |
+
JPanel buttonPanel = new JPanel(new FlowLayout());
|
| 48 |
+
buttonPanel.add(loginButton);
|
| 49 |
+
buttonPanel.add(signupButton);
|
| 50 |
+
add(buttonPanel, gbc);
|
| 51 |
+
|
| 52 |
+
gbc.gridy++;
|
| 53 |
+
statusLabel = new JLabel("", SwingConstants.CENTER);
|
| 54 |
+
add(statusLabel, gbc);
|
| 55 |
+
|
| 56 |
+
loginButton.addActionListener(e -> {
|
| 57 |
+
if (isSignUpMode)
|
| 58 |
+
handleSignUp();
|
| 59 |
+
else
|
| 60 |
+
handleLogin();
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
signupButton.addActionListener(e -> toggleMode());
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
private void toggleMode() {
|
| 67 |
+
isSignUpMode = !isSignUpMode;
|
| 68 |
+
loginButton.setText(isSignUpMode ? "Create Account" : "Login");
|
| 69 |
+
signupButton.setText(isSignUpMode ? "Back to Login" : "Sign Up");
|
| 70 |
+
statusLabel.setText("");
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
private void handleLogin() {
|
| 74 |
+
String username = usernameField.getText().trim();
|
| 75 |
+
String password = new String(passwordField.getPassword());
|
| 76 |
+
|
| 77 |
+
if (username.isEmpty() || password.isEmpty()) {
|
| 78 |
+
showMessage("Please enter both fields", Color.RED);
|
| 79 |
+
return;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
if (authenticate(username, password)) {
|
| 83 |
+
controller.setCurrentUser(username);
|
| 84 |
+
controller.showScreen("Welcome");
|
| 85 |
+
} else {
|
| 86 |
+
showMessage("Invalid credentials", Color.RED);
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
private void handleSignUp() {
|
| 91 |
+
String username = usernameField.getText().trim();
|
| 92 |
+
String password = new String(passwordField.getPassword());
|
| 93 |
+
|
| 94 |
+
if (username.isEmpty() || password.isEmpty()) {
|
| 95 |
+
showMessage("Please fill all fields", Color.RED);
|
| 96 |
+
return;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
File file = new File("users.txt");
|
| 100 |
+
try {
|
| 101 |
+
file.createNewFile();
|
| 102 |
+
BufferedReader reader = new BufferedReader(new FileReader(file));
|
| 103 |
+
String line;
|
| 104 |
+
while ((line = reader.readLine()) != null) {
|
| 105 |
+
if (line.startsWith(username + ":")) {
|
| 106 |
+
reader.close();
|
| 107 |
+
showMessage("Username already exists", Color.RED);
|
| 108 |
+
return;
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
reader.close();
|
| 112 |
+
|
| 113 |
+
BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
|
| 114 |
+
writer.write(username + ":" + password);
|
| 115 |
+
writer.newLine();
|
| 116 |
+
writer.close();
|
| 117 |
+
|
| 118 |
+
JOptionPane.showMessageDialog(this, "Account created successfully!");
|
| 119 |
+
toggleMode();
|
| 120 |
+
} catch (IOException ex) {
|
| 121 |
+
showMessage("Error accessing file", Color.RED);
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
private boolean authenticate(String username, String password) {
|
| 126 |
+
File file = new File("users.txt");
|
| 127 |
+
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
| 128 |
+
String line;
|
| 129 |
+
while ((line = reader.readLine()) != null) {
|
| 130 |
+
String[] parts = line.split(":");
|
| 131 |
+
if (parts.length == 2 && parts[0].equals(username) && parts[1].equals(password)) {
|
| 132 |
+
return true;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
} catch (IOException e) {
|
| 136 |
+
showMessage("Error reading file", Color.RED);
|
| 137 |
+
}
|
| 138 |
+
return false;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
private void showMessage(String message, Color color) {
|
| 142 |
+
statusLabel.setText(message);
|
| 143 |
+
statusLabel.setForeground(color);
|
| 144 |
+
}
|
| 145 |
+
}
|
MozillaTTSClient.java
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.io.*;
|
| 2 |
+
import java.net.HttpURLConnection;
|
| 3 |
+
import java.net.URL;
|
| 4 |
+
import javax.sound.sampled.*;
|
| 5 |
+
|
| 6 |
+
public class MozillaTTSClient {
|
| 7 |
+
private final String voiceId = "en_US/vctk_low"; // Free voice model
|
| 8 |
+
private final String apiUrl = "https://api-inference.huggingface.co/models/mozilla/tts";
|
| 9 |
+
|
| 10 |
+
public MozillaTTSClient() {
|
| 11 |
+
// No API key needed for basic usage
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
public void speakText(String text) throws IOException {
|
| 15 |
+
String inputJson = "{\n" +
|
| 16 |
+
" \"inputs\": \"" + text.replace("\"", "\\\"") + "\",\n" +
|
| 17 |
+
" \"parameters\": {\n" +
|
| 18 |
+
" \"voice_preset\": \"" + voiceId + "\"\n" +
|
| 19 |
+
" }\n" +
|
| 20 |
+
"}";
|
| 21 |
+
|
| 22 |
+
URL url = new URL(apiUrl);
|
| 23 |
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
| 24 |
+
conn.setRequestMethod("POST");
|
| 25 |
+
conn.setRequestProperty("Content-Type", "application/json");
|
| 26 |
+
conn.setDoOutput(true);
|
| 27 |
+
|
| 28 |
+
try (OutputStream os = conn.getOutputStream()) {
|
| 29 |
+
os.write(inputJson.getBytes("utf-8"));
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
int status = conn.getResponseCode();
|
| 33 |
+
if (status == 200) {
|
| 34 |
+
try (InputStream is = conn.getInputStream()) {
|
| 35 |
+
// Save the audio to a temporary file
|
| 36 |
+
File tempFile = File.createTempFile("speech", ".wav");
|
| 37 |
+
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
| 38 |
+
byte[] buffer = new byte[1024];
|
| 39 |
+
int bytesRead;
|
| 40 |
+
while ((bytesRead = is.read(buffer)) != -1) {
|
| 41 |
+
fos.write(buffer, 0, bytesRead);
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// Play the audio
|
| 46 |
+
try (AudioInputStream audioIn = AudioSystem.getAudioInputStream(tempFile)) {
|
| 47 |
+
Clip clip = AudioSystem.getClip();
|
| 48 |
+
clip.open(audioIn);
|
| 49 |
+
clip.start();
|
| 50 |
+
clip.drain();
|
| 51 |
+
clip.close();
|
| 52 |
+
} catch (UnsupportedAudioFileException | LineUnavailableException e) {
|
| 53 |
+
throw new IOException("Error playing audio: " + e.getMessage());
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Clean up the temporary file
|
| 57 |
+
tempFile.delete();
|
| 58 |
+
}
|
| 59 |
+
} else {
|
| 60 |
+
throw new IOException("Error: " + status);
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
QuestionData.java
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
public class QuestionData {
|
| 2 |
+
private String question;
|
| 3 |
+
private String answer;
|
| 4 |
+
private String feedback;
|
| 5 |
+
|
| 6 |
+
public QuestionData() {
|
| 7 |
+
this.question = "";
|
| 8 |
+
this.feedback = "";
|
| 9 |
+
this.answer = "";
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
public void setQuestion(String question) {
|
| 13 |
+
this.question = question;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
public void setFeedback(String feedback) {
|
| 17 |
+
this.feedback = feedback;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
public void setAnswer(String answer) {
|
| 21 |
+
this.answer = answer;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
public String getQuestion() {
|
| 25 |
+
return question;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
public String getFeedback() {
|
| 29 |
+
return feedback;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
public String getAnswer() {
|
| 33 |
+
return answer;
|
| 34 |
+
}
|
| 35 |
+
}
|
README.md
CHANGED
|
@@ -1,12 +1,77 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ClearPass AI - Java Swing Interview Simulator
|
| 2 |
+
|
| 3 |
+
ClearPass AI is a Java Swing-based desktop application that simulates AI-powered interview questions. It allows users to log in, specify a job role, answer AI-generated questions, receive real-time feedback, and view or export their session summaries.
|
| 4 |
+
|
| 5 |
+
## ✨ Features
|
| 6 |
+
- 🔐 User login and signup system (local file-based)
|
| 7 |
+
- 🧠 AI-generated interview questions based on job role (using Google's Gemini API)
|
| 8 |
+
- 😊 Sentiment analysis of answers (using HuggingFace's DistilBERT model)
|
| 9 |
+
- ✅ Real-time feedback on user responses
|
| 10 |
+
- ⏱ Countdown timer per question
|
| 11 |
+
- 📊 Progress tracker (answered vs. remaining)
|
| 12 |
+
- 💾 Save full session summary and result to `.txt` files
|
| 13 |
+
- 🧾 Clean MVC-based structure for easy updates
|
| 14 |
+
|
| 15 |
+
## 🛠 Requirements
|
| 16 |
+
- Java 8 or higher
|
| 17 |
+
- Internet connection (for AI API calls)
|
| 18 |
+
- JSON library (org.json)
|
| 19 |
+
|
| 20 |
+
## 📁 File Structure
|
| 21 |
+
```
|
| 22 |
+
project-root/
|
| 23 |
+
├── ClearPassAIApp.java
|
| 24 |
+
├── ClearPassAIGUI.java
|
| 25 |
+
├── InterviewScreen.java
|
| 26 |
+
├── LoginPage.java
|
| 27 |
+
├── WelcomeScreen.java
|
| 28 |
+
├── ResultScreen.java
|
| 29 |
+
├── DataModel.java
|
| 30 |
+
├── QuestionData.java
|
| 31 |
+
├── GeminiClient.java
|
| 32 |
+
├── HuggingFaceClient.java
|
| 33 |
+
├── users.txt
|
| 34 |
+
├── config.properties <-- contains your API keys
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
## 🔑 Setup API Keys
|
| 38 |
+
1. Create a `config.properties` file in the root directory.
|
| 39 |
+
2. Add your API keys:
|
| 40 |
+
|
| 41 |
+
```properties
|
| 42 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 43 |
+
HUGGINGFACE_API_KEY=your_huggingface_api_key_here
|
| 44 |
+
```
|
| 45 |
|
| 46 |
+
## ▶️ How to Run
|
| 47 |
+
### If using terminal:
|
| 48 |
+
```bash
|
| 49 |
+
javac -cp ".:lib/json-20250107.jar" *.java
|
| 50 |
+
java -cp ".:lib/json-20250107.jar" ClearPassAIApp
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### If using an IDE:
|
| 54 |
+
- Open the project.
|
| 55 |
+
- Add the JSON library to your project dependencies.
|
| 56 |
+
- Set `ClearPassAIApp` as the main class.
|
| 57 |
+
- Run the project.
|
| 58 |
+
|
| 59 |
+
## 💬 Sample Credentials
|
| 60 |
+
- Username: `dhruvparmar`
|
| 61 |
+
- Password: `1234`
|
| 62 |
+
|
| 63 |
+
You can create new users via the Sign-Up screen.
|
| 64 |
+
|
| 65 |
+
## 📂 Output Files
|
| 66 |
+
- `username_answers.txt` → contains each Q&A + feedback
|
| 67 |
+
- `username_summary.txt` → full interview session
|
| 68 |
+
- `username_result.txt` → saved from Result screen (if user clicks save)
|
| 69 |
+
|
| 70 |
+
## ✅ Future Improvements
|
| 71 |
+
- Add more AI models for different types of analysis
|
| 72 |
+
- Implement voice input/output
|
| 73 |
+
- Add support for different languages
|
| 74 |
+
- Enhance the UI with modern design elements
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
Made with 💙 by Dhruv Parmar
|
ResultScreen.java
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
import java.awt.*;
|
| 3 |
+
import java.awt.event.ActionEvent;
|
| 4 |
+
import java.io.*;
|
| 5 |
+
import java.util.List;
|
| 6 |
+
|
| 7 |
+
public class ResultScreen extends JPanel {
|
| 8 |
+
private JTextArea resultArea;
|
| 9 |
+
|
| 10 |
+
public ResultScreen(ClearPassAIGUI controller) {
|
| 11 |
+
setLayout(new BorderLayout());
|
| 12 |
+
|
| 13 |
+
resultArea = new JTextArea();
|
| 14 |
+
resultArea.setEditable(false);
|
| 15 |
+
JScrollPane scrollPane = new JScrollPane(resultArea);
|
| 16 |
+
add(scrollPane, BorderLayout.CENTER);
|
| 17 |
+
|
| 18 |
+
List<QuestionData> history = controller.getDataModel().getHistory();
|
| 19 |
+
int total = controller.getQuestionCount();
|
| 20 |
+
String username = controller.getCurrentUser();
|
| 21 |
+
|
| 22 |
+
if (history.isEmpty()) {
|
| 23 |
+
resultArea.setText("No responses recorded.");
|
| 24 |
+
} else {
|
| 25 |
+
StringBuilder sb = new StringBuilder();
|
| 26 |
+
sb.append("Summary: You answered ").append(history.size()).append(" out of ").append(total)
|
| 27 |
+
.append(" questions.\n\n");
|
| 28 |
+
|
| 29 |
+
for (int i = 0; i < history.size(); i++) {
|
| 30 |
+
QuestionData q = history.get(i);
|
| 31 |
+
sb.append("Q").append(i + 1).append(": ").append(q.getQuestion()).append("\n");
|
| 32 |
+
sb.append("Your Answer: ").append(q.getAnswer()).append("\n");
|
| 33 |
+
sb.append("AI Feedback: ").append(q.getFeedback()).append("\n\n");
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
resultArea.setText(sb.toString());
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
JButton backBtn = new JButton("Back to Welcome");
|
| 40 |
+
backBtn.addActionListener(e -> controller.showScreen("Welcome"));
|
| 41 |
+
|
| 42 |
+
JButton saveBtn = new JButton("Save Result");
|
| 43 |
+
saveBtn.addActionListener(
|
| 44 |
+
(ActionEvent e) -> saveResultToFile(controller.getCurrentUser(), resultArea.getText()));
|
| 45 |
+
|
| 46 |
+
JPanel bottomPanel = new JPanel(new FlowLayout());
|
| 47 |
+
bottomPanel.add(backBtn);
|
| 48 |
+
bottomPanel.add(saveBtn);
|
| 49 |
+
|
| 50 |
+
add(bottomPanel, BorderLayout.SOUTH);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
private void saveResultToFile(String username, String content) {
|
| 54 |
+
String filename = username + "_result.txt";
|
| 55 |
+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
|
| 56 |
+
writer.write(content);
|
| 57 |
+
JOptionPane.showMessageDialog(this, "Result saved to " + filename);
|
| 58 |
+
} catch (IOException e) {
|
| 59 |
+
JOptionPane.showMessageDialog(this, "Failed to save result.", "Error", JOptionPane.ERROR_MESSAGE);
|
| 60 |
+
e.printStackTrace();
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
TextToSpeechClient.java
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import java.io.*;
|
| 2 |
+
import java.net.HttpURLConnection;
|
| 3 |
+
import java.net.URL;
|
| 4 |
+
import javax.sound.sampled.*;
|
| 5 |
+
import org.json.JSONObject;
|
| 6 |
+
|
| 7 |
+
public class TextToSpeechClient {
|
| 8 |
+
private final String apiKey;
|
| 9 |
+
private final String model = "facebook/fastspeech2-en-ljspeech";
|
| 10 |
+
|
| 11 |
+
public TextToSpeechClient(String apiKey) {
|
| 12 |
+
this.apiKey = apiKey;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
public void speakText(String text) throws IOException {
|
| 16 |
+
String inputJson = "{\n" +
|
| 17 |
+
" \"inputs\": \"" + text.replace("\"", "\\\"") + "\",\n" +
|
| 18 |
+
" \"parameters\": {\n" +
|
| 19 |
+
" \"return_timestamps\": false\n" +
|
| 20 |
+
" }\n" +
|
| 21 |
+
"}";
|
| 22 |
+
|
| 23 |
+
URL url = new URL("https://api-inference.huggingface.co/models/" + model);
|
| 24 |
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
| 25 |
+
conn.setRequestMethod("POST");
|
| 26 |
+
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
|
| 27 |
+
conn.setRequestProperty("Content-Type", "application/json");
|
| 28 |
+
conn.setDoOutput(true);
|
| 29 |
+
|
| 30 |
+
try (OutputStream os = conn.getOutputStream()) {
|
| 31 |
+
os.write(inputJson.getBytes("utf-8"));
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
int status = conn.getResponseCode();
|
| 35 |
+
if (status == 200) {
|
| 36 |
+
try (InputStream is = conn.getInputStream()) {
|
| 37 |
+
// Save the audio to a temporary file
|
| 38 |
+
File tempFile = File.createTempFile("speech", ".wav");
|
| 39 |
+
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
| 40 |
+
byte[] buffer = new byte[1024];
|
| 41 |
+
int bytesRead;
|
| 42 |
+
while ((bytesRead = is.read(buffer)) != -1) {
|
| 43 |
+
fos.write(buffer, 0, bytesRead);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Play the audio
|
| 48 |
+
try (AudioInputStream audioIn = AudioSystem.getAudioInputStream(tempFile)) {
|
| 49 |
+
Clip clip = AudioSystem.getClip();
|
| 50 |
+
clip.open(audioIn);
|
| 51 |
+
clip.start();
|
| 52 |
+
clip.drain();
|
| 53 |
+
clip.close();
|
| 54 |
+
} catch (UnsupportedAudioFileException | LineUnavailableException e) {
|
| 55 |
+
throw new IOException("Error playing audio: " + e.getMessage());
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Clean up the temporary file
|
| 59 |
+
tempFile.delete();
|
| 60 |
+
}
|
| 61 |
+
} else {
|
| 62 |
+
throw new IOException("Error: " + status);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
WelcomeScreen.java
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import javax.swing.*;
|
| 2 |
+
import java.awt.*;
|
| 3 |
+
|
| 4 |
+
public class WelcomeScreen extends JPanel {
|
| 5 |
+
private ClearPassAIGUI controller;
|
| 6 |
+
private JTextField roleField;
|
| 7 |
+
private JSpinner questionCountSpinner;
|
| 8 |
+
|
| 9 |
+
public WelcomeScreen(ClearPassAIGUI controller) {
|
| 10 |
+
this.controller = controller;
|
| 11 |
+
setLayout(new FlowLayout());
|
| 12 |
+
|
| 13 |
+
add(new JLabel("Enter Job Role:"));
|
| 14 |
+
roleField = new JTextField(20);
|
| 15 |
+
add(roleField);
|
| 16 |
+
|
| 17 |
+
add(new JLabel("Number of Questions:"));
|
| 18 |
+
questionCountSpinner = new JSpinner(new SpinnerNumberModel(5, 1, 20, 1));
|
| 19 |
+
add(questionCountSpinner);
|
| 20 |
+
|
| 21 |
+
JButton startButton = new JButton("Start Interview");
|
| 22 |
+
add(startButton);
|
| 23 |
+
|
| 24 |
+
startButton.addActionListener(e -> {
|
| 25 |
+
String role = roleField.getText().trim();
|
| 26 |
+
int questionCount = (int) questionCountSpinner.getValue();
|
| 27 |
+
if (!role.isEmpty()) {
|
| 28 |
+
controller.setRole(role);
|
| 29 |
+
controller.setQuestionCount(questionCount);
|
| 30 |
+
controller.startSession(role, questionCount);
|
| 31 |
+
} else {
|
| 32 |
+
JOptionPane.showMessageDialog(this, "Please enter a job role.", "Input Error",
|
| 33 |
+
JOptionPane.ERROR_MESSAGE);
|
| 34 |
+
}
|
| 35 |
+
});
|
| 36 |
+
}
|
| 37 |
+
}
|
json-20210307.jar
ADDED
|
Binary file (69.7 kB). View file
|
|
|
json-20231013.jar
ADDED
|
Binary file (74.7 kB). View file
|
|
|
json-20250107.jar
ADDED
|
Binary file (81.4 kB). View file
|
|
|
users.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dhruvparmar:1234
|
| 2 |
+
aksha:123
|
| 3 |
+
justin:123
|
| 4 |
+
jason:123
|
| 5 |
+
saahil:123
|
| 6 |
+
justinTest:test
|