mirror of
https://github.com/karpathy/nanochat.git
synced 2026-05-31 12:08:12 +00:00
* feat(inference): deploy d24 SFT weights to Modal Repoint Modal inference app from the broken d20 checkpoint to our own ManmohanSharma/nanochat-d24 SFT step 484. Rewrites the standalone model as an inference-only port of nanochat/gpt.py so the modern architecture (smear gate, per-layer value embeddings, ve_gate, backout, sliding window attention via SDPA, rotary base 100000, padded vocab, logit softcap) loads cleanly from the checkpoint. Tokenizer loads the pickled tiktoken encoding directly so special tokens end up at their true IDs (32759-32767), and the stop check uses that set instead of hardcoded 0-8. GPU bumped to L4 for headroom. HF token sourced from the 'huggingface' Modal secret. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(frontend): polished redesign with serif display + dark mode Lifts the craft level of the landing and chat UI without changing the desi identity. Adds Fraunces for display headlines, a floating pill LandingNav, a saffron-glow hero with a large serif headline and black pill CTAs, and three gradient-tiled feature cards with inline SVG glyphs replacing the emoji cards. The chat empty state is now a serif greeting with pill-chip prompt starters, and ChatInput is a single rounded pod so the send button sits inside the input (fixes the misaligned floating button). Adds a class-based dark mode across the chat surfaces with a sun/moon toggle in the sidebar footer, powered by a small useTheme hook and a no-flash init script in the root layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(frontend): add ESLint config so CI lint step passes next lint was failing with an interactive prompt because the repo had no ESLint config. Adds a minimal next/core-web-vitals extends and drops the now-unloadable @typescript-eslint/no-explicit-any disable directive in the stream proxy by narrowing the body type to unknown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
import rehypeHighlight from 'rehype-highlight';
|
|
import 'highlight.js/styles/github-dark.css';
|
|
import { Check, Copy } from 'lucide-react';
|
|
import clsx from 'clsx';
|
|
import type { Message } from '@/types/chat';
|
|
import SteamTyping from '@/components/svg/SteamTyping';
|
|
|
|
interface Props {
|
|
message: Message;
|
|
isStreaming?: boolean;
|
|
}
|
|
|
|
function CodeBlock({ inline, className, children, ...props }: {
|
|
inline?: boolean;
|
|
className?: string;
|
|
children?: React.ReactNode;
|
|
} & React.HTMLAttributes<HTMLElement>) {
|
|
const [copied, setCopied] = useState(false);
|
|
const content = String(children ?? '').replace(/\n$/, '');
|
|
|
|
if (inline) {
|
|
return (
|
|
<code className={className} {...props}>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|
|
|
|
const copy = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(content);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1500);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="relative group">
|
|
<button
|
|
type="button"
|
|
onClick={copy}
|
|
aria-label="Copy code"
|
|
className="absolute top-2 right-2 p-1.5 rounded bg-slate-700/70 text-slate-100 opacity-0 group-hover:opacity-100 hover:bg-slate-600 transition-opacity"
|
|
>
|
|
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
</button>
|
|
<pre>
|
|
<code className={className} {...props}>
|
|
{children}
|
|
</code>
|
|
</pre>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function MessageBubble({ message, isStreaming }: Props) {
|
|
const isUser = message.role === 'user';
|
|
const isConsole = message.role === 'console';
|
|
|
|
if (isConsole) {
|
|
return (
|
|
<div className="flex justify-start mb-2 animate-fade-in">
|
|
<div className="font-mono text-sm bg-cream-light dark:bg-ink-soft border border-cream-border dark:border-ink-border text-brown-light dark:text-ink-text-soft px-4 py-3 rounded-xl max-w-[80%]">
|
|
{message.content}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={clsx('flex mb-3 animate-fade-in', isUser ? 'justify-end' : 'justify-start')}>
|
|
<div
|
|
className={clsx(
|
|
'max-w-[85%] md:max-w-[75%]',
|
|
isUser
|
|
? 'bg-cream dark:bg-ink-elev border border-cream-border dark:border-ink-border rounded-[1.25rem] px-4 py-3'
|
|
: 'bg-transparent px-2 py-1',
|
|
)}
|
|
>
|
|
{!isUser && isStreaming && message.content.length === 0 ? (
|
|
<SteamTyping />
|
|
) : isUser ? (
|
|
<div className="whitespace-pre-wrap leading-relaxed text-[0.95rem] text-gray-900 dark:text-ink-text">
|
|
{message.content}
|
|
</div>
|
|
) : (
|
|
<div className="markdown-body text-[0.95rem] text-gray-900 dark:text-ink-text leading-relaxed">
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
rehypePlugins={[rehypeHighlight]}
|
|
components={{ code: CodeBlock as never }}
|
|
>
|
|
{message.content}
|
|
</ReactMarkdown>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|