@hadoku/resume-bot 0.0.0-stage → 3.14.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -3
- package/dist/App.d.ts +9 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/ChatInterface-mHtxUGpp.js +128 -0
- package/dist/ChatInterface-mHtxUGpp.js.map +1 -0
- package/dist/ResumeViewer-C-uxZ-7E.js +296 -0
- package/dist/ResumeViewer-C-uxZ-7E.js.map +1 -0
- package/dist/builder/BlockCard.d.ts +15 -0
- package/dist/builder/BlockCard.d.ts.map +1 -0
- package/dist/builder/BuilderApp.d.ts +3 -0
- package/dist/builder/BuilderApp.d.ts.map +1 -0
- package/dist/builder/api.d.ts +37 -0
- package/dist/builder/api.d.ts.map +1 -0
- package/dist/builder/entry.d.ts +8 -0
- package/dist/builder/entry.d.ts.map +1 -0
- package/dist/builder/useBuilderAuth.d.ts +10 -0
- package/dist/builder/useBuilderAuth.d.ts.map +1 -0
- package/dist/builder.js +537 -0
- package/dist/builder.js.map +1 -0
- package/dist/components/ChatInterface.d.ts +10 -0
- package/dist/components/ChatInterface.d.ts.map +1 -0
- package/dist/components/ResumeDocument.d.ts +30 -0
- package/dist/components/ResumeDocument.d.ts.map +1 -0
- package/dist/components/ResumeViewer.d.ts +12 -0
- package/dist/components/ResumeViewer.d.ts.map +1 -0
- package/dist/entry-BTJjyohQ.js +154 -0
- package/dist/entry-BTJjyohQ.js.map +1 -0
- package/dist/entry.d.ts +22 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown-vendor-61sRGACd.js +10510 -0
- package/dist/markdown-vendor-61sRGACd.js.map +1 -0
- package/dist/rehype-vendor-C_Q-vwLE.js +5647 -0
- package/dist/rehype-vendor-C_Q-vwLE.js.map +1 -0
- package/dist/services/api.d.ts +62 -0
- package/dist/services/api.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/worker/application-extras.d.ts +56 -0
- package/dist/worker/application-extras.d.ts.map +1 -0
- package/dist/worker/blocks.d.ts +12 -0
- package/dist/worker/blocks.d.ts.map +1 -0
- package/dist/worker/builder.d.ts +33 -0
- package/dist/worker/builder.d.ts.map +1 -0
- package/dist/worker/constants.d.ts +39 -0
- package/dist/worker/constants.d.ts.map +1 -0
- package/dist/worker/contact.d.ts +34 -0
- package/dist/worker/contact.d.ts.map +1 -0
- package/dist/worker/cover-letter.d.ts +16 -0
- package/dist/worker/cover-letter.d.ts.map +1 -0
- package/dist/worker/index.d.ts +38 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/llm.d.ts +77 -0
- package/dist/worker/llm.d.ts.map +1 -0
- package/dist/worker/pdf.d.ts +46 -0
- package/dist/worker/pdf.d.ts.map +1 -0
- package/dist/worker/rate-limit.d.ts +10 -0
- package/dist/worker/rate-limit.d.ts.map +1 -0
- package/dist/worker/resume.d.ts +33 -0
- package/dist/worker/resume.d.ts.map +1 -0
- package/dist/worker/skeleton.d.ts +73 -0
- package/dist/worker/skeleton.d.ts.map +1 -0
- package/dist/worker/tailored-resume.d.ts +31 -0
- package/dist/worker/tailored-resume.d.ts.map +1 -0
- package/dist/worker/typography.d.ts +12 -0
- package/dist/worker/typography.d.ts.map +1 -0
- package/dist/worker/variants.d.ts +51 -0
- package/dist/worker/variants.d.ts.map +1 -0
- package/dist/worker.js +42508 -0
- package/package.json +43 -4
package/README.md
CHANGED
|
@@ -1,3 +1,135 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @wolffm/resume-bot
|
|
2
|
+
|
|
3
|
+
AI-powered conversational interface for interactive resume exploration — chat with an AI about my background, skills, and experience while browsing the resume itself.
|
|
4
|
+
|
|
5
|
+
**See it live:** [hadoku.me/resume](https://hadoku.me/resume)
|
|
6
|
+
|
|
7
|
+
This repo is the npm package that powers that widget. The rest of this README is for contributors working on the package itself.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Two-panel UI: a markdown resume viewer on the left, an AI chat interface on the right. The chat uses Groq's LLM to answer questions about the resume. Also exposes a worker API for generating tailored resumes and cover letters.
|
|
12
|
+
|
|
13
|
+
## Development
|
|
14
|
+
|
|
15
|
+
### Local Development Setup
|
|
16
|
+
|
|
17
|
+
This package includes a development environment for testing the component locally:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install dependencies
|
|
21
|
+
pnpm install
|
|
22
|
+
|
|
23
|
+
# Start dev server (opens http://localhost:5173)
|
|
24
|
+
pnpm dev
|
|
25
|
+
|
|
26
|
+
# Build for production
|
|
27
|
+
pnpm build
|
|
28
|
+
|
|
29
|
+
# Lint and format
|
|
30
|
+
pnpm lint:fix
|
|
31
|
+
pnpm format
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The dev server uses `index.html` which imports and mounts the component directly. You can pass different configurations via URL parameters:
|
|
35
|
+
|
|
36
|
+
**Example URLs:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Default configuration
|
|
40
|
+
http://localhost:5173
|
|
41
|
+
|
|
42
|
+
# Custom theme
|
|
43
|
+
http://localhost:5173?theme=ocean
|
|
44
|
+
|
|
45
|
+
# Admin user
|
|
46
|
+
http://localhost:5173?userType=admin&sessionId=test-123
|
|
47
|
+
|
|
48
|
+
# Full configuration
|
|
49
|
+
http://localhost:5173?theme=ocean&userType=admin&sessionId=dev-session
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Integration
|
|
53
|
+
|
|
54
|
+
This app is a child component of the [hadoku_site](https://github.com/WolffM/hadoku_site) parent application.
|
|
55
|
+
|
|
56
|
+
### Props
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
interface ResumeBotAppProps {
|
|
60
|
+
theme?: string // Optional: 'default', 'ocean', 'forest', etc.
|
|
61
|
+
apiBaseUrl: string // Required: API root path or URL (e.g., '/resume/api' or 'https://api.yourapp.com/api')
|
|
62
|
+
ownerName?: string // Optional: name shown in the chat welcome message (default: 'the candidate')
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Mounting
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { mount, unmount } from '@wolffm/resume-bot'
|
|
70
|
+
|
|
71
|
+
// Mount the app
|
|
72
|
+
const element = document.getElementById('app-root')
|
|
73
|
+
mount(element, {
|
|
74
|
+
theme: 'ocean', // optional
|
|
75
|
+
apiBaseUrl: '/resume/api' // required - path to the API root
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Unmount when done
|
|
79
|
+
unmount(element)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Important:** `apiBaseUrl` is the **API root**, matching the rest of the hadoku
|
|
83
|
+
ecosystem (`/oss/api`, `/jobplatform/api`). Endpoints are appended to it directly.
|
|
84
|
+
It can be either:
|
|
85
|
+
|
|
86
|
+
- A **path** (e.g., `/resume/api`) if the backend is hosted on the same domain
|
|
87
|
+
- A **full URL** (e.g., `https://api.yourapp.com/api`) if the backend is on a different domain
|
|
88
|
+
|
|
89
|
+
> **Breaking change in 2.0.0.** Through 1.x this prop was the _app_ root (`/resume`)
|
|
90
|
+
> and the package appended `/api/...` itself; 1.3.14 accepted both spellings. 2.0.0
|
|
91
|
+
> removes that shim — pass the API root. Passing `/resume` now requests `/resume/chat`
|
|
92
|
+
> and 404s. Migration is a one-line change at the mount site: `/resume` -> `/resume/api`.
|
|
93
|
+
> This does not affect the `./api` worker export — `createResumeHandler(basePath)` is
|
|
94
|
+
> unchanged.
|
|
95
|
+
|
|
96
|
+
The backend handles these endpoints (public unless noted; non-public routes are
|
|
97
|
+
gated in-worker via `@wolffm/worker-utils` edge-auth — see `worker/src/index.ts`):
|
|
98
|
+
|
|
99
|
+
- `${apiBaseUrl}/chat` - POST - Chat with the bot (public, rate-limited)
|
|
100
|
+
- `${apiBaseUrl}/resume` - GET - Fetch resume content, incl. `?v={slug}` variants (public)
|
|
101
|
+
- `${apiBaseUrl}/system-prompt` - GET - Fetch system prompt (admin/friend)
|
|
102
|
+
- `${apiBaseUrl}/tailored-resume` - POST - Per-job tailored resume (admin/friend/service)
|
|
103
|
+
- `${apiBaseUrl}/cover-letter` - POST - Per-job cover letter (admin/friend/service)
|
|
104
|
+
- `${apiBaseUrl}/variants` - POST/GET/DELETE - Variant management (admin/friend)
|
|
105
|
+
|
|
106
|
+
## Deployment
|
|
107
|
+
|
|
108
|
+
Pushes to `main` automatically:
|
|
109
|
+
|
|
110
|
+
1. Build and publish to GitHub Packages
|
|
111
|
+
2. Notify parent site to update
|
|
112
|
+
3. Parent pulls new version and redeploys
|
|
113
|
+
|
|
114
|
+
### Versioning
|
|
115
|
+
|
|
116
|
+
Version bumps are handled automatically through two mechanisms:
|
|
117
|
+
|
|
118
|
+
1. **Pre-commit hook** (primary): The `.husky/pre-commit` hook automatically bumps the version for every commit
|
|
119
|
+
2. **Workflow fallback**: The publish workflow checks if the current version already exists in the registry and bumps it if needed
|
|
120
|
+
|
|
121
|
+
This dual approach ensures versions are always incremented, even if commits bypass the pre-commit hook (e.g., web UI edits, `--no-verify` commits).
|
|
122
|
+
|
|
123
|
+
Version bumping follows this pattern:
|
|
124
|
+
|
|
125
|
+
- Patch version increments on each commit (1.1.8 → 1.1.9)
|
|
126
|
+
- At patch 20, rolls over to next minor (1.1.20 → 1.2.0)
|
|
127
|
+
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
This package has two exports:
|
|
131
|
+
|
|
132
|
+
- **UI component** (`.` export): `mount(el, props)` / `unmount(el)` — React app from `src/entry.tsx`
|
|
133
|
+
- **Worker API** (`./api` export): `createResumeHandler(basePath, options)` — Hono router from `worker/src/index.ts` (`options.ownerName` sets the chat owner)
|
|
134
|
+
|
|
135
|
+
Both are built and published together as `@wolffm/resume-bot`.
|
package/dist/App.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ResumeBotAppProps } from './entry';
|
|
2
|
+
/**
|
|
3
|
+
* Provider boundary. Theme state belongs to the platform (@wolffm/themes),
|
|
4
|
+
* not to this app — the local hooks/useTheme.ts, prefs/themePrefs.ts and
|
|
5
|
+
* app/themeConfig.tsx copies are gone. AppHeader renders the shared picker
|
|
6
|
+
* from this context, so nothing below passes one.
|
|
7
|
+
*/
|
|
8
|
+
export default function App(props: ResumeBotAppProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=App.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAMhD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,iBAAiB,2CAOnD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsxs as o, jsx as a } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as E, useState as u, useRef as b, useEffect as C, useImperativeHandle as x } from "react";
|
|
3
|
+
import { logger as M } from "@wolffm/logger/client";
|
|
4
|
+
import { s as y } from "./entry-BTJjyohQ.js";
|
|
5
|
+
import { M as T, a as j } from "./markdown-vendor-61sRGACd.js";
|
|
6
|
+
import { r as B } from "./rehype-vendor-C_Q-vwLE.js";
|
|
7
|
+
const V = E(
|
|
8
|
+
({ ownerName: k = "the candidate" }, w) => {
|
|
9
|
+
const [r, c] = u([]), [m, g] = u(""), [i, h] = u(!1), [_, l] = u(null), v = b(null), N = b(null);
|
|
10
|
+
C(() => {
|
|
11
|
+
r.length > 0 && v.current?.scrollIntoView({ behavior: "smooth" });
|
|
12
|
+
}, [r]), C(() => {
|
|
13
|
+
i || N.current?.focus();
|
|
14
|
+
}, [i]), x(w, () => ({
|
|
15
|
+
askAbout: (e) => {
|
|
16
|
+
const n = {
|
|
17
|
+
role: "user",
|
|
18
|
+
content: `Tell me about: ${e}`
|
|
19
|
+
};
|
|
20
|
+
c((s) => [...s, n]), l(null), h(!0), (async () => {
|
|
21
|
+
try {
|
|
22
|
+
const s = [...r, n], p = {
|
|
23
|
+
role: "assistant",
|
|
24
|
+
content: (await y(s)).message
|
|
25
|
+
};
|
|
26
|
+
c((S) => [...S, p]);
|
|
27
|
+
} catch (s) {
|
|
28
|
+
const d = s instanceof Error ? s.message : "Failed to send message";
|
|
29
|
+
l(d), c((p) => p.slice(0, -1));
|
|
30
|
+
} finally {
|
|
31
|
+
h(!1);
|
|
32
|
+
}
|
|
33
|
+
})().catch(
|
|
34
|
+
(s) => M.error("[ChatInterface] Error sending message", {
|
|
35
|
+
error: s?.message ?? String(s)
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
async function I(e) {
|
|
41
|
+
if (e.preventDefault(), !m.trim() || i) return;
|
|
42
|
+
const t = {
|
|
43
|
+
role: "user",
|
|
44
|
+
content: m.trim()
|
|
45
|
+
};
|
|
46
|
+
c((n) => [...n, t]), g(""), l(null), h(!0);
|
|
47
|
+
try {
|
|
48
|
+
const n = [...r, t], s = {
|
|
49
|
+
role: "assistant",
|
|
50
|
+
content: (await y(n)).message
|
|
51
|
+
};
|
|
52
|
+
c((d) => [...d, s]);
|
|
53
|
+
} catch (n) {
|
|
54
|
+
const f = n instanceof Error ? n.message : "Failed to send message";
|
|
55
|
+
l(f), c((s) => s.slice(0, -1)), g(t.content);
|
|
56
|
+
} finally {
|
|
57
|
+
h(!1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function R() {
|
|
61
|
+
c([]), l(null);
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ o("div", { className: "chat-interface", children: [
|
|
64
|
+
/* @__PURE__ */ o("div", { className: "chat-interface__header", children: [
|
|
65
|
+
/* @__PURE__ */ a("h3", { children: "Chat with Resume Bot" }),
|
|
66
|
+
r.length > 0 && /* @__PURE__ */ a("button", { onClick: R, className: "chat-interface__clear-button", children: "Clear Chat" })
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ o("div", { className: "chat-interface__messages", children: [
|
|
69
|
+
r.length === 0 && /* @__PURE__ */ a("div", { className: "chat-interface__welcome", children: /* @__PURE__ */ o("p", { children: [
|
|
70
|
+
"Hi! I can answer questions about ",
|
|
71
|
+
k,
|
|
72
|
+
"'s resume. Try asking about their experience, skills, or projects!"
|
|
73
|
+
] }) }),
|
|
74
|
+
r.map((e, t) => /* @__PURE__ */ o("div", { className: `chat-message chat-message--${e.role}`, children: [
|
|
75
|
+
/* @__PURE__ */ a("div", { className: "chat-message__role", children: e.role === "user" ? "You" : "Resume Bot" }),
|
|
76
|
+
/* @__PURE__ */ a("div", { className: "chat-message__content", children: e.role === "assistant" ? /* @__PURE__ */ a(T, { remarkPlugins: [j], rehypePlugins: [B], children: e.content }) : e.content })
|
|
77
|
+
] }, t)),
|
|
78
|
+
i && /* @__PURE__ */ o("div", { className: "chat-message chat-message--assistant chat-message--loading", children: [
|
|
79
|
+
/* @__PURE__ */ a("div", { className: "chat-message__role", children: "Resume Bot" }),
|
|
80
|
+
/* @__PURE__ */ a("div", { className: "chat-message__content", children: "Thinking..." })
|
|
81
|
+
] }),
|
|
82
|
+
/* @__PURE__ */ a("div", { ref: v })
|
|
83
|
+
] }),
|
|
84
|
+
_ && /* @__PURE__ */ a("div", { className: "chat-interface__error", children: /* @__PURE__ */ a("p", { children: _ }) }),
|
|
85
|
+
/* @__PURE__ */ o(
|
|
86
|
+
"form",
|
|
87
|
+
{
|
|
88
|
+
onSubmit: (e) => {
|
|
89
|
+
I(e).catch(
|
|
90
|
+
(t) => M.error("[ChatInterface] Error sending message", {
|
|
91
|
+
error: t?.message ?? String(t)
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
className: "chat-interface__input-form",
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ a(
|
|
98
|
+
"input",
|
|
99
|
+
{
|
|
100
|
+
ref: N,
|
|
101
|
+
type: "text",
|
|
102
|
+
value: m,
|
|
103
|
+
onChange: (e) => g(e.target.value),
|
|
104
|
+
placeholder: "Ask me about the resume...",
|
|
105
|
+
disabled: i,
|
|
106
|
+
className: "chat-interface__input"
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
/* @__PURE__ */ a(
|
|
110
|
+
"button",
|
|
111
|
+
{
|
|
112
|
+
type: "submit",
|
|
113
|
+
disabled: i || !m.trim(),
|
|
114
|
+
className: "chat-interface__send-button",
|
|
115
|
+
children: "Send"
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
] });
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
V.displayName = "ChatInterface";
|
|
125
|
+
export {
|
|
126
|
+
V as default
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=ChatInterface-mHtxUGpp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatInterface-mHtxUGpp.js","sources":["../src/components/ChatInterface.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useImperativeHandle, forwardRef } from 'react'\r\nimport ReactMarkdown from 'react-markdown'\r\nimport remarkGfm from 'remark-gfm'\r\nimport rehypeRaw from 'rehype-raw'\r\nimport { logger } from '@wolffm/logger/client'\r\nimport { sendChatMessage, type ChatMessage } from '../services/api'\r\n\r\nexport interface ChatInterfaceRef {\r\n askAbout: (text: string) => void\r\n}\r\n\r\ninterface ChatInterfaceProps {\r\n ownerName?: string\r\n}\r\n\r\nconst ChatInterface = forwardRef<ChatInterfaceRef, ChatInterfaceProps>(\r\n ({ ownerName = 'the candidate' }, ref) => {\r\n const [messages, setMessages] = useState<ChatMessage[]>([])\r\n const [inputValue, setInputValue] = useState('')\r\n const [isLoading, setIsLoading] = useState(false)\r\n const [error, setError] = useState<string | null>(null)\r\n const messagesEndRef = useRef<HTMLDivElement>(null)\r\n const inputRef = useRef<HTMLInputElement>(null)\r\n\r\n // Scroll to bottom when messages change (but not on initial render)\r\n useEffect(() => {\r\n if (messages.length > 0) {\r\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })\r\n }\r\n }, [messages])\r\n\r\n // Re-focus input after loading completes\r\n useEffect(() => {\r\n if (!isLoading) {\r\n inputRef.current?.focus()\r\n }\r\n }, [isLoading])\r\n\r\n // Expose method to parent component\r\n useImperativeHandle(ref, () => ({\r\n askAbout: (text: string) => {\r\n // Create the message content\r\n const messageContent = `Tell me about: ${text}`\r\n\r\n // Create user message\r\n const userMessage: ChatMessage = {\r\n role: 'user',\r\n content: messageContent\r\n }\r\n\r\n // Add user message to chat\r\n setMessages(prev => [...prev, userMessage])\r\n setError(null)\r\n setIsLoading(true)\r\n\r\n // Send the message automatically\r\n const sendMessage = async () => {\r\n try {\r\n const messagesToSend: ChatMessage[] = [...messages, userMessage]\r\n\r\n const response = await sendChatMessage(messagesToSend)\r\n\r\n const assistantMessage: ChatMessage = {\r\n role: 'assistant',\r\n content: response.message\r\n }\r\n\r\n setMessages(prev => [...prev, assistantMessage])\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to send message'\r\n setError(errorMessage)\r\n setMessages(prev => prev.slice(0, -1))\r\n } finally {\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n sendMessage().catch(err =>\r\n logger.error('[ChatInterface] Error sending message', {\r\n error: (err as Error)?.message ?? String(err)\r\n })\r\n )\r\n }\r\n }))\r\n\r\n async function handleSendMessage(e: React.FormEvent) {\r\n e.preventDefault()\r\n\r\n if (!inputValue.trim() || isLoading) return\r\n\r\n const userMessage: ChatMessage = {\r\n role: 'user',\r\n content: inputValue.trim()\r\n }\r\n\r\n // Add user message to chat\r\n setMessages(prev => [...prev, userMessage])\r\n setInputValue('')\r\n setError(null)\r\n setIsLoading(true)\r\n\r\n try {\r\n const messagesToSend: ChatMessage[] = [...messages, userMessage]\r\n\r\n // Send all messages to backend\r\n const response = await sendChatMessage(messagesToSend)\r\n\r\n // Add assistant response to chat\r\n const assistantMessage: ChatMessage = {\r\n role: 'assistant',\r\n content: response.message\r\n }\r\n\r\n setMessages(prev => [...prev, assistantMessage])\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to send message'\r\n setError(errorMessage)\r\n\r\n // Remove the failed user message\r\n setMessages(prev => prev.slice(0, -1))\r\n // Restore the input\r\n setInputValue(userMessage.content)\r\n } finally {\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n function handleClearChat() {\r\n setMessages([])\r\n setError(null)\r\n }\r\n\r\n return (\r\n <div className=\"chat-interface\">\r\n <div className=\"chat-interface__header\">\r\n <h3>Chat with Resume Bot</h3>\r\n {messages.length > 0 && (\r\n <button onClick={handleClearChat} className=\"chat-interface__clear-button\">\r\n Clear Chat\r\n </button>\r\n )}\r\n </div>\r\n\r\n <div className=\"chat-interface__messages\">\r\n {messages.length === 0 && (\r\n <div className=\"chat-interface__welcome\">\r\n <p>\r\n Hi! I can answer questions about {ownerName}'s resume. Try asking about their\r\n experience, skills, or projects!\r\n </p>\r\n </div>\r\n )}\r\n\r\n {messages.map((message, index) => (\r\n <div key={index} className={`chat-message chat-message--${message.role}`}>\r\n <div className=\"chat-message__role\">\r\n {message.role === 'user' ? 'You' : 'Resume Bot'}\r\n </div>\r\n <div className=\"chat-message__content\">\r\n {message.role === 'assistant' ? (\r\n <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>\r\n {message.content}\r\n </ReactMarkdown>\r\n ) : (\r\n message.content\r\n )}\r\n </div>\r\n </div>\r\n ))}\r\n\r\n {isLoading && (\r\n <div className=\"chat-message chat-message--assistant chat-message--loading\">\r\n <div className=\"chat-message__role\">Resume Bot</div>\r\n <div className=\"chat-message__content\">Thinking...</div>\r\n </div>\r\n )}\r\n\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"chat-interface__error\">\r\n <p>{error}</p>\r\n </div>\r\n )}\r\n\r\n <form\r\n onSubmit={e => {\r\n handleSendMessage(e).catch(err =>\r\n logger.error('[ChatInterface] Error sending message', {\r\n error: (err as Error)?.message ?? String(err)\r\n })\r\n )\r\n }}\r\n className=\"chat-interface__input-form\"\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={inputValue}\r\n onChange={e => setInputValue(e.target.value)}\r\n placeholder=\"Ask me about the resume...\"\r\n disabled={isLoading}\r\n className=\"chat-interface__input\"\r\n />\r\n <button\r\n type=\"submit\"\r\n disabled={isLoading || !inputValue.trim()}\r\n className=\"chat-interface__send-button\"\r\n >\r\n Send\r\n </button>\r\n </form>\r\n </div>\r\n )\r\n }\r\n)\r\n\r\nChatInterface.displayName = 'ChatInterface'\r\n\r\nexport default ChatInterface\r\n"],"names":["ChatInterface","forwardRef","ownerName","ref","messages","setMessages","useState","inputValue","setInputValue","isLoading","setIsLoading","error","setError","messagesEndRef","useRef","inputRef","useEffect","useImperativeHandle","text","userMessage","prev","messagesToSend","assistantMessage","sendChatMessage","err","errorMessage","logger","handleSendMessage","handleClearChat","jsxs","jsx","message","index","ReactMarkdown","remarkGfm","rehypeRaw"],"mappings":"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"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { jsx as n, jsxs as i, Fragment as N } from "react/jsx-runtime";
|
|
2
|
+
import z, { Children as $, isValidElement as U, useState as f, useEffect as j } from "react";
|
|
3
|
+
import { logger as A } from "@wolffm/logger/client";
|
|
4
|
+
import { f as G, r as I } from "./entry-BTJjyohQ.js";
|
|
5
|
+
import { M as H, a as J } from "./markdown-vendor-61sRGACd.js";
|
|
6
|
+
const K = "<!-- page-break -->", X = /^#[^#]\s*(.+?)\s*$/;
|
|
7
|
+
function Y(e) {
|
|
8
|
+
return e.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9
|
+
}
|
|
10
|
+
function q(e) {
|
|
11
|
+
const t = [];
|
|
12
|
+
let r = {
|
|
13
|
+
id: "masthead",
|
|
14
|
+
heading: null,
|
|
15
|
+
body: []
|
|
16
|
+
};
|
|
17
|
+
const s = () => {
|
|
18
|
+
(r.heading !== null || r.body.join("").trim() !== "") && t.push({ id: r.id, heading: r.heading, body: r.body.join(`
|
|
19
|
+
`) });
|
|
20
|
+
};
|
|
21
|
+
for (const o of e.split(`
|
|
22
|
+
`)) {
|
|
23
|
+
const c = X.exec(o);
|
|
24
|
+
c ? (s(), r = { id: Y(c[1]), heading: c[1], body: [] }) : r.body.push(o);
|
|
25
|
+
}
|
|
26
|
+
return s(), t;
|
|
27
|
+
}
|
|
28
|
+
function Q(e) {
|
|
29
|
+
return e.replace(/^(\*\*[^*]+\*\*)\s*[-–—]\s+/gm, "$1 ").replace(/\s*·\s*(?=\[)/g, " ");
|
|
30
|
+
}
|
|
31
|
+
function W(e) {
|
|
32
|
+
return e.replace(/^(\*\*[^*]+?)\s*:\*\*/gm, "$1**");
|
|
33
|
+
}
|
|
34
|
+
const M = (e) => e.type !== "text" || (e.value ?? "").trim() !== "";
|
|
35
|
+
function Z(e) {
|
|
36
|
+
const t = e?.children?.find(M);
|
|
37
|
+
return t?.type === "element" ? t : void 0;
|
|
38
|
+
}
|
|
39
|
+
function ee(e) {
|
|
40
|
+
return (e?.children ?? []).filter(M).length;
|
|
41
|
+
}
|
|
42
|
+
function te(e) {
|
|
43
|
+
const t = $.toArray(e);
|
|
44
|
+
let r = t.length;
|
|
45
|
+
for (let s = t.length - 1; s >= 0; s--) {
|
|
46
|
+
const o = t[s];
|
|
47
|
+
if (!(typeof o == "string" && o.trim() === "")) {
|
|
48
|
+
if (U(o) && o.type === "a") {
|
|
49
|
+
r = s;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [t.slice(0, r), t.slice(r)];
|
|
56
|
+
}
|
|
57
|
+
function ne(e) {
|
|
58
|
+
const t = [[]];
|
|
59
|
+
for (const r of $.toArray(e))
|
|
60
|
+
U(r) && r.type === "br" ? t.push([]) : t[t.length - 1].push(r);
|
|
61
|
+
return t;
|
|
62
|
+
}
|
|
63
|
+
function re() {
|
|
64
|
+
let e = !1;
|
|
65
|
+
return {
|
|
66
|
+
// `node` is destructured off so it never reaches the DOM — react-markdown
|
|
67
|
+
// hands it to every override, and React warns on an unknown `node` attr.
|
|
68
|
+
p({ node: t, children: r, ...s }) {
|
|
69
|
+
const [o, ...c] = ne(r);
|
|
70
|
+
return e || c.length === 0 ? /* @__PURE__ */ n("p", { ...s, children: r }) : (e = !0, /* @__PURE__ */ i(N, { children: [
|
|
71
|
+
/* @__PURE__ */ n("h1", { className: "resume-doc__name", children: o }),
|
|
72
|
+
c.map((a, m) => /* @__PURE__ */ n("p", { className: "resume-doc__contact", children: a }, m))
|
|
73
|
+
] }));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function se(e) {
|
|
78
|
+
let t = !1;
|
|
79
|
+
return {
|
|
80
|
+
p({ node: r, children: s, ...o }) {
|
|
81
|
+
const c = Z(r);
|
|
82
|
+
if (c?.tagName === "strong") {
|
|
83
|
+
const [a, m] = te(s);
|
|
84
|
+
return /* @__PURE__ */ i("p", { className: "resume-doc__entry", ...o, children: [
|
|
85
|
+
a,
|
|
86
|
+
m.length > 0 && /* @__PURE__ */ n("span", { className: "resume-doc__links", children: m })
|
|
87
|
+
] });
|
|
88
|
+
}
|
|
89
|
+
return c?.tagName === "em" && ee(r) === 1 ? /* @__PURE__ */ n("p", { className: "resume-doc__meta", ...o, children: s }) : e === "projects" && !t ? (t = !0, /* @__PURE__ */ n("p", { className: "resume-doc__lede", ...o, children: s })) : /* @__PURE__ */ n("p", { ...o, children: s });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const oe = {
|
|
94
|
+
projects: Q,
|
|
95
|
+
"technical-skills": W
|
|
96
|
+
}, ce = (e) => e;
|
|
97
|
+
function le({
|
|
98
|
+
id: e,
|
|
99
|
+
heading: t,
|
|
100
|
+
body: r,
|
|
101
|
+
masthead: s
|
|
102
|
+
}) {
|
|
103
|
+
const o = (oe[e] ?? ce)(r);
|
|
104
|
+
return /* @__PURE__ */ i("section", { className: `resume-doc__section resume-doc__section--${e}`, children: [
|
|
105
|
+
t !== null && /* @__PURE__ */ n("h2", { className: "resume-doc__section-title", children: t }),
|
|
106
|
+
/* @__PURE__ */ n(
|
|
107
|
+
H,
|
|
108
|
+
{
|
|
109
|
+
remarkPlugins: [J],
|
|
110
|
+
components: s ? re() : se(e),
|
|
111
|
+
children: o
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
function b({
|
|
117
|
+
content: e,
|
|
118
|
+
variant: t = "resume"
|
|
119
|
+
}) {
|
|
120
|
+
const r = e.split(K);
|
|
121
|
+
return /* @__PURE__ */ n("div", { className: `resume-doc resume-doc--${t}`, children: r.map((s, o) => /* @__PURE__ */ i(z.Fragment, { children: [
|
|
122
|
+
o > 0 && /* @__PURE__ */ n("div", { className: "resume-viewer__pagebreak" }),
|
|
123
|
+
/* @__PURE__ */ n("article", { className: "resume-doc__sheet", children: q(s).map((c, a) => /* @__PURE__ */ n(
|
|
124
|
+
le,
|
|
125
|
+
{
|
|
126
|
+
...c,
|
|
127
|
+
masthead: t === "resume" && c.heading === null
|
|
128
|
+
},
|
|
129
|
+
a
|
|
130
|
+
)) })
|
|
131
|
+
] }, o)) });
|
|
132
|
+
}
|
|
133
|
+
const ie = "/contact/";
|
|
134
|
+
function ae(e) {
|
|
135
|
+
const t = (e ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
136
|
+
return t ? `${t}-resume` : "resume";
|
|
137
|
+
}
|
|
138
|
+
function ge({
|
|
139
|
+
onAskAbout: e,
|
|
140
|
+
ownerName: t,
|
|
141
|
+
contactUrl: r = ie
|
|
142
|
+
}) {
|
|
143
|
+
const [s, o] = f(null), [c, a] = f("resume"), [m, k] = f(!0), [y, C] = f(null), [g, w] = f(
|
|
144
|
+
null
|
|
145
|
+
);
|
|
146
|
+
j(() => {
|
|
147
|
+
R().catch(
|
|
148
|
+
(l) => A.error("[ResumeViewer] Error loading resume", {
|
|
149
|
+
error: l?.message ?? String(l)
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
}, []);
|
|
153
|
+
async function R() {
|
|
154
|
+
try {
|
|
155
|
+
k(!0), C(null);
|
|
156
|
+
const l = new URLSearchParams(window.location.search).get("v") ?? void 0, p = await G(l);
|
|
157
|
+
o(p);
|
|
158
|
+
} catch (l) {
|
|
159
|
+
C(l instanceof Error ? l.message : "Failed to load resume");
|
|
160
|
+
} finally {
|
|
161
|
+
k(!1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
j(() => {
|
|
165
|
+
const l = () => w(null);
|
|
166
|
+
return document.addEventListener("click", l), () => document.removeEventListener("click", l);
|
|
167
|
+
}, []);
|
|
168
|
+
const P = (l) => {
|
|
169
|
+
l.preventDefault();
|
|
170
|
+
const _ = window.getSelection()?.toString().trim();
|
|
171
|
+
_ && _.length > 0 && w({
|
|
172
|
+
x: l.clientX,
|
|
173
|
+
y: l.clientY,
|
|
174
|
+
text: _
|
|
175
|
+
});
|
|
176
|
+
}, T = () => {
|
|
177
|
+
g && (e(g.text), w(null));
|
|
178
|
+
}, x = (l, p, _) => {
|
|
179
|
+
const S = URL.createObjectURL(new Blob([l], { type: p })), u = document.createElement("a");
|
|
180
|
+
u.href = S, u.download = _, u.rel = "noopener", u.style.display = "none", document.body.appendChild(u), u.click(), setTimeout(() => {
|
|
181
|
+
u.remove(), URL.revokeObjectURL(S);
|
|
182
|
+
}, 0);
|
|
183
|
+
}, d = s?.content ?? "", h = s?.coverLetter ?? null, v = h !== null && h.length > 0, D = c === "cover" && v ? h : d, E = s?.variant ?? new URLSearchParams(window.location.search).get("v"), L = ae(t), F = v ? `${d}
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# Cover Letter
|
|
188
|
+
|
|
189
|
+
${h}` : d, B = () => x(v ? F : d, "text/markdown", `${L}.md`), O = () => x(
|
|
190
|
+
JSON.stringify(
|
|
191
|
+
{
|
|
192
|
+
content: d,
|
|
193
|
+
cover_letter: h ?? void 0,
|
|
194
|
+
variant: E ?? void 0,
|
|
195
|
+
label: s?.label ?? void 0,
|
|
196
|
+
company: s?.company ?? void 0,
|
|
197
|
+
job_title: s?.jobTitle ?? void 0,
|
|
198
|
+
format: "markdown"
|
|
199
|
+
},
|
|
200
|
+
null,
|
|
201
|
+
2
|
|
202
|
+
),
|
|
203
|
+
"application/json",
|
|
204
|
+
`${L}.json`
|
|
205
|
+
), V = I(E ?? void 0);
|
|
206
|
+
return m ? /* @__PURE__ */ n("div", { className: "resume-viewer resume-viewer--loading", children: /* @__PURE__ */ n("p", { children: "Loading resume..." }) }) : y ? /* @__PURE__ */ i("div", { className: "resume-viewer resume-viewer--error", children: [
|
|
207
|
+
/* @__PURE__ */ i("p", { className: "resume-viewer__error-message", children: [
|
|
208
|
+
"Error: ",
|
|
209
|
+
y
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ n(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
onClick: () => {
|
|
215
|
+
R().catch(
|
|
216
|
+
(l) => A.error("[ResumeViewer] Error loading resume", {
|
|
217
|
+
error: l?.message ?? String(l)
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
className: "resume-viewer__retry-button",
|
|
222
|
+
children: "Retry"
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] }) : /* @__PURE__ */ i(N, { children: [
|
|
226
|
+
/* @__PURE__ */ i("div", { className: "resume-viewer", onContextMenu: P, children: [
|
|
227
|
+
/* @__PURE__ */ i("div", { className: "resume-viewer__toolbar", children: [
|
|
228
|
+
v && /* @__PURE__ */ i("div", { className: "resume-viewer__toggle", role: "tablist", "aria-label": "Packet document", children: [
|
|
229
|
+
/* @__PURE__ */ n(
|
|
230
|
+
"button",
|
|
231
|
+
{
|
|
232
|
+
type: "button",
|
|
233
|
+
role: "tab",
|
|
234
|
+
"aria-selected": c === "resume",
|
|
235
|
+
className: c === "resume" ? "resume-viewer__toggle-btn resume-viewer__toggle-btn--active" : "resume-viewer__toggle-btn",
|
|
236
|
+
onClick: () => a("resume"),
|
|
237
|
+
children: "Résumé"
|
|
238
|
+
}
|
|
239
|
+
),
|
|
240
|
+
/* @__PURE__ */ n(
|
|
241
|
+
"button",
|
|
242
|
+
{
|
|
243
|
+
type: "button",
|
|
244
|
+
role: "tab",
|
|
245
|
+
"aria-selected": c === "cover",
|
|
246
|
+
className: c === "cover" ? "resume-viewer__toggle-btn resume-viewer__toggle-btn--active" : "resume-viewer__toggle-btn",
|
|
247
|
+
onClick: () => a("cover"),
|
|
248
|
+
children: "Cover letter"
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
] }),
|
|
252
|
+
/* @__PURE__ */ i("div", { className: "resume-viewer__downloads", children: [
|
|
253
|
+
/* @__PURE__ */ n("span", { className: "resume-viewer__downloads-label", children: "Download" }),
|
|
254
|
+
/* @__PURE__ */ n("a", { className: "resume-viewer__download-button", href: V, download: !0, children: ".pdf" }),
|
|
255
|
+
/* @__PURE__ */ n("button", { className: "resume-viewer__download-button", onClick: B, children: ".md" }),
|
|
256
|
+
/* @__PURE__ */ n("button", { className: "resume-viewer__download-button", onClick: O, children: ".json" })
|
|
257
|
+
] }),
|
|
258
|
+
/* @__PURE__ */ n("a", { className: "resume-viewer__schedule-button", href: r, children: "Schedule a Meeting" })
|
|
259
|
+
] }),
|
|
260
|
+
/* @__PURE__ */ i("div", { className: "resume-viewer__scroll", children: [
|
|
261
|
+
/* @__PURE__ */ n("div", { className: "resume-viewer__screen", children: /* @__PURE__ */ n(
|
|
262
|
+
b,
|
|
263
|
+
{
|
|
264
|
+
content: D,
|
|
265
|
+
variant: c === "cover" && v ? "letter" : "resume"
|
|
266
|
+
}
|
|
267
|
+
) }),
|
|
268
|
+
/* @__PURE__ */ i("div", { className: "resume-viewer__print", children: [
|
|
269
|
+
/* @__PURE__ */ n(b, { content: d }),
|
|
270
|
+
v && /* @__PURE__ */ i(N, { children: [
|
|
271
|
+
/* @__PURE__ */ n("div", { className: "resume-viewer__pagebreak" }),
|
|
272
|
+
/* @__PURE__ */ n(b, { content: h, variant: "letter" })
|
|
273
|
+
] })
|
|
274
|
+
] })
|
|
275
|
+
] })
|
|
276
|
+
] }),
|
|
277
|
+
g && /* @__PURE__ */ n(
|
|
278
|
+
"div",
|
|
279
|
+
{
|
|
280
|
+
className: "context-menu",
|
|
281
|
+
style: {
|
|
282
|
+
position: "fixed",
|
|
283
|
+
top: g.y,
|
|
284
|
+
left: g.x,
|
|
285
|
+
zIndex: 1e3
|
|
286
|
+
},
|
|
287
|
+
children: /* @__PURE__ */ n("button", { className: "context-menu__item", onClick: T, children: "Ask about this" })
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
] });
|
|
291
|
+
}
|
|
292
|
+
export {
|
|
293
|
+
ge as default,
|
|
294
|
+
ae as resumeFilenameBase
|
|
295
|
+
};
|
|
296
|
+
//# sourceMappingURL=ResumeViewer-C-uxZ-7E.js.map
|