mirror of
https://github.com/hex248/ob248.com.git
synced 2026-02-07 18:23:04 +00:00
ask ai
This commit is contained in:
8
bun.lock
8
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "ob248.com",
|
"name": "ob248.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify/react": "^6.0.2",
|
||||||
"@nsmr/pixelart-react": "^2.0.0",
|
"@nsmr/pixelart-react": "^2.0.0",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^6.30.1",
|
"react-router-dom": "^6.30.1",
|
||||||
|
"simple-icons": "^16.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
},
|
},
|
||||||
@@ -159,6 +161,10 @@
|
|||||||
|
|
||||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
"@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/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=="],
|
"@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=="],
|
"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=="],
|
"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=="],
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify/react": "^6.0.2",
|
||||||
"@nsmr/pixelart-react": "^2.0.0",
|
"@nsmr/pixelart-react": "^2.0.0",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^6.30.1",
|
"react-router-dom": "^6.30.1",
|
||||||
|
"simple-icons": "^16.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.1.18"
|
||||||
},
|
},
|
||||||
|
|||||||
88
src/components/ask-ai.tsx
Normal file
88
src/components/ask-ai.tsx
Normal 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
10
src/lib/constants.ts
Normal 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.`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user