Skip to content

Commit e2fc31e

Browse files
dachi-devclaude
andcommitted
Move Gemini API key to Cloudflare Worker proxy
- API key is now stored as a Cloudflare secret, not in client code - Worker proxies requests to Gemini with CORS for GitHub Pages - Frontend calls worker endpoint instead of Gemini directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5c435e7 commit e2fc31e

3 files changed

Lines changed: 149 additions & 46 deletions

File tree

src/api.js

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,18 @@
1-
import resumeData from "./resumeData";
2-
3-
const API_KEY = "AIzaSyAQXjL6FqW9QiOxhVm7pbhuDMAG-FtuoyU";
4-
5-
const SYSTEM_PROMPT = `You are an AI assistant embedded in Tommy Ho's personal resume website.
6-
Tommy also goes by "Dachi" — both names refer to the same person.
7-
Your ONLY purpose is to answer questions about Tommy's professional background,
8-
skills, work experience, education, and career based on the resume data provided below.
9-
10-
STRICT RULES:
11-
- ONLY discuss topics directly related to Tommy's professional resume, career, and experience.
12-
- If someone asks about anything unrelated to Tommy's professional life (personal questions, general knowledge, coding help, opinions, politics, etc.), politely decline and redirect them to ask about Tommy's resume or experience instead.
13-
- Do NOT make up or infer information not present in the resume data.
14-
- If you don't know something specific about Tommy, say so honestly.
15-
16-
Be friendly, concise, and professional. Keep responses short (2-4 sentences) unless the visitor asks for more detail.
17-
18-
- When someone asks to see the resume, asks for a summary/blurb, or says "let me see your resume", provide a brief professional blurb summarizing Tommy as a Sr. Sales Engineer with 7+ years of experience, then mention they can download the full resume PDF using the link in the header above, or at: /Tommy_Ho_Resume.pdf
19-
20-
RESUME DATA:
21-
${resumeData}`;
1+
const API_URL =
2+
import.meta.env.VITE_API_URL || "https://dachidev-api.tommyhojobs.workers.dev";
223

