Skip to content

2. Hackbot User Widget#444

Open
ReehalS wants to merge 6 commits intomainfrom
hackbot-user-widget
Open

2. Hackbot User Widget#444
ReehalS wants to merge 6 commits intomainfrom
hackbot-user-widget

Conversation

@ReehalS
Copy link
Copy Markdown
Member

@ReehalS ReehalS commented Mar 12, 2026

Add HackBot chat widget with event cards, markdown, and layout integration

  • Chat widget with streaming responses, retry logic, and resize handle.
  • Event cards (full for workshops/activities, compact for meals/general).
  • Markdown text renderer, session-gated wrapper, cascading animations.
  • Layout integration in (hackers) route group.

MERGE ONLY AFTER #441 IS MERGED

Closes #443

ReehalS added 2 commits March 12, 2026 15:34
…CI/CD

- OpenAI streaming chat endpoint with get_events and provide_links tools
- Server actions for knowledge CRUD, reseed, import, usage metrics
- Event filtering/formatting utilities with timezone-aware LA time handling
- System prompt builder with profile-aware personalization and prefix caching
- Vector search context retrieval with retry/backoff
- Knowledge base JSON (55 entries: FAQ, tracks, judging, submission, general)
- CI/CD seed scripts for hackbot_knowledge to hackbot_docs
- Auth session extended with position, is_beginner, name fields
- Tailwind hackbot-slide-in animation keyframe
- Dependencies: ai@6, @ai-sdk/openai
…ation

Chat widget with streaming responses, retry logic, and resize handle.
Event cards (full for workshops/activities, compact for meals/general).
Markdown text renderer, session-gated wrapper, cascading animations.
Layout integration in (hackers) route group.
@ReehalS ReehalS changed the title Hackbot User Widget 2. Hackbot User Widget Mar 12, 2026
@michelleyeoh michelleyeoh marked this pull request as ready for review April 4, 2026 20:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the HackBot “HackDavis Helper” chat widget to the hackers section, including streaming chat UX (events + links), a minimal markdown renderer, and small server-streaming refinements to support the UI.

Changes:

  • Adds the HackBot widget UI (panel, header, message list, input, event cards) and mounts it in the (hackers) layout with session gating.
  • Updates the HackBot streaming API to optionally disable the get_events tool based on query intent and tweaks streaming behavior for tool-first responses.
  • Adjusts stream request validation to tolerate tool-only assistant messages and increases the allowed total history size.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
