Skip to content

Commit 36c77ee

Browse files
committed
feat(chat): update assistant to Agent AJ with site logo and predefined quick action buttons
1 parent 9609ef7 commit 36c77ee

2 files changed

Lines changed: 161 additions & 33 deletions

File tree

‎apps/site/src/components/ChatModal.astro‎

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@
66
<button
77
type="button"
88
id="ai-chat-open-btn"
9-
class="fixed bottom-6 right-6 z-50 flex items-center gap-2 rounded-full bg-primary px-4 py-3 text-sm font-semibold text-primary-foreground shadow-lg hover:opacity-90 transition-all hover:scale-105 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
10-
aria-label="Open AI Assistant"
9+
class="fixed bottom-6 right-6 z-50 flex items-center gap-2.5 rounded-full bg-primary px-4 py-3 text-sm font-semibold text-primary-foreground shadow-lg hover:opacity-90 transition-all hover:scale-105 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
10+
aria-label="Open Agent AJ"
1111
>
12-
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
13-
<path d="M12 2a10 10 0 0 1 10 10c0 5.523-4.477 10-10 10a9.96 9.96 0 0 1-4.708-1.175L2 22l1.175-5.292A9.96 9.96 0 0 1 2 12 10 10 0 0 1 12 2z"></path>
14-
<path d="m9 12 2 2 4-4"></path>
15-
</svg>
16-
<span>Ask AI</span>
12+
<img src="/icon.svg" alt="" class="size-5 object-contain" width="20" height="20" />
13+
<span>Ask Agent AJ</span>
1714
</button>
1815

