mirror of
https://github.com/hex248/ob248.com.git
synced 2026-02-07 18:23:04 +00:00
"Ask AI about me"
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
"@astrojs/react": "^4.3.0",
|
"@astrojs/react": "^4.3.0",
|
||||||
"@astrojs/vercel": "^8.2.7",
|
"@astrojs/vercel": "^8.2.7",
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
|
"@iconify-json/simple-icons": "^1.2.66",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@types/react": "^19.1.12",
|
"@types/react": "^19.1.12",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
|
|||||||
24
src/components/AIPrompt.astro
Normal file
24
src/components/AIPrompt.astro
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import { AI_SUMMARY_PROMPT } from "../lib/constants";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url: string; // anything that precedes the prompt: e.g. "https://ai.example.com/?prompt="
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url, title, icon } = Astro.props;
|
||||||
|
|
||||||
|
const fullUrl = url + encodeURIComponent(AI_SUMMARY_PROMPT);
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={fullUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-[var(--ayu-gutter)] hover:text-[var(--ayu-accent)] transition-colors duration-150"
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<Icon name={icon} class="w-7 h-7" />
|
||||||
|
</a>
|
||||||
2
src/lib/constants.ts
Normal file
2
src/lib/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
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?";
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import { Icon } from "astro-icon/components";
|
||||||
|
import AIPrompt from "../components/AIPrompt.astro";
|
||||||
import ProjectListItem from "../components/ProjectListItem.astro";
|
import ProjectListItem from "../components/ProjectListItem.astro";
|
||||||
import TimeSince from "../components/TimeSince.astro";
|
import TimeSince from "../components/TimeSince.astro";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
import { Icon } from "astro-icon/components";
|
import { AI_SUMMARY_PROMPT } from "../lib/constants";
|
||||||
|
|
||||||
export interface ProjectMetadata {
|
export interface ProjectMetadata {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -107,7 +108,39 @@ const sortedTags = Array.from(allTags).sort((a, b) => a.localeCompare(b));
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<Layout currentPage={{ title: "home", path: "/" }}>
|
<Layout currentPage={{ title: "home", path: "/" }}>
|
||||||
<h2 class="text-2xl font-600 text-ayu-green-500 mb-2">ABOUT</h2>
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<h2 class="text-2xl font-600 text-ayu-green-500">ABOUT</h2>
|
||||||
|
<div class="flex flex-col items-end gap-2">
|
||||||
|
<p class="text-ayu-gutter text-lg">Ask AI about me</p>
|
||||||
|
<div class="flex gap-4 items-center">
|
||||||
|
<AIPrompt
|
||||||
|
url="https://chat.openai.com/?q="
|
||||||
|
title="Ask ChatGPT"
|
||||||
|
icon="simple-icons:openai"
|
||||||
|
/>
|
||||||
|
<AIPrompt
|
||||||
|
url="https://claude.ai/new?q="
|
||||||
|
title="Ask Claude"
|
||||||
|
icon="simple-icons:claude"
|
||||||
|
/>
|
||||||
|
<div class="relative flex items-center">
|
||||||
|
<button
|
||||||
|
id="copy-prompt-btn"
|
||||||
|
class="text-[var(--ayu-gutter)] hover:text-[var(--ayu-accent)] transition-colors duration-150 cursor-pointer flex items-center"
|
||||||
|
title="Copy prompt to clipboard"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:content-copy" class="w-7 h-7" />
|
||||||
|
</button>
|
||||||
|
<span
|
||||||
|
id="copy-tooltip"
|
||||||
|
class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-sm bg-[var(--ayu-popup)] text-[var(--ayu-fg)] border border-[var(--ayu-gutter-dim)] rounded opacity-0 pointer-events-none transition-opacity duration-150 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
Copied to clipboard
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<span class="flex flex-wrap gap-2 mb-2">
|
<span class="flex flex-wrap gap-2 mb-2">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/hex248"
|
href="https://github.com/hex248"
|
||||||
@@ -143,20 +176,20 @@ const sortedTags = Array.from(allTags).sort((a, b) => a.localeCompare(b));
|
|||||||
<span class="text-ayu-red-500 font-500">Name: </span>Oliver Bryan
|
<span class="text-ayu-red-500 font-500">Name: </span>Oliver Bryan
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="text-ayu-blue-500 font-500">Role: </span>Junior
|
<span class="text-ayu-blue-500 font-500">Role: </span>Junior Software
|
||||||
Software Developer
|
Developer
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="text-ayu-red-500 font-500">Education: </span>Final Year
|
<span class="text-ayu-red-500 font-500">Education: </span>Final Year
|
||||||
Software Engineeering (BEng), University of Westminster (2024-2026)
|
Software Engineeering (BEng), University of Westminster (2024-2026)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="text-ayu-blue-500 font-500">Experience: </span>7 years,
|
<span class="text-ayu-blue-500 font-500">Experience: </span>7 years, 1
|
||||||
1 year in industry as a Junior Software Developer
|
year in industry as a Junior Software Developer
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="text-ayu-red-500 font-500">Skills: </span>Web
|
<span class="text-ayu-red-500 font-500">Skills: </span>Web Development,
|
||||||
Development, Game Development, Python, JavaScript, TypeScript
|
Game Development, Python, JavaScript, TypeScript
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="text-ayu-blue-500 font-500">Age: </span>
|
<span class="text-ayu-blue-500 font-500">Age: </span>
|
||||||
@@ -241,10 +274,7 @@ const sortedTags = Array.from(allTags).sort((a, b) => a.localeCompare(b));
|
|||||||
const tagName = tag.getAttribute("data-tag");
|
const tagName = tag.getAttribute("data-tag");
|
||||||
if (selectedTags.has(tagName || "")) {
|
if (selectedTags.has(tagName || "")) {
|
||||||
tag.classList.add(selectedTagBg, selectedTagText);
|
tag.classList.add(selectedTagBg, selectedTagText);
|
||||||
tag.classList.remove(
|
tag.classList.remove(unselectedTagBorder, unselectedTagText);
|
||||||
unselectedTagBorder,
|
|
||||||
unselectedTagText
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
tag.classList.remove(selectedTagBg, selectedTagText);
|
tag.classList.remove(selectedTagBg, selectedTagText);
|
||||||
tag.classList.add(unselectedTagBorder, unselectedTagText);
|
tag.classList.add(unselectedTagBorder, unselectedTagText);
|
||||||
@@ -261,37 +291,22 @@ const sortedTags = Array.from(allTags).sort((a, b) => a.localeCompare(b));
|
|||||||
.map((t) => t.trim()) || [];
|
.map((t) => t.trim()) || [];
|
||||||
const hasAllTags =
|
const hasAllTags =
|
||||||
selectedTags.size === 0 ||
|
selectedTags.size === 0 ||
|
||||||
Array.from(selectedTags).every((tag) =>
|
Array.from(selectedTags).every((tag) => projectTags.includes(tag));
|
||||||
projectTags.includes(tag)
|
|
||||||
);
|
|
||||||
if (hasAllTags) {
|
if (hasAllTags) {
|
||||||
(project as HTMLElement).style.display = "block";
|
(project as HTMLElement).style.display = "block";
|
||||||
|
|
||||||
const tagElements =
|
const tagElements = project.getElementsByClassName("project-tag");
|
||||||
project.getElementsByClassName("project-tag");
|
|
||||||
|
|
||||||
const matchingTags = Array.from(tagElements).filter(
|
const matchingTags = Array.from(tagElements).filter((tagElement) =>
|
||||||
(tagElement) =>
|
selectedTags.has((tagElement as HTMLElement).innerText)
|
||||||
selectedTags.has(
|
|
||||||
(tagElement as HTMLElement).innerText
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
Array.from(tagElements).forEach((tag) => {
|
Array.from(tagElements).forEach((tag) => {
|
||||||
tag.classList.remove(
|
tag.classList.remove(miniSelectedTagBorder, miniSelectedTagText);
|
||||||
miniSelectedTagBorder,
|
tag.classList.add(miniUnselectedTagBorder, miniUnselectedTagText);
|
||||||
miniSelectedTagText
|
|
||||||
);
|
|
||||||
tag.classList.add(
|
|
||||||
miniUnselectedTagBorder,
|
|
||||||
miniUnselectedTagText
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
matchingTags.forEach((tag) => {
|
matchingTags.forEach((tag) => {
|
||||||
tag.classList.add(
|
tag.classList.add(miniSelectedTagBorder, miniSelectedTagText);
|
||||||
miniSelectedTagBorder,
|
|
||||||
miniSelectedTagText
|
|
||||||
);
|
|
||||||
tag.classList.remove(
|
tag.classList.remove(
|
||||||
miniUnselectedTagBorder,
|
miniUnselectedTagBorder,
|
||||||
miniUnselectedTagText
|
miniUnselectedTagText
|
||||||
@@ -319,4 +334,20 @@ const sortedTags = Array.from(allTags).sort((a, b) => a.localeCompare(b));
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script define:vars={{ AI_SUMMARY_PROMPT }}>
|
||||||
|
document
|
||||||
|
.getElementById("copy-prompt-btn")
|
||||||
|
?.addEventListener("click", async () => {
|
||||||
|
await navigator.clipboard.writeText(AI_SUMMARY_PROMPT);
|
||||||
|
|
||||||
|
const tooltip = document.getElementById("copy-tooltip");
|
||||||
|
if (tooltip) {
|
||||||
|
tooltip.style.opacity = "1";
|
||||||
|
setTimeout(() => {
|
||||||
|
tooltip.style.opacity = "0";
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user