diff --git a/src/app.css b/src/app.css
index 3c253e7..947df98 100644
--- a/src/app.css
+++ b/src/app.css
@@ -4423,7 +4423,7 @@ body {
.back-top {
position: fixed;
right: 18px;
- bottom: 18px;
+ bottom: 90px;
z-index: 80;
width: 46px;
height: 46px;
diff --git a/src/components/AiChatWidget.tsx b/src/components/AiChatWidget.tsx
index 14e1ae8..f3ba42c 100644
--- a/src/components/AiChatWidget.tsx
+++ b/src/components/AiChatWidget.tsx
@@ -10,7 +10,7 @@ interface ChatMessage {
}
interface ChatResponse {
- message: string;
+ reply: string;
conversation_id: string;
intent: string;
confidence: number;
@@ -22,7 +22,7 @@ export function AiChatWidget() {
{
role: "assistant",
content:
- "Hi! I'm your AI assistant. I can help you create support tickets, fill out forms, generate job descriptions, or write cover letters. What can I help you with?",
+ "Hi! I'm Ask Ash, your Nxtgauge assistant. I can help you:\n• Search help articles & KB\n• Create support tickets\n• Explain your AI plan & usage\n• Answer questions about the platform",
},
]);
const [input, setInput] = createSignal("");
@@ -59,7 +59,7 @@ export function AiChatWidget() {
const assistantMessage: ChatMessage = {
role: "assistant",
- content: data.message,
+ content: data.reply,
intent: data.intent,
};
setMessages((prev) => [...prev, assistantMessage]);
@@ -148,7 +148,8 @@ export function AiChatWidget() {
{ e.currentTarget.style.display = 'none'; }}
/>
@@ -180,11 +181,16 @@ export function AiChatWidget() {
"flex-wrap": "wrap",
}}
>
- {["Create Ticket", "Job Description", "Cover Letter", "Fill Form"].map((label) => (
+ {[
+ { label: "Support Ticket", text: "I need help with " },
+ { label: "Search KB", text: "How do I " },
+ { label: "AI Plan", text: "Explain my AI plan" },
+ { label: "Check Balance", text: "Check my AI balance" },
+ ].map((action) => (
))}
diff --git a/src/components/dashboard/JobSeekerJobsPage.tsx b/src/components/dashboard/JobSeekerJobsPage.tsx
index 4dae4d0..c732859 100644
--- a/src/components/dashboard/JobSeekerJobsPage.tsx
+++ b/src/components/dashboard/JobSeekerJobsPage.tsx
@@ -3,9 +3,11 @@
* Endpoints:
* GET /api/jobseeker/jobs - List available jobs from companies
* POST /api/jobseeker/jobs/:id/apply - Apply for a job
+ * POST /api/ai/generate-cover-letter - AI cover letter generation
* Custom data: saved_jobs - Bookmarked jobs stored in profile
*/
import { For, Show, createMemo, createSignal, onMount } from "solid-js";
+import { Sparkles, Loader } from "lucide-solid";
import { BTN_GHOST, BTN_PRIMARY, CARD, INPUT } from "~/components/DashboardShell";
import { readJobSeekerProfile, updateJobSeekerCustomData } from "~/lib/job-seeker-custom-data";
@@ -102,6 +104,10 @@ export default function JobSeekerJobsPage() {
const [activeTag, setActiveTag] = createSignal("");
const [msg, setMsg] = createSignal("");
const [err, setErr] = createSignal("");
+ const [generatingCover, setGeneratingCover] = createSignal(null);
+ const [coverLetter, setCoverLetter] = createSignal(null);
+ const [aiRemaining, setAiRemaining] = createSignal(5);
+ const [aiLimit, setAiLimit] = createSignal(5);
const availableTags = createMemo(() => {
const tags = new Set();
@@ -146,14 +152,16 @@ export default function JobSeekerJobsPage() {
void loadSavedJobs();
});
- const applyJob = async (jobId: string) => {
+ const applyJob = async (jobId: string, generatedCoverLetter?: string | null) => {
setBusyId(jobId);
setMsg("");
setErr("");
try {
const res = await apiFetch(`/api/jobseeker/jobs/${jobId}/apply`, {
method: "POST",
- body: JSON.stringify({}),
+ body: JSON.stringify({
+ cover_letter: generatedCoverLetter || coverLetter() || undefined
+ }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
@@ -161,6 +169,9 @@ export default function JobSeekerJobsPage() {
return;
}
setMsg("Application submitted successfully.");
+ if (generatedCoverLetter) {
+ setCoverLetter(null);
+ }
} catch {
setErr("Network error while applying.");
} finally {
@@ -168,6 +179,48 @@ export default function JobSeekerJobsPage() {
}
};
+ const generateCoverLetter = async (job: JobItem) => {
+ if (aiRemaining() <= 0) {
+ setErr("Daily AI generation limit reached. Upgrade to AI Pack for more.");
+ return;
+ }
+ setGeneratingCover(job.id);
+ setErr("");
+ try {
+ const token = window.sessionStorage.getItem("nxtgauge_access_token") || "";
+ const res = await fetch(`${API}/api/ai/generate-cover-letter`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ job_title: job.title || "this position",
+ company_name: job.company_name || undefined,
+ job_description: job.description || undefined,
+ }),
+ });
+
+ if (res.status === 429) {
+ setErr("Daily AI generation limit reached. Upgrade to AI Pack for more.");
+ return;
+ }
+
+ const data = await res.json();
+ if (res.ok && data.cover_letter) {
+ setCoverLetter(data.cover_letter);
+ setAiRemaining(data.remaining_today ?? aiRemaining() - 1);
+ setAiLimit(data.daily_limit ?? aiLimit());
+ } else {
+ setErr(data.error || "Failed to generate cover letter");
+ }
+ } catch {
+ setErr("Network error during cover letter generation");
+ } finally {
+ setGeneratingCover(null);
+ }
+ };
+
const isSaved = (jobId: string) => savedJobs().some((row) => row.id === jobId);
const toggleSave = async (job: JobItem) => {
@@ -393,20 +446,40 @@ export default function JobSeekerJobsPage() {
+
+
+
+
+ Cover Letter Ready
+
+
{coverLetter()?.substring(0, 200)}...
+
+
+
)}
+
+
);
}