1916
<!-- Modal Backdrop & Window -->
@@ -27,14 +24,16 @@
2724
<div class="relative flex flex-col w-full max-w-2xl h-[640px] max-h-[90vh] rounded-2xl border border-border bg-card shadow-2xl overflow-hidden">
2825
<!-- Header -->
2926
<header class="flex items-center justify-between border-b border-border px-5 py-3.5 bg-muted/40">
30-
<div class="flex items-center gap-2.5">
31-
<div class="flex size-8 items-center justify-center rounded-lg bg-primary/10 text-primary">
32-
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
33-
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
34-
</svg>
35-
</div>
27+
<div class="flex items-center gap-3">
28+
<img
29+
src="/icon.svg"
30+
alt="Agent AJ Logo"
31+
class="size-8 rounded-lg object-contain bg-primary/10 p-1"
32+
width="32"
33+
height="32"
34+
/>
3635
<div>
37-
<h2 id="ai-chat-title" class="text-sm font-bold tracking-tight">CodingCat AI Assistant</h2>
36+
<h2 id="ai-chat-title" class="text-sm font-bold tracking-tight">Agent AJ</h2>
3837
<p class="text-[11px] text-muted-foreground">Grounded in Sanity Content Lake & Workers AI</p>
3938
</div>
4039
</div>
@@ -55,36 +54,99 @@
5554
<div id="ai-chat-messages" class="flex-1 overflow-y-auto p-4 sm:p-5 flex flex-col gap-4 text-sm">
5655
<!-- Welcome / Prompt chips -->
5756
<div id="ai-chat-welcome" class="flex flex-col gap-3 my-auto text-center items-center py-6">
58-
<div class="size-10 rounded-full bg-primary/10 text-primary flex items-center justify-center">
59-
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
60-
<circle cx="12" cy="12" r="10"></circle>
61-
<path d="m9 12 2 2 4-4"></path>
62-
</svg>
63-
</div>
64-
<h3 class="font-semibold text-base">How can I help you learn today?</h3>
57+
<img src="/icon.svg" alt="Agent AJ" class="size-14 rounded-2xl object-contain bg-primary/10 p-2 shadow-sm" width="56" height="56" />
58+
<h3 class="font-semibold text-base">Hi, I'm Agent AJ!</h3>
6559
<p class="text-xs text-muted-foreground max-w-sm">
66-
I query the live CodingCat.dev content catalog, podcasts, video transcripts, and author notes.
60+
I query live CodingCat.dev podcasts, tutorials, and transcripts. Ask me anything or select a quick request:
6761
</p>
6862
<div class="flex flex-wrap gap-2 justify-center max-w-md pt-2">
69-
<button type="button" class="ai-prompt-chip text-xs px-3 py-1.5 rounded-full border border-border bg-card hover:bg-accent cursor-pointer transition-colors" data-prompt="What tutorials are available for Astro?">
70-
Tutorials for Astro
63+
<button
64+
type="button"
65+
class="ai-prompt-chip inline-flex items-center gap-1.5 text-xs px-3.5 py-2 rounded-full border border-border bg-card hover:bg-accent text-foreground cursor-pointer transition-all font-medium shadow-2xs hover:scale-105"
66+
data-prompt="What is the latest podcast episode?"
67+
>
68+
<svg class="size-3.5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
69+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
70+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
71+
<line x1="12" x2="12" y1="19" y2="22"></line>
72+
</svg>
73+
<span>Latest podcast</span>
7174
</button>
72-
<button type="button" class="ai-prompt-chip text-xs px-3 py-1.5 rounded-full border border-border bg-card hover:bg-accent cursor-pointer transition-colors" data-prompt="Who is Raul Melo and what did he write?">
73-
Who is Raul Melo?
75+
<button
76+
type="button"
77+
class="ai-prompt-chip inline-flex items-center gap-1.5 text-xs px-3.5 py-2 rounded-full border border-border bg-card hover:bg-accent text-foreground cursor-pointer transition-all font-medium shadow-2xs hover:scale-105"
78+
data-prompt="What is the latest blog post?"
79+
>
80+
<svg class="size-3.5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
81+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
82+
<polyline points="14 2 14 8 20 8"></polyline>
83+
<line x1="16" y1="13" x2="8" y2="13"></line>
84+
<line x1="16" y1="17" x2="8" y2="17"></line>
85+
</svg>
86+
<span>Latest blog post</span>
7487
</button>
75-
<button type="button" class="ai-prompt-chip text-xs px-3 py-1.5 rounded-full border border-border bg-card hover:bg-accent cursor-pointer transition-colors" data-prompt="What podcast episodes cover Sanity?">
76-
Podcasts about Sanity
88+
<button
89+
type="button"
90+
class="ai-prompt-chip inline-flex items-center gap-1.5 text-xs px-3.5 py-2 rounded-full border border-border bg-card hover:bg-accent text-foreground cursor-pointer transition-all font-medium shadow-2xs hover:scale-105"
91+
data-prompt="What are the top videos on CodingCat.dev?"
92+
>
93+
<svg class="size-3.5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
94+
<polygon points="23 7 16 12 23 17 23 7"></polygon>
95+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
96+
</svg>
97+
<span>Top videos</span>
7798
</button>
7899
</div>
79100
</div>
80101
</div>
81102

