Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .manus/db/db-query-1774834216959.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"stdout": "",
"stderr": "",
"execution_time_ms": 10273
}
}
16 changes: 11 additions & 5 deletions AI_CURRICULUM_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ This document outlines the theoretical framework and algorithmic logic for the A

SprintWork follows a research-backed 4-stage framework for career development:

| Stage | Goal | AI Feature |
|---|---|---|
| Stage | Goal | AI Feature |
| ----------- | ------------------------------------ | --------------------------------------- |
| **Explore** | Identify career paths and skill gaps | Smart Job Matching & Skill Gap Analysis |
| **Build** | Create professional assets | AI CV Tailoring & Resume Builder |
| **Connect** | Build professional network | Networking Hub & Recruiter Matching |
| **Refine** | Master interview and soft skills | AI Mock Interviews & Scoring |
| **Build** | Create professional assets | AI CV Tailoring & Resume Builder |
| **Connect** | Build professional network | Networking Hub & Recruiter Matching |
| **Refine** | Master interview and soft skills | AI Mock Interviews & Scoring |

---

Expand All @@ -22,6 +22,7 @@ SprintWork follows a research-backed 4-stage framework for career development:
The interview curriculum is divided into four specialized tracks, each with a structured progression:

### A. Behavioral Track (STAR Method)

- **Focus:** Soft skills, leadership, conflict resolution.
- **Curriculum:**
1. **Foundations:** Introduction to the STAR method.
Expand All @@ -31,6 +32,7 @@ The interview curriculum is divided into four specialized tracks, each with a st
- **Algorithm:** AI evaluates responses based on the presence of **Situation, Task, Action, and Result** components.

### B. Technical Track (DSA & System Design)

- **Focus:** Problem-solving, coding proficiency, architecture.
- **Curriculum:**
1. **Data Structures:** Arrays, Linked Lists, Trees, Graphs.
Expand All @@ -39,6 +41,7 @@ The interview curriculum is divided into four specialized tracks, each with a st
- **Algorithm:** AI checks for technical accuracy, time/space complexity analysis, and edge case handling.

### C. Case Study Track

- **Focus:** Analytical thinking, business logic.
- **Curriculum:**
1. **Market Entry:** Analyzing new business opportunities.
Expand All @@ -50,6 +53,7 @@ The interview curriculum is divided into four specialized tracks, each with a st
## 3. Core Algorithms

### A. Smart Job Matching Algorithm (Hybrid Approach)

The matching score ($S$) is calculated as a weighted sum of multiple factors:

$$S = w_1 \cdot S_{skills} + w_2 \cdot S_{experience} + w_3 \cdot S_{location} + w_4 \cdot S_{salary}$$
Expand All @@ -60,6 +64,7 @@ $$S = w_1 \cdot S_{skills} + w_2 \cdot S_{experience} + w_3 \cdot S_{location} +
- **$S_{salary}$:** Overlap between user expectations and job budget.

### B. AI CV Tailoring Algorithm

1. **Extraction:** Use LLM to extract key requirements (Skills, Keywords, Responsibilities) from a job description.
2. **Analysis:** Compare extracted keywords with the user's current resume.
3. **Optimization:**
Expand All @@ -68,6 +73,7 @@ $$S = w_1 \cdot S_{skills} + w_2 \cdot S_{experience} + w_3 \cdot S_{location} +
- **Quantification:** Prompt user to add metrics (e.g., "Increased sales by 20%").

### C. Interview Scoring Rubric (0-100)

- **Content (40%):** Accuracy, relevance, and depth of the answer.
- **Structure (30%):** Use of STAR method or logical flow.
- **Communication (20%):** Clarity, tone, and professional language.
Expand Down
5 changes: 2 additions & 3 deletions client/index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1" />
content="width=device-width, initial-scale=1.0, maximum-scale=1"
/>
<title>SprintWork - The AI-Powered Job Market Revolution</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
90 changes: 57 additions & 33 deletions client/public/__manus__/debug-collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@
if (value === undefined) return undefined;

if (typeof value === "string") {
return value.length > 1000 ? value.slice(0, 1000) + "...[truncated]" : value;
return value.length > 1000
? value.slice(0, 1000) + "...[truncated]"
: value;
}

if (typeof value !== "object") return value;
Expand Down Expand Up @@ -178,7 +180,7 @@
getAttr("data-test") ||
null;