234
export async function sendMessage(messages) {
24-
const contents = messages.map((m) => ({
25-
role: m.role === "assistant" ? "model" : "user",
26-
parts: [{ text: m.content }],
27-
}));
28-
29-
const response = await fetch(
30-
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${API_KEY}`,
31-
{
32-
method: "POST",
33-
headers: { "Content-Type": "application/json" },
34-
body: JSON.stringify({
35-
system_instruction: {
36-
parts: [{ text: SYSTEM_PROMPT }],
37-
},
38-
contents,
39-
generationConfig: {
40-
maxOutputTokens: 1024,
41-
},
42-
}),
43-
}
44-
);
5+
const response = await fetch(`${API_URL}/chat`, {
6+
method: "POST",
7+
headers: { "Content-Type": "application/json" },
8+
body: JSON.stringify({ messages }),
9+
});
4510

4611
if (!response.ok) {
4712
const err = await response.json().catch(() => ({}));
48-
throw new Error(
49-
err?.error?.message || `API error: ${response.status}`
50-
);
13+
throw new Error(err?.error || `API error: ${response.status}`);
5114
}
5215

5316
const data = await response.json();
54-
return data.candidates[0].content.parts[0].text;
17+
return data.text;
5518
}

worker/index.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const RESUME_DATA = `
2+
NAME: Tommy Ho
3+
LOCATION: Houston, Texas USA
4+
CITIZENSHIP: US Citizen
5+
PHONE: (337)-381-6846
6+
EMAIL: TommyHoJobs@gmail.com
7+
8+
WORK EXPERIENCE:
9+
10+
- Sales Engineer at Socket (May 2025 — Nov 2025)
11+
• Partnered with 4 AEs to drive enterprise and commercial deals in developer tooling/code security, leading cross-functional discovery, tailored demos, PoCs/POVs, and security reviews.
12+
• Drove 3x revenue growth and 5x close-rate improvement, reducing deal losses by 80% through tighter technical alignment and stronger POV execution.
13+
• Closed multiple strategic accounts within the first 90 days by translating security requirements into an implementation plan that fit customer SDLC constraints.
14+
15+
- Sales Engineer at Nodies (Dec 2024 — May 2025)
16+
• Collaborated with 4 AEs to drive $10M+ revenue, owning discovery, demos, PoCs/POVs, and post-sale technical enablement across the customer lifecycle.
17+
• Established 30+ strategic partnerships, expanding distribution channels and accelerating pipeline growth.
18+
• Designed reference architectures and shipped customer-driven code and integrations to support production deployments.
19+
20+
- Solutions Architect at Amazon Web Services (Jan 2022 — Dec 2024)
21+
• Partnered with account teams to run customer-facing technical engagements owning discovery, architecture reviews, and executive-ready readouts aligning business goals with scalable cloud designs.
22+
• Led whiteboarding sessions, demos, and hands-on workshops for engineering teams to accelerate adoption and unblock implementation.
23+
• Advised on SDLC and CI/CD modernization (build/release workflows, deployment patterns, environment strategy), helping teams ship faster with stronger operational guardrails.
24+
25+
- Technical Sales Consultant at CGI Federal (Feb 2021 — Jan 2022)
26+
• Led technical discovery with stakeholders and wrote concise summaries of constraints (security/compliance, hosting, data, integrations) to shape scope, milestones, and risks.
27+
• Built and delivered customer-facing demos/technical briefings, answering deep technical questions and aligning solution tradeoffs to mission outcomes.
28+
• Coordinated inputs across engineering, security, and program teams to produce proposal technical volumes and ensure consistency between proposed architecture, staffing, and delivery assumptions.
29+
30+
- Sales Engineer at Pocket Network (May 2018 — Feb 2021)
31+
• Led technical GTM and presales for an experimental prototype (pre-product).
32+
• Secured $300K in pre-release commitments, 12 months before GA launch.
33+
34+
SKILLS:
35+
Pre-sales execution (discovery, solution design, demos, POV/PoCs, security reviews, workshops, exec readouts) • AWS • SDLC/CI/CD (GitHub Actions, GitLab, Jenkins, CircleCI, Buildkite) • Linux/networking • DevSecOps/AppSec (SAST/SCA, vuln mgmt, SBOM/secrets concepts) • Observability • Python/Bash • APIs (REST, webhooks, OAuth, JSON/YAML)
36+
37+
EDUCATION:
38+
Bachelor of Science in Computer Science, Western Governors University, GPA: 3.77, July 2020
39+
40+
LINKS:
41+
- LinkedIn: https://www.linkedin.com/in/tommy-ho-se/
42+
- GitHub: https://github.com/dachi-dev
43+
- Resume PDF: Available for download on the website
44+
`;
45+
46+
const SYSTEM_PROMPT = `You are an AI assistant embedded in Tommy Ho's personal resume website.
47+
Tommy also goes by "Dachi" — both names refer to the same person.
48+
Your ONLY purpose is to answer questions about Tommy's professional background,
49+
skills, work experience, education, and career based on the resume data provided below.
50+
51+
STRICT RULES:
52+
- ONLY discuss topics directly related to Tommy's professional resume, career, and experience.
53+
- If someone asks about anything unrelated to Tommy's professional life (personal questions, general knowledge, coding help, opinions, politics, etc.), politely decline and redirect them to ask about Tommy's resume or experience instead.
54+
- Do NOT make up or infer information not present in the resume data.
55+
- If you don't know something specific about Tommy, say so honestly.
56+
57+
Be friendly, concise, and professional. Keep responses short (2-4 sentences) unless the visitor asks for more detail.
58+
59+
- When someone asks to see the resume, asks for a summary/blurb, or says "let me see your resume", provide a brief professional blurb summarizing Tommy as a Sr. Sales Engineer with 7+ years of experience, then mention they can download the full resume PDF using the link in the header above, or at: /Tommy_Ho_Resume.pdf
60+
61+
RESUME DATA:
62+
${RESUME_DATA}`;
63+
64+
const ALLOWED_ORIGINS = [
65+
"https://dachi-dev.github.io",
66+
"http://localhost:5173",
67+
"http://localhost:4173",
68+
];
69+
70+
function corsHeaders(origin) {
71+
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
72+
return {
73+
"Access-Control-Allow-Origin": allowed,
74+
"Access-Control-Allow-Methods": "POST, OPTIONS",
75+
"Access-Control-Allow-Headers": "Content-Type",
76+
};
77+
}
78+
79+
export default {
80+
async fetch(request, env) {
81+
const origin = request.headers.get("Origin") || "";
82+
const headers = corsHeaders(origin);
83+
84+
if (request.method === "OPTIONS") {
85+
return new Response(null, { status: 204, headers });
86+
}
87+
88+
if (request.method !== "POST") {
89+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
90+
status: 405,
91+
headers: { ...headers, "Content-Type": "application/json" },
92+
});
93+
}
94+
95+
try {
96+
const { messages } = await request.json();
97+
98+
const contents = messages.map((m) => ({
99+
role: m.role === "assistant" ? "model" : "user",
100+
parts: [{ text: m.content }],
101+
}));
102+
103+
const geminiResponse = await fetch(
104+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${env.GEMINI_API_KEY}`,
105+
{
106+
method: "POST",
107+
headers: { "Content-Type": "application/json" },
108+
body: JSON.stringify({
109+
system_instruction: { parts: [{ text: SYSTEM_PROMPT }] },
110+
contents,
111+
generationConfig: { maxOutputTokens: 1024 },
112+
}),
113+
}
114+
);
115+
116+
if (!geminiResponse.ok) {
117+
const err = await geminiResponse.json().catch(() => ({}));
118+
return new Response(
119+
JSON.stringify({ error: err?.error?.message || `Gemini API error: ${geminiResponse.status}` }),
120+
{ status: 502, headers: { ...headers, "Content-Type": "application/json" } }
121+
);
122+
}
123+
124+
const data = await geminiResponse.json();
125+
const text = data.candidates[0].content.parts[0].text;
126+
127+
return new Response(JSON.stringify({ text }), {
128+
headers: { ...headers, "Content-Type": "application/json" },
129+
});
130+
} catch (err) {
131+
return new Response(JSON.stringify({ error: "Internal server error" }), {
132+
status: 500,
133+
headers: { ...headers, "Content-Type": "application/json" },
134+
});
135+
}
136+
},
137+
};

worker/wrangler.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name = "dachidev-api"
2+
main = "index.js"
3+
compatibility_date = "2024-12-01"

0 commit comments

Comments
 (0)