103+
<!-- Predefined Quick Request Buttons (Sticky Bar) -->
104+
<div class="flex items-center gap-1.5 px-3 sm:px-4 py-2 border-t border-border/40 bg-muted/20 overflow-x-auto no-scrollbar">
105+
<span class="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider shrink-0 mr-1">Quick:</span>
106+
<button
107+
type="button"
108+
class="ai-prompt-chip shrink-0 inline-flex items-center gap-1.5 text-xs px-3 py-1 rounded-full border border-border bg-card hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer transition-colors font-medium shadow-2xs"
109+
data-prompt="What is the latest podcast episode?"
110+
>
111+
<svg class="size-3 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
112+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
113+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
114+
<line x1="12" x2="12" y1="19" y2="22"></line>
115+
</svg>
116+
<span>Latest podcast</span>
117+
</button>
118+
<button
119+
type="button"
120+
class="ai-prompt-chip shrink-0 inline-flex items-center gap-1.5 text-xs px-3 py-1 rounded-full border border-border bg-card hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer transition-colors font-medium shadow-2xs"
121+
data-prompt="What is the latest blog post?"
122+
>
123+
<svg class="size-3 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
124+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
125+
<polyline points="14 2 14 8 20 8"></polyline>
126+
<line x1="16" y1="13" x2="8" y2="13"></line>
127+
<line x1="16" y1="17" x2="8" y2="17"></line>
128+
</svg>
129+
<span>Latest blog post</span>
130+
</button>
131+
<button
132+
type="button"
133+
class="ai-prompt-chip shrink-0 inline-flex items-center gap-1.5 text-xs px-3 py-1 rounded-full border border-border bg-card hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer transition-colors font-medium shadow-2xs"
134+
data-prompt="What are the top videos on CodingCat.dev?"
135+
>
136+
<svg class="size-3 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
137+
<polygon points="23 7 16 12 23 17 23 7"></polygon>
138+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
139+
</svg>
140+
<span>Top videos</span>
141+
</button>
142+
</div>
143+
82144
<!-- Input Form -->
83145
<form id="ai-chat-form" class="border-t border-border p-3 sm:p-4 bg-card flex items-center gap-2">
84146
<input
85147
type="text"
86148
id="ai-chat-input"
87-
placeholder="Ask anything about CodingCat.dev..."
149+
placeholder="Ask Agent AJ about CodingCat.dev..."
88150
class="flex-1 rounded-xl border border-input bg-background px-4 py-2.5 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary transition-all"
89151
autocomplete="off"
90152
/>
@@ -162,6 +224,13 @@
162224
: "mr-auto bg-muted/60 text-foreground rounded-2xl rounded-tl-xs px-4 py-2.5 border border-border"
163225
}`;
164226

227+
if (role === "assistant") {
228+
const badge = document.createElement("div");
229+
badge.className = "flex items-center gap-1.5 text-[11px] font-semibold text-primary mb-0.5";
230+
badge.innerHTML = `<img src="/icon.svg" alt="" class="size-3.5 object-contain" width="14" height="14" /><span>Agent AJ</span>`;
231+
msgDiv.appendChild(badge);
232+
}
233+
165234
const contentDiv = document.createElement("div");
166235
contentDiv.className = "whitespace-pre-wrap leading-relaxed";
167236
contentDiv.textContent = initialText;

‎apps/site/src/pages/api/chat.ts‎

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const GET: APIRoute = async () => {
2929
return new Response(
3030
JSON.stringify({
3131
status: "ready",
32-
name: "CodingCat AI Assistant",
32+
name: "Agent AJ",
3333
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
3434
mcp: SANITY_CONTEXT_MCP_URL,
3535
}),
@@ -204,6 +204,63 @@ export const POST: APIRoute = async ({ request }) => {
204204
}));
205205
},
206206
}),
207+
get_recent_content: tool({
208+
description:
209+
"Retrieve the most recently published podcast episodes or blog articles on CodingCat.dev. ALWAYS use this tool first when asked for 'latest podcast', 'recent episodes', 'latest blog post', or 'new articles'.",
210+
inputSchema: jsonSchema<{
211+
type?: "all" | "podcast" | "post";
212+
limit?: number;
213+
}>({
214+
type: "object",
215+
properties: {
216+
type: {
217+
type: "string",
218+
description:
219+
"Filter by content type: 'all', 'podcast', or 'post'. Defaults to 'all'.",
220+
enum: ["all", "podcast", "post"],
221+
},
222+
limit: {
223+
type: "integer",
224+
description: "Number of items to return (default: 5, max: 10).",
225+
},
226+
},
227+
}),
228+
execute: async ({ type, limit }) => {
229+
const rawType = type ? String(type).toLowerCase() : "all";
230+
const types =
231+
rawType === "podcast"
232+
? ["podcast"]
233+
: rawType === "post"
234+
? ["post"]
235+
: ["podcast", "post"];
236+
const count = Math.min(Math.max(Number(limit) || 5, 1), 10);
237+
try {
238+
const recent = await sanityClient.fetch(
239+
`*[_type in $types && defined(slug.current)] | order(date desc, _createdAt desc)[0...$limit]{
240+
title,
241+
_type,
242+
"slug": slug.current,
243+
date,
244+
excerpt,
245+
youtube
246+
}`,
247+
{ types, limit: count },
248+
);
249+
return (recent || []).map((item: any) => ({
250+
title: item.title,
251+
type: item._type,
252+
slug: item.slug,
253+
url: `/${item._type === "podcast" ? "podcast" : "post"}/${item.slug}`,
254+
date: item.date,
255+
excerpt: item.excerpt || "",
256+
youtube: item.youtube || null,
257+
}));
258+
} catch (err) {
259+
console.error("[get_recent_content error]", err);
260+
return [];
261+
}
262+
},
263+
}),
207264
groq_query: {
208265
...mcpTools.groq_query,
209266
execute: async ({ query }: { query: string }) => {
@@ -307,12 +364,14 @@ export const POST: APIRoute = async ({ request }) => {
307364
const result = streamText({
308365
model,
309366
system:
310-
"You are CodingCat.dev's intelligent content assistant (AJ). Answer user queries about CodingCat.dev podcasts, tutorials, articles, authors, and transcripts.\n\n" +
367+
"You are Agent AJ, CodingCat.dev's intelligent AI assistant. Answer user queries about CodingCat.dev podcasts, tutorials, articles, authors, and transcripts.\n\n" +
311368
"SEARCH & TOOL INSTRUCTIONS:\n" +
312-
"- ALWAYS use the `search_content` tool when asked to find, recommend, or query podcasts, posts, or articles on any topic, technology, or question (e.g. 'Sanity', 'Firebase', 'Next.js', 'AI'). It performs hybrid semantic and keyword search across all episodes, posts, and transcripts.\n" +
369+
"- ALWAYS use the `get_recent_content` tool first when asked for 'latest podcast', 'recent episodes', 'latest blog post', or 'new articles'.\n" +
370+
"- ALWAYS use the `search_content` tool when asked to find, recommend, or query podcasts, posts, or articles on any topic, technology, or question (e.g. 'Sanity', 'Firebase', 'Next.js', 'AI', 'web dev'). It performs hybrid semantic and keyword search across all episodes, posts, and transcripts.\n" +
371+
"- When asked for 'top videos' or popular video podcasts, search for high-profile episodes with video recordings (e.g. episodes featuring Guillermo Rauch, Rich Harris, Lee Robinson, or recent GenAI/MCP video podcasts) and provide their YouTube links alongside CodingCat.dev episode links.\n" +
313372
"- Use `groq_query` for precise lookups when searching for specific author profiles (e.g. *[_type == 'author' && title match '*Alex*']).\n" +
314373
"- CRITICAL: ALWAYS execute tools by invoking them via function calls. NEVER output raw JSON or code blocks in your text describing tool calls instead of executing them.\n" +
315-
"- Once you receive results from your tool calls, synthesize them into an engaging, helpful response. List the relevant episode or article titles, brief descriptions, and markdown links using relative paths (e.g. [Title](/podcast/slug) or [Title](/post/slug)). Never use localhost or absolute domain URLs.",
374+
"- Once you receive results from your tool calls, synthesize them into an engaging, helpful response. List the relevant episode or article titles, brief descriptions, and markdown links using relative paths (e.g. [Title](/podcast/slug) or [Title](/post/slug)). If YouTube links are available, include them. Never use localhost or absolute domain URLs.",
316375
messages: coreMessages,
317376
tools,
318377
stopWhen: isStepCount(5),

0 commit comments

Comments
 (0)