app/(pages)/(hackers)/layout.tsx Mounts the HackBot widget wrapper in the hackers route group and passes an initial profile derived from the server session.
app/(pages)/(hackers)/_components/Hackbot/MarkdownText.tsx Adds a lightweight inline markdown renderer (bold/italic + line breaks).
app/(pages)/(hackers)/_components/Hackbot/HackbotWidgetWrapper.tsx Client-side session gating for showing the widget only to hacker/admin users.
app/(pages)/(hackers)/_components/Hackbot/HackbotWidget.tsx Main chat widget: streaming fetch, retries, resize, localStorage history, and event/link buffering.
app/(pages)/(hackers)/_components/Hackbot/HackbotMessageList.tsx Renders chat bubbles, typing/retry indicators, links, and event cards/rows.
app/(pages)/(hackers)/_components/Hackbot/HackbotInputForm.tsx Input textarea, send button, character counter, and suggestions accordion.
app/(pages)/(hackers)/_components/Hackbot/HackbotHeader.tsx Chat header UI and close button.
app/(pages)/(hackers)/_components/Hackbot/HackbotEventCard.tsx Event card UI with add/remove personal-schedule actions.
app/(api)/api/hackbot/stream/route.ts Builds tools dynamically (conditionally includes get_events) and wires streaming response.
app/(api)/_utils/hackbot/stream/responseStream.ts Alters text-suppression logic after tool results to support tool-first UX.
app/(api)/_utils/hackbot/stream/request.ts Accepts empty assistant messages in history by dropping them; increases total history character cap.
app/(api)/_utils/hackbot/stream/intent.ts Adds heuristic intent detection to disable get_events for non-schedule factual queries.
.claude/settings.json Adds Claude tool permission settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +247 to +252
const content = line
.slice(2)
.trim()
.replace(/^"([\s\S]*)"$/, '$1')
.replace(/\\n/g, '\n')
.replace(/\\"/g, '"');
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Streaming text parsing is currently calling .trim() on each 0: text delta and manually stripping quotes/escapes. Because streaming deltas often start with a leading space (e.g. " world"), .trim() will remove significant whitespace and can concatenate words; the ad-hoc unescaping can also corrupt valid JSON-escaped sequences. Prefer JSON.parse(line.slice(2)) (no trim) inside a try/catch, and only append the parsed string as-is.

Suggested change
const content = line
.slice(2)
.trim()
.replace(/^"([\s\S]*)"$/, '$1')
.replace(/\\n/g, '\n')
.replace(/\\"/g, '"');
let content: string;
try {
const parsedContent = JSON.parse(line.slice(2));
if (typeof parsedContent !== 'string') {
continue;
}
content = parsedContent;
} catch {
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines +234 to +241
while (keepReading) {
// eslint-disable-next-line no-await-in-loop
const { done, value } = await reader.read();
if (done) {
keepReading = false;
} else {
const chunk = decoder.decode(value, { stream: true });
for (const line of chunk.split('\n')) {
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream reader splits each received chunk on \n and processes lines immediately, but network chunking can split a logical line across reads. This can cause missed/garbled prefixes (0:, a:) and JSON parse failures. Keep a persistent buffer string across reader.read() calls, append new decoded text, and only process complete lines (leaving the trailing partial line in the buffer for the next iteration).

Suggested change
while (keepReading) {
// eslint-disable-next-line no-await-in-loop
const { done, value } = await reader.read();
if (done) {
keepReading = false;
} else {
const chunk = decoder.decode(value, { stream: true });
for (const line of chunk.split('\n')) {
let lineBuffer = '';
while (keepReading) {
// eslint-disable-next-line no-await-in-loop
const { done, value } = await reader.read();
lineBuffer += done
? decoder.decode()
: decoder.decode(value, { stream: true });
const lines = lineBuffer.split('\n');
if (done) {
keepReading = false;
lineBuffer = '';
} else {
lineBuffer = lines.pop() ?? '';
}
for (const line of lines) {

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +233
const reader = response.body?.getReader();
const decoder = new TextDecoder();

if (reader) {
let keepReading = true;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If response.body?.getReader() is unavailable (e.g., streaming not supported in a given runtime/browser), the code currently treats the request as successful but will leave the assistant message empty. Consider explicitly handling the !reader case (e.g., fall back to await response.text() or throw to trigger retry/error state).

Copilot uses AI. Check for mistakes.
style={{ color: style?.text ?? '#003D3D' }}
>
{/* Left: datetime, location, tags */}
<div className="flex-1 min-w-0 space-y-1 space-between">
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space-between is not a Tailwind utility class (and there doesn't appear to be a project-defined .space-between class), so it has no effect here. If the intent is vertical distribution, use flex flex-col justify-between; otherwise remove the stray class to avoid confusion.

Suggested change
<div className="flex-1 min-w-0 space-y-1 space-between">
<div className="flex-1 min-w-0 space-y-1">

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +135
{/* Right: type badge, hosted by, recommended */}
<div className="shrink-0 flex flex-col items-end gap-1.5 pt-0.5 space-between">
{style && (
<span
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space-between is not a Tailwind utility class (and there doesn't appear to be a project-defined .space-between class), so it has no effect here. If the intent is vertical distribution, use flex flex-col justify-between; otherwise remove the stray class to avoid confusion.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HackBot User Widget

3 participants