var type = tag === "input" ? (getAttr("type") || "text") : null;
var type = tag === "input" ? getAttr("type") || "text" : null;
var href = tag === "a" ? getAttr("href") || null : null;

// a small, stable hint for agents (avoid building full CSS paths)
Expand Down Expand Up @@ -233,7 +235,8 @@

if (isSensitiveField(el)) return { masked: true, length: v.length };

if (v.length > CONFIG.uiInputMaxLen) v = v.slice(0, CONFIG.uiInputMaxLen) + "…";
if (v.length > CONFIG.uiInputMaxLen)
v = v.slice(0, CONFIG.uiInputMaxLen) + "…";
return v;
}

Expand Down Expand Up @@ -456,9 +459,10 @@
init = init || {};
var startTime = Date.now();
// Handle string, Request object, or URL object
var url = typeof input === "string"
? input
: (input && (input.url || input.href || String(input))) || "";
var url =
typeof input === "string"
? input
: (input && (input.url || input.href || String(input))) || "";
var method = init.method || (input && input.method) || "GET";

// Don't intercept internal requests
Expand All @@ -470,7 +474,9 @@
var requestHeaders = {};
try {
if (init.headers) {
requestHeaders = Object.fromEntries(new Headers(init.headers).entries());
requestHeaders = Object.fromEntries(
new Headers(init.headers).entries()
);
}
} catch (e) {
requestHeaders = { _parseError: true };
Expand All @@ -494,7 +500,9 @@
.then(function (response) {
entry.duration = Date.now() - startTime;

var contentType = (response.headers.get("content-type") || "").toLowerCase();
var contentType = (
response.headers.get("content-type") || ""
).toLowerCase();
var contentLength = response.headers.get("content-length");

entry.response = {
Expand All @@ -516,9 +524,10 @@
}

// Skip body capture for streaming responses (SSE, etc.) to avoid memory leaks
var isStreaming = contentType.indexOf("text/event-stream") !== -1 ||
contentType.indexOf("application/stream") !== -1 ||
contentType.indexOf("application/x-ndjson") !== -1;
var isStreaming =
contentType.indexOf("text/event-stream") !== -1 ||
contentType.indexOf("application/stream") !== -1 ||
contentType.indexOf("application/x-ndjson") !== -1;
if (isStreaming) {
entry.response.body = "[Streaming response - not captured]";
store.networkRequests.push(entry);
Expand All @@ -527,20 +536,25 @@
}

// Skip body capture for large responses to avoid memory issues
if (contentLength && parseInt(contentLength, 10) > CONFIG.maxBodyLength) {
entry.response.body = "[Response too large: " + contentLength + " bytes]";
if (
contentLength &&
parseInt(contentLength, 10) > CONFIG.maxBodyLength
) {
entry.response.body =
"[Response too large: " + contentLength + " bytes]";
store.networkRequests.push(entry);
pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
return response;
}

// Skip body capture for binary content types
var isBinary = contentType.indexOf("image/") !== -1 ||
contentType.indexOf("video/") !== -1 ||
contentType.indexOf("audio/") !== -1 ||
contentType.indexOf("application/octet-stream") !== -1 ||
contentType.indexOf("application/pdf") !== -1 ||
contentType.indexOf("application/zip") !== -1;
var isBinary =
contentType.indexOf("image/") !== -1 ||
contentType.indexOf("video/") !== -1 ||
contentType.indexOf("audio/") !== -1 ||
contentType.indexOf("application/octet-stream") !== -1 ||
contentType.indexOf("application/pdf") !== -1 ||
contentType.indexOf("application/zip") !== -1;
if (isBinary) {
entry.response.body = "[Binary content: " + contentType + "]";
store.networkRequests.push(entry);
Expand All @@ -558,7 +572,8 @@
if (text.length <= CONFIG.maxBodyLength) {
entry.response.body = sanitizeValue(tryParseJson(text));
} else {
entry.response.body = text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
entry.response.body =
text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
}
})
.catch(function () {
Expand Down Expand Up @@ -615,24 +630,30 @@
xhr._manusData.url.indexOf("/__manus__/") !== 0
) {
xhr._manusData.startTime = Date.now();
xhr._manusData.requestBody = body ? sanitizeValue(tryParseJson(body)) : null;
xhr._manusData.requestBody = body
? sanitizeValue(tryParseJson(body))
: null;

xhr.addEventListener("load", function () {
var contentType = (xhr.getResponseHeader("content-type") || "").toLowerCase();
var contentType = (
xhr.getResponseHeader("content-type") || ""
).toLowerCase();
var responseBody = null;

// Skip body capture for streaming responses
var isStreaming = contentType.indexOf("text/event-stream") !== -1 ||
contentType.indexOf("application/stream") !== -1 ||
contentType.indexOf("application/x-ndjson") !== -1;
var isStreaming =
contentType.indexOf("text/event-stream") !== -1 ||
contentType.indexOf("application/stream") !== -1 ||
contentType.indexOf("application/x-ndjson") !== -1;

// Skip body capture for binary content types
var isBinary = contentType.indexOf("image/") !== -1 ||
contentType.indexOf("video/") !== -1 ||
contentType.indexOf("audio/") !== -1 ||
contentType.indexOf("application/octet-stream") !== -1 ||
contentType.indexOf("application/pdf") !== -1 ||
contentType.indexOf("application/zip") !== -1;
var isBinary =
contentType.indexOf("image/") !== -1 ||
contentType.indexOf("video/") !== -1 ||
contentType.indexOf("audio/") !== -1 ||
contentType.indexOf("application/octet-stream") !== -1 ||
contentType.indexOf("application/pdf") !== -1 ||
contentType.indexOf("application/zip") !== -1;

if (isStreaming) {
responseBody = "[Streaming response - not captured]";
Expand All @@ -643,7 +664,8 @@
try {
var text = xhr.responseText || "";
if (text.length > CONFIG.maxBodyLength) {
responseBody = text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
responseBody =
text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
} else {
responseBody = sanitizeValue(tryParseJson(text));
}
Expand Down Expand Up @@ -817,5 +839,7 @@
forceReport: reportLogs,
};

console.debug("[Manus] Debug collector initialized (no rrweb, UI events only)");
console.debug(
"[Manus] Debug collector initialized (no rrweb, UI events only)"
);
})();
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Toaster } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import NotFound from "@/pages/NotFound";
import { Analytics } from "@vercel/analytics/react";
import { Route, Switch } from "wouter";
import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext";
Expand Down Expand Up @@ -59,6 +60,7 @@ function App() {
<TooltipProvider>
<Toaster />
<Router />
<Analytics />
</TooltipProvider>
</ThemeProvider>
</ErrorBoundary>
Expand Down
2 changes: 1 addition & 1 deletion client/src/_core/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function useAuth(options?: UseAuthOptions) {
if (typeof window === "undefined") return;
if (window.location.pathname === redirectPath) return;

window.location.href = redirectPath
window.location.href = redirectPath;
}, [
redirectOnUnauthenticated,
redirectPath,
Expand Down
11 changes: 6 additions & 5 deletions client/src/components/AIChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function AIChatBox({
const textareaRef = useRef<HTMLTextAreaElement>(null);

// Filter out system messages
const displayMessages = messages.filter((msg) => msg.role !== "system");
const displayMessages = messages.filter(msg => msg.role !== "system");

// Calculate min-height for last assistant message to push user message to top
const [minHeightForLastMessage, setMinHeightForLastMessage] = useState(0);
Expand All @@ -143,7 +143,8 @@ export function AIChatBox({
// - user message: 40px (item height) + 16px (margin-top from space-y-4) = 56px
// Note: margin-bottom is not counted because it naturally pushes the assistant message down
const userMessageReservedHeight = 56;
const calculatedHeight = scrollAreaHeight - 32 - userMessageReservedHeight;
const calculatedHeight =
scrollAreaHeight - 32 - userMessageReservedHeight;

setMinHeightForLastMessage(Math.max(0, calculatedHeight));
}
Expand All @@ -152,14 +153,14 @@ export function AIChatBox({
// Scroll to bottom helper function with smooth animation
const scrollToBottom = () => {
const viewport = scrollAreaRef.current?.querySelector(
'[data-radix-scroll-area-viewport]'
"[data-radix-scroll-area-viewport]"
) as HTMLDivElement;

if (viewport) {
requestAnimationFrame(() => {
viewport.scrollTo({
top: viewport.scrollHeight,
behavior: 'smooth'
behavior: "smooth",
});
});
}
Expand Down Expand Up @@ -311,7 +312,7 @@ export function AIChatBox({
<Textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className="flex-1 max-h-32 resize-none min-h-9"
Expand Down
Loading