This commit is contained in:
2026-02-05 16:27:13 +00:00
parent f313420f4c
commit cc740265ea
4 changed files with 108 additions and 0 deletions

View File

@@ -5,6 +5,7 @@
"": {
"name": "ob248.com",
"dependencies": {
"@iconify/react": "^6.0.2",
"@nsmr/pixelart-react": "^2.0.0",
"@tailwindcss/vite": "^4.1.18",
"class-variance-authority": "^0.7.1",
@@ -14,6 +15,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^6.30.1",
"simple-icons": "^16.7.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
},
@@ -159,6 +161,10 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@iconify/react": ["@iconify/react@6.0.2", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-SMmC2sactfpJD427WJEDN6PMyznTFMhByK9yLW0gOTtnjzzbsi/Ke/XqsumsavFPwNiXs8jSiYeZTmLCLwO+Fg=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -657,6 +663,8 @@
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"simple-icons": ["simple-icons@16.7.0", "", {}, "sha512-2BteuQXu1+/jK5dF8YXe8nwoRG3afwl03bCmgPJLiotllUBU46B+WntXT731Z17oOntfV5eeTI7Q1k7MeOA6eg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],

View File

@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@iconify/react": "^6.0.2",
"@nsmr/pixelart-react": "^2.0.0",
"@tailwindcss/vite": "^4.1.18",
"class-variance-authority": "^0.7.1",
@@ -19,6 +20,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^6.30.1",
"simple-icons": "^16.7.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18"
},

88
src/components/ask-ai.tsx Normal file
View File

@@ -0,0 +1,88 @@
import { AI_SUMMARY_PROMPT } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { Icon } from "@iconify/react";
import { Copy } from "@nsmr/pixelart-react";
import { useRef, useState } from "react";
const chatGptUrl = "https://chat.openai.com/?q=";
const claudeUrl = "https://claude.ai/new?q=";
export function AskAI({
name,
prompt = AI_SUMMARY_PROMPT,
inline = false,
}: {
name: string;
prompt?: string;
inline?: boolean;
}) {
const encodedPrompt = encodeURIComponent(prompt);
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<number | null>(null);
const handleCopy = async () => {
if (!navigator.clipboard) return;
try {
await navigator.clipboard.writeText(prompt);
setCopied(true);
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
setCopied(false);
}, 1500);
} catch {
setCopied(false);
}
};
return (
<div
className={cn(
"flex flex-col items-end gap-2",
inline && "flex-row items-center gap-4",
)}
>
<p className="text-fg text-lg text-pretty">Ask AI about {name}:</p>
<div className="flex items-center gap-4">
<a
href={chatGptUrl + encodedPrompt}
target="_blank"
rel="noopener noreferrer"
className="text-fg hover:text-accent"
title={"Ask ChatGPT"}
>
<Icon icon="simple-icons:openai" className="size-6" />
</a>
<a
href={`${claudeUrl}${encodedPrompt}`}
target="_blank"
rel="noopener noreferrer"
className="text-fg hover:text-accent"
title="Ask Claude"
>
<Icon icon="simple-icons:claude" className="size-6" />
</a>
<div className="relative flex items-center">
<button
type="button"
onClick={handleCopy}
className="text-fg hover:text-accent cursor-pointer flex items-center"
title="Copy prompt to clipboard"
aria-label="Copy prompt to clipboard"
>
<Copy className="size-6" />
</button>
<span
className={cn(
"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs bg-background border opacity-0 pointer-events-none whitespace-nowrap",
copied && "opacity-100",
)}
>
Copied to clipboard
</span>
</div>
</div>
</div>
);
}

10
src/lib/constants.ts Normal file
View File

@@ -0,0 +1,10 @@
export const AI_SUMMARY_PROMPT =
"I am a recruiter, tell me about Oliver Bryan, a software developer. What would he bring to the table? Review his portfolio at ob248.com and summarize his key strengths, technical skills, and notable projects. What makes him stand out as a candidate?";
export function getProjectPrompt(
projectName: string,
projectDescription: string,
projectSlug: string,
): string {
return `Tell me about "${projectName}", a project by Oliver Bryan. ${projectDescription} Review the project page at ob248.com/projects/${projectSlug} and explain the technical decisions, technologies used, and what this project demonstrates about Oliver's skills as a developer. Provide any url or repository that would be helpful.`;
}