react-ai-chat-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# react-ai-chat-ui
|
|
2
|
+
|
|
3
|
+
Composable AI chat UI for React 19 — layout shell, message list with auto-scroll, markdown + GFM, **lightweight** fenced code blocks (copy + scroll, no Prism bundle), and a keyboard-friendly composer.
|
|
4
|
+
|
|
5
|
+
> **npm name:** The name `react-ai-ui` is already taken on npm (another maintainer). This library is published as **`react-ai-chat-ui`**.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install react-ai-chat-ui react react-dom
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import the bundled stylesheet once (Tailwind utilities scoped to the classes used in this package):
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import "react-ai-chat-ui/styles.css";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Try it locally (demo app)
|
|
20
|
+
|
|
21
|
+
From the repo root:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run demo
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That builds the library, installs the Vite app under `examples/demo`, and starts **http://localhost:5173**.
|
|
28
|
+
|
|
29
|
+
**Two-terminal workflow** (while you change library source):
|
|
30
|
+
|
|
31
|
+
1. **Terminal A — library watch:** `npm run dev` (rebuilds `dist/` on save).
|
|
32
|
+
2. **Terminal B — demo:** `cd examples/demo && npm install && npm run dev`
|
|
33
|
+
Refresh the browser after each library rebuild (or rely on Vite HMR when only the demo changes).
|
|
34
|
+
|
|
35
|
+
The demo depends on the library via `"react-ai-chat-ui": "file:../.."` in `examples/demo/package.json` (npm links the package into `node_modules`).
|
|
36
|
+
|
|
37
|
+
## Verify it works
|
|
38
|
+
|
|
39
|
+
**Automated sanity check** (types + library build + demo production build):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
All steps must exit with code **0**. That proves the package compiles, bundles, and the demo app can import and build against it.
|
|
46
|
+
|
|
47
|
+
**Manual smoke test** (run `npm run demo`, then in the browser):
|
|
48
|
+
|
|
49
|
+
| Check | What to do | Pass if |
|
|
50
|
+
|--------|------------|---------|
|
|
51
|
+
| Styles | Open the demo | Chat shell, bubbles, and input look styled (not unstyled HTML). |
|
|
52
|
+
| Send | Type text, click **Send** or press **Enter** | User bubble appears on the right; input clears. |
|
|
53
|
+
| Shift+Enter | **Shift+Enter** in the textarea | Inserts a newline without sending. |
|
|
54
|
+
| Empty send | Clear input, click **Send** | Nothing is added (button disabled when empty). |
|
|
55
|
+
| Assistant reply | Send a message | After ~600ms, assistant bubble on the left with **bold** text and a **code** fence. |
|
|
56
|
+
| Markdown | Confirm bold + fence render | Not raw `**` or triple backticks as plain text. |
|
|
57
|
+
| Code block | In assistant message, use horizontal scroll if needed | Monospace block; **Copy** works (paste elsewhere). |
|
|
58
|
+
| Scroll | Send several messages | List scrolls toward the latest message. |
|
|
59
|
+
|
|
60
|
+
For deeper coverage later, add **Vitest + React Testing Library** or **Playwright** against `examples/demo` — not included yet to keep the repo minimal.
|
|
61
|
+
|
|
62
|
+
## Use in another project without publishing
|
|
63
|
+
|
|
64
|
+
From this repo:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run build
|
|
68
|
+
npm pack
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
In your app:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install /absolute/path/to/react-ai-chat-ui-1.0.0.tgz
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or in your app’s `package.json`: `"react-ai-chat-ui": "file:../path/to/this-repo"` then `npm install`.
|
|
78
|
+
|
|
79
|
+
## Bundle size
|
|
80
|
+
|
|
81
|
+
This package intentionally **does not** ship `react-syntax-highlighter` / Prism. Fenced code is readable monospace + horizontal scroll + copy, which keeps `npm install` and the published JS bundle small. If you need token colors, add a highlighter in your app and swap the `code` / `pre` mapping in a fork of `AIMessage`, or wrap `CodeBlock`.
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import {
|
|
87
|
+
ChatWindow,
|
|
88
|
+
MessageList,
|
|
89
|
+
PromptInput,
|
|
90
|
+
useChat,
|
|
91
|
+
} from "react-ai-chat-ui";
|
|
92
|
+
import "react-ai-chat-ui/styles.css";
|
|
93
|
+
|
|
94
|
+
function App() {
|
|
95
|
+
const { messages, sendMessage, addAssistantMessage } = useChat();
|
|
96
|
+
|
|
97
|
+
const handleSend = (text: string) => {
|
|
98
|
+
sendMessage(text);
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
addAssistantMessage(`You said: ${text}`);
|
|
101
|
+
}, 1000);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="h-dvh p-4">
|
|
106
|
+
<ChatWindow className="mx-auto h-full max-h-[720px]">
|
|
107
|
+
<MessageList messages={messages} />
|
|
108
|
+
<PromptInput onSend={handleSend} />
|
|
109
|
+
</ChatWindow>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Exports
|
|
116
|
+
|
|
117
|
+
- `ChatWindow` — flex column shell (`min-h-0` safe for nested scroll)
|
|
118
|
+
- `MessageList` — renders `Message[]`, auto-scroll, optional `footer` slot
|
|
119
|
+
- `MessageBubble` — user vs assistant layout; assistant uses `AIMessage`
|
|
120
|
+
- `AIMessage` — `react-markdown` + `remark-gfm`
|
|
121
|
+
- `CodeBlock` — monospace fence + copy + horizontal scroll (no syntax-highlighter dependency)
|
|
122
|
+
- `PromptInput` — Enter send, Shift+Enter newline, empty guard
|
|
123
|
+
- `TypingIndicator` — status line for loading
|
|
124
|
+
- `useChat` — in-memory `messages` + `sendMessage` / `addAssistantMessage` / `clearMessages`
|
|
125
|
+
- Types: `Message`, `MessageRole`
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime'),M=require('react-markdown'),P=require('remark-gfm');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var M__default=/*#__PURE__*/_interopDefault(M);var P__default=/*#__PURE__*/_interopDefault(P);function u(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function K(){let[e,t]=react.useState([]),o=react.useCallback(s=>{let c=s.trim();c&&t(i=>[...i,{id:u(),role:"user",content:c,createdAt:new Date}]);},[]),a=react.useCallback(s=>{t(c=>[...c,{id:u(),role:"assistant",content:s,createdAt:new Date}]);},[]),n=react.useCallback(()=>{t([]);},[]);return {messages:e,sendMessage:o,addAssistantMessage:a,clearMessages:n}}function q({children:e,className:t=""}){return jsxRuntime.jsx("section",{className:`rai-chat-window flex h-full min-h-0 w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-zinc-50 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-950 ${t}`.trim(),"aria-label":"Chat",children:e})}function C(e){return e.toLowerCase().replace(/^language-/,"").trim()||"text"}function b({language:e,code:t}){let o=C(e),[a,n]=react.useState(false),s=react.useCallback(async()=>{try{await navigator.clipboard.writeText(t),n(!0),window.setTimeout(()=>n(!1),2e3);}catch{n(false);}},[t]);return jsxRuntime.jsxs("figure",{className:"rai-code-block my-3 overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-950","aria-label":`Code sample, ${o}`,children:[jsxRuntime.jsxs("figcaption",{className:"flex items-center justify-between gap-2 border-b border-zinc-200 bg-zinc-50 px-3 py-2 text-xs font-medium text-zinc-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300",children:[jsxRuntime.jsx("span",{className:"truncate font-mono",children:o}),jsxRuntime.jsx("button",{type:"button",onClick:s,className:"shrink-0 rounded-lg border border-zinc-200 bg-white px-2.5 py-1 text-xs font-medium text-zinc-800 transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700",children:a?"Copied":"Copy"})]}),jsxRuntime.jsx("div",{className:"overflow-x-auto border-t border-zinc-100 bg-zinc-50/80 dark:border-zinc-800 dark:bg-zinc-900/40",children:jsxRuntime.jsx("pre",{className:"m-0 p-4",children:jsxRuntime.jsx("code",{className:"block font-mono text-[0.8125rem] leading-relaxed whitespace-pre text-zinc-900 dark:text-zinc-100",children:t})})})]})}function I({className:e,children:t,...o}){return jsxRuntime.jsx("code",{className:`rounded-md bg-zinc-100 px-1.5 py-0.5 font-mono text-[0.9em] text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100 ${e??""}`,...o,children:t})}function R({className:e,children:t,...o}){let a=String(t).replace(/\n$/,""),n=/language-([\w+-]+)/.exec(e||"");return n?jsxRuntime.jsx(b,{language:n[1],code:a}):a.includes(`
|
|
2
|
+
`)?jsxRuntime.jsx(b,{language:"text",code:a}):jsxRuntime.jsx(I,{className:e,...o,children:t})}var D={pre:({children:e})=>jsxRuntime.jsx(jsxRuntime.Fragment,{children:e}),code:R,p:({children:e})=>jsxRuntime.jsx("p",{className:"mb-3 last:mb-0 leading-relaxed text-zinc-800 dark:text-zinc-200",children:e}),a:({href:e,children:t,...o})=>jsxRuntime.jsx("a",{href:e,className:"font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-2 hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30",target:"_blank",rel:"noopener noreferrer",...o,children:t}),ul:({children:e})=>jsxRuntime.jsx("ul",{className:"mb-3 list-disc space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200",children:e}),ol:({children:e})=>jsxRuntime.jsx("ol",{className:"mb-3 list-decimal space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200",children:e}),li:({children:e})=>jsxRuntime.jsx("li",{className:"leading-relaxed",children:e}),h1:({children:e})=>jsxRuntime.jsx("h1",{className:"mb-3 mt-4 text-xl font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),h2:({children:e})=>jsxRuntime.jsx("h2",{className:"mb-2 mt-4 text-lg font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),h3:({children:e})=>jsxRuntime.jsx("h3",{className:"mb-2 mt-3 text-base font-semibold text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),blockquote:({children:e})=>jsxRuntime.jsx("blockquote",{className:"mb-3 border-l-4 border-zinc-300 pl-4 text-zinc-600 italic dark:border-zinc-600 dark:text-zinc-400",children:e}),hr:()=>jsxRuntime.jsx("hr",{className:"my-4 border-zinc-200 dark:border-zinc-700"}),table:({children:e})=>jsxRuntime.jsx("div",{className:"my-3 w-full overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-700",children:jsxRuntime.jsx("table",{className:"w-full min-w-[20rem] border-collapse text-left text-sm",children:e})}),thead:({children:e})=>jsxRuntime.jsx("thead",{className:"bg-zinc-100 dark:bg-zinc-800/80",children:e}),tbody:({children:e})=>jsxRuntime.jsx("tbody",{children:e}),tr:({children:e})=>jsxRuntime.jsx("tr",{className:"border-b border-zinc-200 last:border-b-0 dark:border-zinc-700",children:e}),th:({children:e})=>jsxRuntime.jsx("th",{className:"px-3 py-2 font-semibold text-zinc-900 dark:text-zinc-100",children:e}),td:({children:e})=>jsxRuntime.jsx("td",{className:"px-3 py-2 text-zinc-800 dark:text-zinc-200",children:e}),strong:({children:e})=>jsxRuntime.jsx("strong",{className:"font-semibold text-zinc-900 dark:text-zinc-50",children:e})};function x({content:e}){return jsxRuntime.jsx("div",{className:"rai-ai-message max-w-none text-sm",children:jsxRuntime.jsx(M__default.default,{remarkPlugins:[P__default.default],components:D,children:e})})}var g=react.memo(function({message:t}){let o=t.role==="user";return jsxRuntime.jsx("article",{className:`rai-message-bubble flex w-full ${o?"justify-end":"justify-start"}`,children:jsxRuntime.jsx("div",{className:o?"max-w-[min(100%,42rem)] rounded-2xl rounded-br-md bg-zinc-900 px-4 py-2.5 text-sm leading-relaxed text-zinc-50 shadow-sm dark:bg-zinc-100 dark:text-zinc-900":"max-w-[min(100%,42rem)] rounded-2xl rounded-bl-md border border-zinc-200/80 bg-white px-4 py-3 text-sm leading-relaxed text-zinc-800 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-900 dark:text-zinc-100",children:o?jsxRuntime.jsx("p",{className:"whitespace-pre-wrap break-words",children:t.content}):jsxRuntime.jsx(x,{content:t.content})})})});function ae({messages:e,footer:t}){let o=react.useRef(null);return react.useEffect(()=>{o.current?.scrollIntoView({behavior:"smooth",block:"end"});},[e,t]),jsxRuntime.jsxs("div",{className:"rai-message-list flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-4 sm:px-4",role:"log","aria-live":"polite","aria-relevant":"additions",children:[e.map(a=>jsxRuntime.jsx(g,{message:a},a.id)),t,jsxRuntime.jsx("div",{ref:o,className:"h-px shrink-0","aria-hidden":true})]})}function ce({onSend:e,className:t="","aria-label":o="Message input"}){let[a,n]=react.useState(""),s=react.useCallback(()=>{let i=a.trim();i&&(e(i),n(""));},[a,e]),c=react.useCallback(i=>{i.key==="Enter"&&(i.shiftKey||(i.preventDefault(),s()));},[s]);return jsxRuntime.jsxs("div",{className:`rai-prompt-input border-t border-zinc-200/80 bg-white/90 p-3 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4 ${t}`.trim(),children:[jsxRuntime.jsx("label",{className:"sr-only",htmlFor:"rai-prompt-textarea",children:o}),jsxRuntime.jsxs("div",{className:"flex flex-col gap-2 sm:flex-row sm:items-end",children:[jsxRuntime.jsx("textarea",{id:"rai-prompt-textarea",rows:2,value:a,onChange:i=>n(i.target.value),onKeyDown:c,placeholder:"Message\u2026","aria-label":o,className:"rai-prompt-textarea min-h-[2.75rem] w-full resize-y rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-inner outline-none ring-zinc-400/40 placeholder:text-zinc-400 focus:border-zinc-300 focus:ring-2 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-600 dark:focus:ring-zinc-600/40"}),jsxRuntime.jsx("button",{type:"button",onClick:s,className:"inline-flex shrink-0 items-center justify-center rounded-xl bg-zinc-900 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200",disabled:!a.trim(),children:"Send"})]}),jsxRuntime.jsx("p",{className:"mt-2 text-xs text-zinc-500 dark:text-zinc-400",children:"Enter to send \xB7 Shift+Enter for new line"})]})}function me(){return jsxRuntime.jsx("p",{className:"rai-typing-indicator px-3 py-2 text-sm text-zinc-500 motion-safe:animate-pulse dark:text-zinc-400",role:"status","aria-live":"polite",children:"AI is typing\u2026"})}exports.AIMessage=x;exports.ChatWindow=q;exports.CodeBlock=b;exports.MessageBubble=g;exports.MessageList=ae;exports.PromptInput=ce;exports.TypingIndicator=me;exports.useChat=K;//# sourceMappingURL=index.cjs.map
|
|
3
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useChat.ts","../src/components/ChatWindow.tsx","../src/components/CodeBlock.tsx","../src/components/AIMessage.tsx","../src/components/MessageBubble.tsx","../src/components/MessageList.tsx","../src/components/PromptInput.tsx","../src/components/TypingIndicator.tsx"],"names":["createId","useChat","messages","setMessages","useState","sendMessage","useCallback","content","trimmed","prev","addAssistantMessage","clearMessages","ChatWindow","children","className","jsx","formatLanguageLabel","raw","CodeBlock","language","code","label","copied","setCopied","handleCopy","jsxs","InlineCode","rest","MarkdownCode","text","match","markdownComponents","Fragment","href","AIMessage","ReactMarkdown","remarkGfm","MessageBubble","memo","message","isUser","MessageList","footer","endRef","useRef","useEffect","m","PromptInput","onSend","ariaLabel","draft","setDraft","submit","onKeyDown","e","TypingIndicator"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useCallback, useState } from \"react\";\n\nimport type { Message } from \"../types/chat\";\n\n/**\n * Adhoc: minimal local chat state — **Lifted state** pattern so parents can\n * swap this for server-backed stores without changing UI components.\n */\nfunction createId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Adhoc: headless hook for demos and apps that only need in-memory chat.\n *\n * @returns Messages plus imperative send/clear helpers (stable via useCallback).\n */\nexport function useChat() {\n const [messages, setMessages] = useState<Message[]>([]);\n\n const sendMessage = useCallback((content: string) => {\n const trimmed = content.trim();\n if (!trimmed) {\n return;\n }\n setMessages((prev) => [\n ...prev,\n {\n id: createId(),\n role: \"user\",\n content: trimmed,\n createdAt: new Date(),\n },\n ]);\n }, []);\n\n const addAssistantMessage = useCallback((content: string) => {\n setMessages((prev) => [\n ...prev,\n {\n id: createId(),\n role: \"assistant\",\n content,\n createdAt: new Date(),\n },\n ]);\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n }, []);\n\n return {\n messages,\n sendMessage,\n addAssistantMessage,\n clearMessages,\n };\n}\n","import type { ReactNode } from \"react\";\n\nexport interface ChatWindowProps {\n /** Main chat surface (e.g. message list + composer). */\n children: ReactNode;\n /** Optional class for host layout integration (e.g. `h-screen`). */\n className?: string;\n}\n\n/**\n * Adhoc: root flex column with **min-h-0** so nested `overflow-y-auto` regions\n * scroll correctly inside flex parents (a common flexbox footgun).\n */\nexport function ChatWindow({\n children,\n className = \"\",\n}: ChatWindowProps) {\n return (\n <section\n className={`rai-chat-window flex h-full min-h-0 w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-zinc-50 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-950 ${className}`.trim()}\n aria-label=\"Chat\"\n >\n {children}\n </section>\n );\n}\n","import { useCallback, useState } from \"react\";\n\nexport interface CodeBlockProps {\n /** Fence language tag for the label (informational only; no highlighter bundled). */\n language: string;\n /** Source text inside the fence. */\n code: string;\n}\n\n/** Adhoc: normalize fence labels for display without pulling a grammar registry. */\nfunction formatLanguageLabel(raw: string): string {\n const id = raw.toLowerCase().replace(/^language-/, \"\").trim();\n return id || \"text\";\n}\n\n/**\n * Adhoc: **Tokenless code fence** — trades syntax colors for a much smaller install\n * than `react-syntax-highlighter` + Prism; apps that need highlighting can wrap\n * this component or replace the `code` mapping in `AIMessage`.\n */\nexport function CodeBlock({ language, code }: CodeBlockProps) {\n const label = formatLanguageLabel(language);\n const [copied, setCopied] = useState(false);\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n window.setTimeout(() => setCopied(false), 2000);\n } catch {\n setCopied(false);\n }\n }, [code]);\n\n return (\n <figure\n className=\"rai-code-block my-3 overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-950\"\n aria-label={`Code sample, ${label}`}\n >\n <figcaption className=\"flex items-center justify-between gap-2 border-b border-zinc-200 bg-zinc-50 px-3 py-2 text-xs font-medium text-zinc-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300\">\n <span className=\"truncate font-mono\">{label}</span>\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"shrink-0 rounded-lg border border-zinc-200 bg-white px-2.5 py-1 text-xs font-medium text-zinc-800 transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700\"\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </figcaption>\n <div className=\"overflow-x-auto border-t border-zinc-100 bg-zinc-50/80 dark:border-zinc-800 dark:bg-zinc-900/40\">\n <pre className=\"m-0 p-4\">\n <code className=\"block font-mono text-[0.8125rem] leading-relaxed whitespace-pre text-zinc-900 dark:text-zinc-100\">\n {code}\n </code>\n </pre>\n </div>\n </figure>\n );\n}\n","import type { ComponentPropsWithoutRef } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { CodeBlock } from \"./CodeBlock\";\n\nexport interface AIMessageProps {\n /** Raw assistant markdown (GFM tables, lists, fenced code, links). */\n content: string;\n}\n\nfunction InlineCode({\n className,\n children,\n ...rest\n}: ComponentPropsWithoutRef<\"code\">) {\n return (\n <code\n className={`rounded-md bg-zinc-100 px-1.5 py-0.5 font-mono text-[0.9em] text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100 ${className ?? \"\"}`}\n {...rest}\n >\n {children}\n </code>\n );\n}\n\n/**\n * Adhoc: `react-markdown` emits `pre > code` for fences — we detect `language-*`\n * or multiline bodies so **inline** `code` stays compact.\n */\nfunction MarkdownCode({\n className,\n children,\n ...rest\n}: ComponentPropsWithoutRef<\"code\">) {\n const text = String(children).replace(/\\n$/, \"\");\n const match = /language-([\\w+-]+)/.exec(className || \"\");\n if (match) {\n return (\n <CodeBlock\n language={match[1]}\n code={text}\n />\n );\n }\n if (text.includes(\"\\n\")) {\n return (\n <CodeBlock\n language=\"text\"\n code={text}\n />\n );\n }\n return (\n <InlineCode className={className} {...rest}>\n {children}\n </InlineCode>\n );\n}\n\nconst markdownComponents: Components = {\n pre: ({ children }) => <>{children}</>,\n code: MarkdownCode,\n p: ({ children }) => (\n <p className=\"mb-3 last:mb-0 leading-relaxed text-zinc-800 dark:text-zinc-200\">\n {children}\n </p>\n ),\n a: ({ href, children, ...rest }) => (\n <a\n href={href}\n className=\"font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-2 hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...rest}\n >\n {children}\n </a>\n ),\n ul: ({ children }) => (\n <ul className=\"mb-3 list-disc space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200\">\n {children}\n </ul>\n ),\n ol: ({ children }) => (\n <ol className=\"mb-3 list-decimal space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200\">\n {children}\n </ol>\n ),\n li: ({ children }) => <li className=\"leading-relaxed\">{children}</li>,\n h1: ({ children }) => (\n <h1 className=\"mb-3 mt-4 text-xl font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h1>\n ),\n h2: ({ children }) => (\n <h2 className=\"mb-2 mt-4 text-lg font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h2>\n ),\n h3: ({ children }) => (\n <h3 className=\"mb-2 mt-3 text-base font-semibold text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h3>\n ),\n blockquote: ({ children }) => (\n <blockquote className=\"mb-3 border-l-4 border-zinc-300 pl-4 text-zinc-600 italic dark:border-zinc-600 dark:text-zinc-400\">\n {children}\n </blockquote>\n ),\n hr: () => (\n <hr className=\"my-4 border-zinc-200 dark:border-zinc-700\" />\n ),\n table: ({ children }) => (\n <div className=\"my-3 w-full overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-700\">\n <table className=\"w-full min-w-[20rem] border-collapse text-left text-sm\">\n {children}\n </table>\n </div>\n ),\n thead: ({ children }) => (\n <thead className=\"bg-zinc-100 dark:bg-zinc-800/80\">{children}</thead>\n ),\n tbody: ({ children }) => <tbody>{children}</tbody>,\n tr: ({ children }) => (\n <tr className=\"border-b border-zinc-200 last:border-b-0 dark:border-zinc-700\">\n {children}\n </tr>\n ),\n th: ({ children }) => (\n <th className=\"px-3 py-2 font-semibold text-zinc-900 dark:text-zinc-100\">\n {children}\n </th>\n ),\n td: ({ children }) => (\n <td className=\"px-3 py-2 text-zinc-800 dark:text-zinc-200\">\n {children}\n </td>\n ),\n strong: ({ children }) => (\n <strong className=\"font-semibold text-zinc-900 dark:text-zinc-50\">\n {children}\n </strong>\n ),\n};\n\n/**\n * Adhoc: Assistant markdown surface — **GFM** via `remark-gfm` (tables, task\n * lists, strikethrough). Fenced code uses the lightweight `CodeBlock` (no Prism\n * bundle). URLs pass through `react-markdown`’s default sanitizer.\n */\nexport function AIMessage({ content }: AIMessageProps) {\n return (\n <div className=\"rai-ai-message max-w-none text-sm\">\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={markdownComponents}\n >\n {content}\n </ReactMarkdown>\n </div>\n );\n}\n","import { memo } from \"react\";\n\nimport type { Message } from \"../types/chat\";\nimport { AIMessage } from \"./AIMessage\";\n\nexport interface MessageBubbleProps {\n message: Message;\n}\n\n/**\n * Adhoc: **Presentation split** — user plain text vs assistant markdown keeps\n * rendering cost and XSS surface scoped to AI paths only.\n */\nexport const MessageBubble = memo(function MessageBubble({\n message,\n}: MessageBubbleProps) {\n const isUser = message.role === \"user\";\n\n return (\n <article\n className={`rai-message-bubble flex w-full ${isUser ? \"justify-end\" : \"justify-start\"}`}\n >\n <div\n className={\n isUser\n ? \"max-w-[min(100%,42rem)] rounded-2xl rounded-br-md bg-zinc-900 px-4 py-2.5 text-sm leading-relaxed text-zinc-50 shadow-sm dark:bg-zinc-100 dark:text-zinc-900\"\n : \"max-w-[min(100%,42rem)] rounded-2xl rounded-bl-md border border-zinc-200/80 bg-white px-4 py-3 text-sm leading-relaxed text-zinc-800 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-900 dark:text-zinc-100\"\n }\n >\n {isUser ? (\n <p className=\"whitespace-pre-wrap break-words\">{message.content}</p>\n ) : (\n <AIMessage content={message.content} />\n )}\n </div>\n </article>\n );\n});\n","import { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport type { Message } from \"../types/chat\";\nimport { MessageBubble } from \"./MessageBubble\";\n\nexport interface MessageListProps {\n messages: Message[];\n /** Optional slot below messages (e.g. `<TypingIndicator />`). */\n footer?: ReactNode;\n}\n\n/**\n * Adhoc: **Auto-scroll** via sentinel `scrollIntoView` — simple and reliable\n * vs measuring scrollHeight on every paint; smooth behavior matches chat UX.\n */\nexport function MessageList({ messages, footer }: MessageListProps) {\n const endRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({\n behavior: \"smooth\",\n block: \"end\",\n });\n }, [messages, footer]);\n\n return (\n <div\n className=\"rai-message-list flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-4 sm:px-4\"\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n >\n {messages.map((m) => (\n <MessageBubble key={m.id} message={m} />\n ))}\n {footer}\n <div ref={endRef} className=\"h-px shrink-0\" aria-hidden />\n </div>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\n\nexport interface PromptInputProps {\n /** Fired with trimmed text after validation; parent owns persistence / API. */\n onSend: (message: string) => void;\n /** Extra classes on the outer shell (width, sticky footers, etc.). */\n className?: string;\n /** Accessible label for the composer. */\n \"aria-label\"?: string;\n}\n\n/**\n * Adhoc: **Uncontrolled-to-send** textarea — state resets after send so the\n * host does not need a controlled `value` just to clear the box.\n */\nexport function PromptInput({\n onSend,\n className = \"\",\n \"aria-label\": ariaLabel = \"Message input\",\n}: PromptInputProps) {\n const [draft, setDraft] = useState(\"\");\n\n const submit = useCallback(() => {\n const trimmed = draft.trim();\n if (!trimmed) {\n return;\n }\n onSend(trimmed);\n setDraft(\"\");\n }, [draft, onSend]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key !== \"Enter\") {\n return;\n }\n if (e.shiftKey) {\n return;\n }\n e.preventDefault();\n submit();\n },\n [submit],\n );\n\n return (\n <div\n className={`rai-prompt-input border-t border-zinc-200/80 bg-white/90 p-3 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4 ${className}`.trim()}\n >\n <label className=\"sr-only\" htmlFor=\"rai-prompt-textarea\">\n {ariaLabel}\n </label>\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-end\">\n <textarea\n id=\"rai-prompt-textarea\"\n rows={2}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder=\"Message…\"\n aria-label={ariaLabel}\n className=\"rai-prompt-textarea min-h-[2.75rem] w-full resize-y rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-inner outline-none ring-zinc-400/40 placeholder:text-zinc-400 focus:border-zinc-300 focus:ring-2 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-600 dark:focus:ring-zinc-600/40\"\n />\n <button\n type=\"button\"\n onClick={submit}\n className=\"inline-flex shrink-0 items-center justify-center rounded-xl bg-zinc-900 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200\"\n disabled={!draft.trim()}\n >\n Send\n </button>\n </div>\n <p className=\"mt-2 text-xs text-zinc-500 dark:text-zinc-400\">\n Enter to send · Shift+Enter for new line\n </p>\n </div>\n );\n}\n","/**\n * Adhoc: lightweight typing affordance — **ARIA live region** so screen readers\n * hear when the assistant is working (pair with `aria-busy` on parent if needed).\n */\nexport function TypingIndicator() {\n return (\n <p\n className=\"rai-typing-indicator px-3 py-2 text-sm text-zinc-500 motion-safe:animate-pulse dark:text-zinc-400\"\n role=\"status\"\n aria-live=\"polite\"\n >\n AI is typing…\n </p>\n );\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adhoc: shared chat domain types for react-ai-chat-ui consumers and hooks.
|
|
7
|
+
*/
|
|
8
|
+
/** Who produced the message in a typical user/assistant chat. */
|
|
9
|
+
type MessageRole = "user" | "assistant";
|
|
10
|
+
/** One persisted chat turn (user prompt or assistant reply). */
|
|
11
|
+
interface Message {
|
|
12
|
+
id: string;
|
|
13
|
+
role: MessageRole;
|
|
14
|
+
content: string;
|
|
15
|
+
createdAt: Date;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Adhoc: headless hook for demos and apps that only need in-memory chat.
|
|
20
|
+
*
|
|
21
|
+
* @returns Messages plus imperative send/clear helpers (stable via useCallback).
|
|
22
|
+
*/
|
|
23
|
+
declare function useChat(): {
|
|
24
|
+
messages: Message[];
|
|
25
|
+
sendMessage: (content: string) => void;
|
|
26
|
+
addAssistantMessage: (content: string) => void;
|
|
27
|
+
clearMessages: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface ChatWindowProps {
|
|
31
|
+
/** Main chat surface (e.g. message list + composer). */
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
/** Optional class for host layout integration (e.g. `h-screen`). */
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Adhoc: root flex column with **min-h-0** so nested `overflow-y-auto` regions
|
|
38
|
+
* scroll correctly inside flex parents (a common flexbox footgun).
|
|
39
|
+
*/
|
|
40
|
+
declare function ChatWindow({ children, className, }: ChatWindowProps): react_jsx_runtime.JSX.Element;
|
|
41
|
+
|
|
42
|
+
interface MessageListProps {
|
|
43
|
+
messages: Message[];
|
|
44
|
+
/** Optional slot below messages (e.g. `<TypingIndicator />`). */
|
|
45
|
+
footer?: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Adhoc: **Auto-scroll** via sentinel `scrollIntoView` — simple and reliable
|
|
49
|
+
* vs measuring scrollHeight on every paint; smooth behavior matches chat UX.
|
|
50
|
+
*/
|
|
51
|
+
declare function MessageList({ messages, footer }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
52
|
+
|
|
53
|
+
interface MessageBubbleProps {
|
|
54
|
+
message: Message;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Adhoc: **Presentation split** — user plain text vs assistant markdown keeps
|
|
58
|
+
* rendering cost and XSS surface scoped to AI paths only.
|
|
59
|
+
*/
|
|
60
|
+
declare const MessageBubble: react.NamedExoticComponent<MessageBubbleProps>;
|
|
61
|
+
|
|
62
|
+
interface PromptInputProps {
|
|
63
|
+
/** Fired with trimmed text after validation; parent owns persistence / API. */
|
|
64
|
+
onSend: (message: string) => void;
|
|
65
|
+
/** Extra classes on the outer shell (width, sticky footers, etc.). */
|
|
66
|
+
className?: string;
|
|
67
|
+
/** Accessible label for the composer. */
|
|
68
|
+
"aria-label"?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Adhoc: **Uncontrolled-to-send** textarea — state resets after send so the
|
|
72
|
+
* host does not need a controlled `value` just to clear the box.
|
|
73
|
+
*/
|
|
74
|
+
declare function PromptInput({ onSend, className, "aria-label": ariaLabel, }: PromptInputProps): react_jsx_runtime.JSX.Element;
|
|
75
|
+
|
|
76
|
+
interface AIMessageProps {
|
|
77
|
+
/** Raw assistant markdown (GFM tables, lists, fenced code, links). */
|
|
78
|
+
content: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Adhoc: Assistant markdown surface — **GFM** via `remark-gfm` (tables, task
|
|
82
|
+
* lists, strikethrough). Fenced code uses the lightweight `CodeBlock` (no Prism
|
|
83
|
+
* bundle). URLs pass through `react-markdown`’s default sanitizer.
|
|
84
|
+
*/
|
|
85
|
+
declare function AIMessage({ content }: AIMessageProps): react_jsx_runtime.JSX.Element;
|
|
86
|
+
|
|
87
|
+
interface CodeBlockProps {
|
|
88
|
+
/** Fence language tag for the label (informational only; no highlighter bundled). */
|
|
89
|
+
language: string;
|
|
90
|
+
/** Source text inside the fence. */
|
|
91
|
+
code: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adhoc: **Tokenless code fence** — trades syntax colors for a much smaller install
|
|
95
|
+
* than `react-syntax-highlighter` + Prism; apps that need highlighting can wrap
|
|
96
|
+
* this component or replace the `code` mapping in `AIMessage`.
|
|
97
|
+
*/
|
|
98
|
+
declare function CodeBlock({ language, code }: CodeBlockProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Adhoc: lightweight typing affordance — **ARIA live region** so screen readers
|
|
102
|
+
* hear when the assistant is working (pair with `aria-busy` on parent if needed).
|
|
103
|
+
*/
|
|
104
|
+
declare function TypingIndicator(): react_jsx_runtime.JSX.Element;
|
|
105
|
+
|
|
106
|
+
export { AIMessage, type AIMessageProps, ChatWindow, type ChatWindowProps, CodeBlock, type CodeBlockProps, type Message, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, PromptInput, type PromptInputProps, TypingIndicator, useChat };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adhoc: shared chat domain types for react-ai-chat-ui consumers and hooks.
|
|
7
|
+
*/
|
|
8
|
+
/** Who produced the message in a typical user/assistant chat. */
|
|
9
|
+
type MessageRole = "user" | "assistant";
|
|
10
|
+
/** One persisted chat turn (user prompt or assistant reply). */
|
|
11
|
+
interface Message {
|
|
12
|
+
id: string;
|
|
13
|
+
role: MessageRole;
|
|
14
|
+
content: string;
|
|
15
|
+
createdAt: Date;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Adhoc: headless hook for demos and apps that only need in-memory chat.
|
|
20
|
+
*
|
|
21
|
+
* @returns Messages plus imperative send/clear helpers (stable via useCallback).
|
|
22
|
+
*/
|
|
23
|
+
declare function useChat(): {
|
|
24
|
+
messages: Message[];
|
|
25
|
+
sendMessage: (content: string) => void;
|
|
26
|
+
addAssistantMessage: (content: string) => void;
|
|
27
|
+
clearMessages: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface ChatWindowProps {
|
|
31
|
+
/** Main chat surface (e.g. message list + composer). */
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
/** Optional class for host layout integration (e.g. `h-screen`). */
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Adhoc: root flex column with **min-h-0** so nested `overflow-y-auto` regions
|
|
38
|
+
* scroll correctly inside flex parents (a common flexbox footgun).
|
|
39
|
+
*/
|
|
40
|
+
declare function ChatWindow({ children, className, }: ChatWindowProps): react_jsx_runtime.JSX.Element;
|
|
41
|
+
|
|
42
|
+
interface MessageListProps {
|
|
43
|
+
messages: Message[];
|
|
44
|
+
/** Optional slot below messages (e.g. `<TypingIndicator />`). */
|
|
45
|
+
footer?: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Adhoc: **Auto-scroll** via sentinel `scrollIntoView` — simple and reliable
|
|
49
|
+
* vs measuring scrollHeight on every paint; smooth behavior matches chat UX.
|
|
50
|
+
*/
|
|
51
|
+
declare function MessageList({ messages, footer }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
52
|
+
|
|
53
|
+
interface MessageBubbleProps {
|
|
54
|
+
message: Message;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Adhoc: **Presentation split** — user plain text vs assistant markdown keeps
|
|
58
|
+
* rendering cost and XSS surface scoped to AI paths only.
|
|
59
|
+
*/
|
|
60
|
+
declare const MessageBubble: react.NamedExoticComponent<MessageBubbleProps>;
|
|
61
|
+
|
|
62
|
+
interface PromptInputProps {
|
|
63
|
+
/** Fired with trimmed text after validation; parent owns persistence / API. */
|
|
64
|
+
onSend: (message: string) => void;
|
|
65
|
+
/** Extra classes on the outer shell (width, sticky footers, etc.). */
|
|
66
|
+
className?: string;
|
|
67
|
+
/** Accessible label for the composer. */
|
|
68
|
+
"aria-label"?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Adhoc: **Uncontrolled-to-send** textarea — state resets after send so the
|
|
72
|
+
* host does not need a controlled `value` just to clear the box.
|
|
73
|
+
*/
|
|
74
|
+
declare function PromptInput({ onSend, className, "aria-label": ariaLabel, }: PromptInputProps): react_jsx_runtime.JSX.Element;
|
|
75
|
+
|
|
76
|
+
interface AIMessageProps {
|
|
77
|
+
/** Raw assistant markdown (GFM tables, lists, fenced code, links). */
|
|
78
|
+
content: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Adhoc: Assistant markdown surface — **GFM** via `remark-gfm` (tables, task
|
|
82
|
+
* lists, strikethrough). Fenced code uses the lightweight `CodeBlock` (no Prism
|
|
83
|
+
* bundle). URLs pass through `react-markdown`’s default sanitizer.
|
|
84
|
+
*/
|
|
85
|
+
declare function AIMessage({ content }: AIMessageProps): react_jsx_runtime.JSX.Element;
|
|
86
|
+
|
|
87
|
+
interface CodeBlockProps {
|
|
88
|
+
/** Fence language tag for the label (informational only; no highlighter bundled). */
|
|
89
|
+
language: string;
|
|
90
|
+
/** Source text inside the fence. */
|
|
91
|
+
code: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adhoc: **Tokenless code fence** — trades syntax colors for a much smaller install
|
|
95
|
+
* than `react-syntax-highlighter` + Prism; apps that need highlighting can wrap
|
|
96
|
+
* this component or replace the `code` mapping in `AIMessage`.
|
|
97
|
+
*/
|
|
98
|
+
declare function CodeBlock({ language, code }: CodeBlockProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Adhoc: lightweight typing affordance — **ARIA live region** so screen readers
|
|
102
|
+
* hear when the assistant is working (pair with `aria-busy` on parent if needed).
|
|
103
|
+
*/
|
|
104
|
+
declare function TypingIndicator(): react_jsx_runtime.JSX.Element;
|
|
105
|
+
|
|
106
|
+
export { AIMessage, type AIMessageProps, ChatWindow, type ChatWindowProps, CodeBlock, type CodeBlockProps, type Message, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, PromptInput, type PromptInputProps, TypingIndicator, useChat };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {memo,useState,useCallback,useRef,useEffect}from'react';import {jsx,Fragment,jsxs}from'react/jsx-runtime';import M from'react-markdown';import P from'remark-gfm';function u(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function K(){let[e,t]=useState([]),o=useCallback(s=>{let c=s.trim();c&&t(i=>[...i,{id:u(),role:"user",content:c,createdAt:new Date}]);},[]),a=useCallback(s=>{t(c=>[...c,{id:u(),role:"assistant",content:s,createdAt:new Date}]);},[]),n=useCallback(()=>{t([]);},[]);return {messages:e,sendMessage:o,addAssistantMessage:a,clearMessages:n}}function q({children:e,className:t=""}){return jsx("section",{className:`rai-chat-window flex h-full min-h-0 w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-zinc-50 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-950 ${t}`.trim(),"aria-label":"Chat",children:e})}function C(e){return e.toLowerCase().replace(/^language-/,"").trim()||"text"}function b({language:e,code:t}){let o=C(e),[a,n]=useState(false),s=useCallback(async()=>{try{await navigator.clipboard.writeText(t),n(!0),window.setTimeout(()=>n(!1),2e3);}catch{n(false);}},[t]);return jsxs("figure",{className:"rai-code-block my-3 overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-950","aria-label":`Code sample, ${o}`,children:[jsxs("figcaption",{className:"flex items-center justify-between gap-2 border-b border-zinc-200 bg-zinc-50 px-3 py-2 text-xs font-medium text-zinc-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300",children:[jsx("span",{className:"truncate font-mono",children:o}),jsx("button",{type:"button",onClick:s,className:"shrink-0 rounded-lg border border-zinc-200 bg-white px-2.5 py-1 text-xs font-medium text-zinc-800 transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700",children:a?"Copied":"Copy"})]}),jsx("div",{className:"overflow-x-auto border-t border-zinc-100 bg-zinc-50/80 dark:border-zinc-800 dark:bg-zinc-900/40",children:jsx("pre",{className:"m-0 p-4",children:jsx("code",{className:"block font-mono text-[0.8125rem] leading-relaxed whitespace-pre text-zinc-900 dark:text-zinc-100",children:t})})})]})}function I({className:e,children:t,...o}){return jsx("code",{className:`rounded-md bg-zinc-100 px-1.5 py-0.5 font-mono text-[0.9em] text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100 ${e??""}`,...o,children:t})}function R({className:e,children:t,...o}){let a=String(t).replace(/\n$/,""),n=/language-([\w+-]+)/.exec(e||"");return n?jsx(b,{language:n[1],code:a}):a.includes(`
|
|
2
|
+
`)?jsx(b,{language:"text",code:a}):jsx(I,{className:e,...o,children:t})}var D={pre:({children:e})=>jsx(Fragment,{children:e}),code:R,p:({children:e})=>jsx("p",{className:"mb-3 last:mb-0 leading-relaxed text-zinc-800 dark:text-zinc-200",children:e}),a:({href:e,children:t,...o})=>jsx("a",{href:e,className:"font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-2 hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30",target:"_blank",rel:"noopener noreferrer",...o,children:t}),ul:({children:e})=>jsx("ul",{className:"mb-3 list-disc space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200",children:e}),ol:({children:e})=>jsx("ol",{className:"mb-3 list-decimal space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200",children:e}),li:({children:e})=>jsx("li",{className:"leading-relaxed",children:e}),h1:({children:e})=>jsx("h1",{className:"mb-3 mt-4 text-xl font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),h2:({children:e})=>jsx("h2",{className:"mb-2 mt-4 text-lg font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),h3:({children:e})=>jsx("h3",{className:"mb-2 mt-3 text-base font-semibold text-zinc-900 first:mt-0 dark:text-zinc-50",children:e}),blockquote:({children:e})=>jsx("blockquote",{className:"mb-3 border-l-4 border-zinc-300 pl-4 text-zinc-600 italic dark:border-zinc-600 dark:text-zinc-400",children:e}),hr:()=>jsx("hr",{className:"my-4 border-zinc-200 dark:border-zinc-700"}),table:({children:e})=>jsx("div",{className:"my-3 w-full overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-700",children:jsx("table",{className:"w-full min-w-[20rem] border-collapse text-left text-sm",children:e})}),thead:({children:e})=>jsx("thead",{className:"bg-zinc-100 dark:bg-zinc-800/80",children:e}),tbody:({children:e})=>jsx("tbody",{children:e}),tr:({children:e})=>jsx("tr",{className:"border-b border-zinc-200 last:border-b-0 dark:border-zinc-700",children:e}),th:({children:e})=>jsx("th",{className:"px-3 py-2 font-semibold text-zinc-900 dark:text-zinc-100",children:e}),td:({children:e})=>jsx("td",{className:"px-3 py-2 text-zinc-800 dark:text-zinc-200",children:e}),strong:({children:e})=>jsx("strong",{className:"font-semibold text-zinc-900 dark:text-zinc-50",children:e})};function x({content:e}){return jsx("div",{className:"rai-ai-message max-w-none text-sm",children:jsx(M,{remarkPlugins:[P],components:D,children:e})})}var g=memo(function({message:t}){let o=t.role==="user";return jsx("article",{className:`rai-message-bubble flex w-full ${o?"justify-end":"justify-start"}`,children:jsx("div",{className:o?"max-w-[min(100%,42rem)] rounded-2xl rounded-br-md bg-zinc-900 px-4 py-2.5 text-sm leading-relaxed text-zinc-50 shadow-sm dark:bg-zinc-100 dark:text-zinc-900":"max-w-[min(100%,42rem)] rounded-2xl rounded-bl-md border border-zinc-200/80 bg-white px-4 py-3 text-sm leading-relaxed text-zinc-800 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-900 dark:text-zinc-100",children:o?jsx("p",{className:"whitespace-pre-wrap break-words",children:t.content}):jsx(x,{content:t.content})})})});function ae({messages:e,footer:t}){let o=useRef(null);return useEffect(()=>{o.current?.scrollIntoView({behavior:"smooth",block:"end"});},[e,t]),jsxs("div",{className:"rai-message-list flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-4 sm:px-4",role:"log","aria-live":"polite","aria-relevant":"additions",children:[e.map(a=>jsx(g,{message:a},a.id)),t,jsx("div",{ref:o,className:"h-px shrink-0","aria-hidden":true})]})}function ce({onSend:e,className:t="","aria-label":o="Message input"}){let[a,n]=useState(""),s=useCallback(()=>{let i=a.trim();i&&(e(i),n(""));},[a,e]),c=useCallback(i=>{i.key==="Enter"&&(i.shiftKey||(i.preventDefault(),s()));},[s]);return jsxs("div",{className:`rai-prompt-input border-t border-zinc-200/80 bg-white/90 p-3 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4 ${t}`.trim(),children:[jsx("label",{className:"sr-only",htmlFor:"rai-prompt-textarea",children:o}),jsxs("div",{className:"flex flex-col gap-2 sm:flex-row sm:items-end",children:[jsx("textarea",{id:"rai-prompt-textarea",rows:2,value:a,onChange:i=>n(i.target.value),onKeyDown:c,placeholder:"Message\u2026","aria-label":o,className:"rai-prompt-textarea min-h-[2.75rem] w-full resize-y rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-inner outline-none ring-zinc-400/40 placeholder:text-zinc-400 focus:border-zinc-300 focus:ring-2 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-600 dark:focus:ring-zinc-600/40"}),jsx("button",{type:"button",onClick:s,className:"inline-flex shrink-0 items-center justify-center rounded-xl bg-zinc-900 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200",disabled:!a.trim(),children:"Send"})]}),jsx("p",{className:"mt-2 text-xs text-zinc-500 dark:text-zinc-400",children:"Enter to send \xB7 Shift+Enter for new line"})]})}function me(){return jsx("p",{className:"rai-typing-indicator px-3 py-2 text-sm text-zinc-500 motion-safe:animate-pulse dark:text-zinc-400",role:"status","aria-live":"polite",children:"AI is typing\u2026"})}export{x as AIMessage,q as ChatWindow,b as CodeBlock,g as MessageBubble,ae as MessageList,ce as PromptInput,me as TypingIndicator,K as useChat};//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useChat.ts","../src/components/ChatWindow.tsx","../src/components/CodeBlock.tsx","../src/components/AIMessage.tsx","../src/components/MessageBubble.tsx","../src/components/MessageList.tsx","../src/components/PromptInput.tsx","../src/components/TypingIndicator.tsx"],"names":["createId","useChat","messages","setMessages","useState","sendMessage","useCallback","content","trimmed","prev","addAssistantMessage","clearMessages","ChatWindow","children","className","jsx","formatLanguageLabel","raw","CodeBlock","language","code","label","copied","setCopied","handleCopy","jsxs","InlineCode","rest","MarkdownCode","text","match","markdownComponents","Fragment","href","AIMessage","ReactMarkdown","remarkGfm","MessageBubble","memo","message","isUser","MessageList","footer","endRef","useRef","useEffect","m","PromptInput","onSend","ariaLabel","draft","setDraft","submit","onKeyDown","e","TypingIndicator"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useState } from \"react\";\n\nimport type { Message } from \"../types/chat\";\n\n/**\n * Adhoc: minimal local chat state — **Lifted state** pattern so parents can\n * swap this for server-backed stores without changing UI components.\n */\nfunction createId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Adhoc: headless hook for demos and apps that only need in-memory chat.\n *\n * @returns Messages plus imperative send/clear helpers (stable via useCallback).\n */\nexport function useChat() {\n const [messages, setMessages] = useState<Message[]>([]);\n\n const sendMessage = useCallback((content: string) => {\n const trimmed = content.trim();\n if (!trimmed) {\n return;\n }\n setMessages((prev) => [\n ...prev,\n {\n id: createId(),\n role: \"user\",\n content: trimmed,\n createdAt: new Date(),\n },\n ]);\n }, []);\n\n const addAssistantMessage = useCallback((content: string) => {\n setMessages((prev) => [\n ...prev,\n {\n id: createId(),\n role: \"assistant\",\n content,\n createdAt: new Date(),\n },\n ]);\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n }, []);\n\n return {\n messages,\n sendMessage,\n addAssistantMessage,\n clearMessages,\n };\n}\n","import type { ReactNode } from \"react\";\n\nexport interface ChatWindowProps {\n /** Main chat surface (e.g. message list + composer). */\n children: ReactNode;\n /** Optional class for host layout integration (e.g. `h-screen`). */\n className?: string;\n}\n\n/**\n * Adhoc: root flex column with **min-h-0** so nested `overflow-y-auto` regions\n * scroll correctly inside flex parents (a common flexbox footgun).\n */\nexport function ChatWindow({\n children,\n className = \"\",\n}: ChatWindowProps) {\n return (\n <section\n className={`rai-chat-window flex h-full min-h-0 w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-zinc-50 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-950 ${className}`.trim()}\n aria-label=\"Chat\"\n >\n {children}\n </section>\n );\n}\n","import { useCallback, useState } from \"react\";\n\nexport interface CodeBlockProps {\n /** Fence language tag for the label (informational only; no highlighter bundled). */\n language: string;\n /** Source text inside the fence. */\n code: string;\n}\n\n/** Adhoc: normalize fence labels for display without pulling a grammar registry. */\nfunction formatLanguageLabel(raw: string): string {\n const id = raw.toLowerCase().replace(/^language-/, \"\").trim();\n return id || \"text\";\n}\n\n/**\n * Adhoc: **Tokenless code fence** — trades syntax colors for a much smaller install\n * than `react-syntax-highlighter` + Prism; apps that need highlighting can wrap\n * this component or replace the `code` mapping in `AIMessage`.\n */\nexport function CodeBlock({ language, code }: CodeBlockProps) {\n const label = formatLanguageLabel(language);\n const [copied, setCopied] = useState(false);\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n window.setTimeout(() => setCopied(false), 2000);\n } catch {\n setCopied(false);\n }\n }, [code]);\n\n return (\n <figure\n className=\"rai-code-block my-3 overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-950\"\n aria-label={`Code sample, ${label}`}\n >\n <figcaption className=\"flex items-center justify-between gap-2 border-b border-zinc-200 bg-zinc-50 px-3 py-2 text-xs font-medium text-zinc-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300\">\n <span className=\"truncate font-mono\">{label}</span>\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"shrink-0 rounded-lg border border-zinc-200 bg-white px-2.5 py-1 text-xs font-medium text-zinc-800 transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700\"\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </figcaption>\n <div className=\"overflow-x-auto border-t border-zinc-100 bg-zinc-50/80 dark:border-zinc-800 dark:bg-zinc-900/40\">\n <pre className=\"m-0 p-4\">\n <code className=\"block font-mono text-[0.8125rem] leading-relaxed whitespace-pre text-zinc-900 dark:text-zinc-100\">\n {code}\n </code>\n </pre>\n </div>\n </figure>\n );\n}\n","import type { ComponentPropsWithoutRef } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { CodeBlock } from \"./CodeBlock\";\n\nexport interface AIMessageProps {\n /** Raw assistant markdown (GFM tables, lists, fenced code, links). */\n content: string;\n}\n\nfunction InlineCode({\n className,\n children,\n ...rest\n}: ComponentPropsWithoutRef<\"code\">) {\n return (\n <code\n className={`rounded-md bg-zinc-100 px-1.5 py-0.5 font-mono text-[0.9em] text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100 ${className ?? \"\"}`}\n {...rest}\n >\n {children}\n </code>\n );\n}\n\n/**\n * Adhoc: `react-markdown` emits `pre > code` for fences — we detect `language-*`\n * or multiline bodies so **inline** `code` stays compact.\n */\nfunction MarkdownCode({\n className,\n children,\n ...rest\n}: ComponentPropsWithoutRef<\"code\">) {\n const text = String(children).replace(/\\n$/, \"\");\n const match = /language-([\\w+-]+)/.exec(className || \"\");\n if (match) {\n return (\n <CodeBlock\n language={match[1]}\n code={text}\n />\n );\n }\n if (text.includes(\"\\n\")) {\n return (\n <CodeBlock\n language=\"text\"\n code={text}\n />\n );\n }\n return (\n <InlineCode className={className} {...rest}>\n {children}\n </InlineCode>\n );\n}\n\nconst markdownComponents: Components = {\n pre: ({ children }) => <>{children}</>,\n code: MarkdownCode,\n p: ({ children }) => (\n <p className=\"mb-3 last:mb-0 leading-relaxed text-zinc-800 dark:text-zinc-200\">\n {children}\n </p>\n ),\n a: ({ href, children, ...rest }) => (\n <a\n href={href}\n className=\"font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-2 hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...rest}\n >\n {children}\n </a>\n ),\n ul: ({ children }) => (\n <ul className=\"mb-3 list-disc space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200\">\n {children}\n </ul>\n ),\n ol: ({ children }) => (\n <ol className=\"mb-3 list-decimal space-y-1 pl-5 text-zinc-800 last:mb-0 dark:text-zinc-200\">\n {children}\n </ol>\n ),\n li: ({ children }) => <li className=\"leading-relaxed\">{children}</li>,\n h1: ({ children }) => (\n <h1 className=\"mb-3 mt-4 text-xl font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h1>\n ),\n h2: ({ children }) => (\n <h2 className=\"mb-2 mt-4 text-lg font-semibold tracking-tight text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h2>\n ),\n h3: ({ children }) => (\n <h3 className=\"mb-2 mt-3 text-base font-semibold text-zinc-900 first:mt-0 dark:text-zinc-50\">\n {children}\n </h3>\n ),\n blockquote: ({ children }) => (\n <blockquote className=\"mb-3 border-l-4 border-zinc-300 pl-4 text-zinc-600 italic dark:border-zinc-600 dark:text-zinc-400\">\n {children}\n </blockquote>\n ),\n hr: () => (\n <hr className=\"my-4 border-zinc-200 dark:border-zinc-700\" />\n ),\n table: ({ children }) => (\n <div className=\"my-3 w-full overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-700\">\n <table className=\"w-full min-w-[20rem] border-collapse text-left text-sm\">\n {children}\n </table>\n </div>\n ),\n thead: ({ children }) => (\n <thead className=\"bg-zinc-100 dark:bg-zinc-800/80\">{children}</thead>\n ),\n tbody: ({ children }) => <tbody>{children}</tbody>,\n tr: ({ children }) => (\n <tr className=\"border-b border-zinc-200 last:border-b-0 dark:border-zinc-700\">\n {children}\n </tr>\n ),\n th: ({ children }) => (\n <th className=\"px-3 py-2 font-semibold text-zinc-900 dark:text-zinc-100\">\n {children}\n </th>\n ),\n td: ({ children }) => (\n <td className=\"px-3 py-2 text-zinc-800 dark:text-zinc-200\">\n {children}\n </td>\n ),\n strong: ({ children }) => (\n <strong className=\"font-semibold text-zinc-900 dark:text-zinc-50\">\n {children}\n </strong>\n ),\n};\n\n/**\n * Adhoc: Assistant markdown surface — **GFM** via `remark-gfm` (tables, task\n * lists, strikethrough). Fenced code uses the lightweight `CodeBlock` (no Prism\n * bundle). URLs pass through `react-markdown`’s default sanitizer.\n */\nexport function AIMessage({ content }: AIMessageProps) {\n return (\n <div className=\"rai-ai-message max-w-none text-sm\">\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={markdownComponents}\n >\n {content}\n </ReactMarkdown>\n </div>\n );\n}\n","import { memo } from \"react\";\n\nimport type { Message } from \"../types/chat\";\nimport { AIMessage } from \"./AIMessage\";\n\nexport interface MessageBubbleProps {\n message: Message;\n}\n\n/**\n * Adhoc: **Presentation split** — user plain text vs assistant markdown keeps\n * rendering cost and XSS surface scoped to AI paths only.\n */\nexport const MessageBubble = memo(function MessageBubble({\n message,\n}: MessageBubbleProps) {\n const isUser = message.role === \"user\";\n\n return (\n <article\n className={`rai-message-bubble flex w-full ${isUser ? \"justify-end\" : \"justify-start\"}`}\n >\n <div\n className={\n isUser\n ? \"max-w-[min(100%,42rem)] rounded-2xl rounded-br-md bg-zinc-900 px-4 py-2.5 text-sm leading-relaxed text-zinc-50 shadow-sm dark:bg-zinc-100 dark:text-zinc-900\"\n : \"max-w-[min(100%,42rem)] rounded-2xl rounded-bl-md border border-zinc-200/80 bg-white px-4 py-3 text-sm leading-relaxed text-zinc-800 shadow-sm dark:border-zinc-700/80 dark:bg-zinc-900 dark:text-zinc-100\"\n }\n >\n {isUser ? (\n <p className=\"whitespace-pre-wrap break-words\">{message.content}</p>\n ) : (\n <AIMessage content={message.content} />\n )}\n </div>\n </article>\n );\n});\n","import { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport type { Message } from \"../types/chat\";\nimport { MessageBubble } from \"./MessageBubble\";\n\nexport interface MessageListProps {\n messages: Message[];\n /** Optional slot below messages (e.g. `<TypingIndicator />`). */\n footer?: ReactNode;\n}\n\n/**\n * Adhoc: **Auto-scroll** via sentinel `scrollIntoView` — simple and reliable\n * vs measuring scrollHeight on every paint; smooth behavior matches chat UX.\n */\nexport function MessageList({ messages, footer }: MessageListProps) {\n const endRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({\n behavior: \"smooth\",\n block: \"end\",\n });\n }, [messages, footer]);\n\n return (\n <div\n className=\"rai-message-list flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-4 sm:px-4\"\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n >\n {messages.map((m) => (\n <MessageBubble key={m.id} message={m} />\n ))}\n {footer}\n <div ref={endRef} className=\"h-px shrink-0\" aria-hidden />\n </div>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\n\nexport interface PromptInputProps {\n /** Fired with trimmed text after validation; parent owns persistence / API. */\n onSend: (message: string) => void;\n /** Extra classes on the outer shell (width, sticky footers, etc.). */\n className?: string;\n /** Accessible label for the composer. */\n \"aria-label\"?: string;\n}\n\n/**\n * Adhoc: **Uncontrolled-to-send** textarea — state resets after send so the\n * host does not need a controlled `value` just to clear the box.\n */\nexport function PromptInput({\n onSend,\n className = \"\",\n \"aria-label\": ariaLabel = \"Message input\",\n}: PromptInputProps) {\n const [draft, setDraft] = useState(\"\");\n\n const submit = useCallback(() => {\n const trimmed = draft.trim();\n if (!trimmed) {\n return;\n }\n onSend(trimmed);\n setDraft(\"\");\n }, [draft, onSend]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key !== \"Enter\") {\n return;\n }\n if (e.shiftKey) {\n return;\n }\n e.preventDefault();\n submit();\n },\n [submit],\n );\n\n return (\n <div\n className={`rai-prompt-input border-t border-zinc-200/80 bg-white/90 p-3 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4 ${className}`.trim()}\n >\n <label className=\"sr-only\" htmlFor=\"rai-prompt-textarea\">\n {ariaLabel}\n </label>\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-end\">\n <textarea\n id=\"rai-prompt-textarea\"\n rows={2}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder=\"Message…\"\n aria-label={ariaLabel}\n className=\"rai-prompt-textarea min-h-[2.75rem] w-full resize-y rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-inner outline-none ring-zinc-400/40 placeholder:text-zinc-400 focus:border-zinc-300 focus:ring-2 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-600 dark:focus:ring-zinc-600/40\"\n />\n <button\n type=\"button\"\n onClick={submit}\n className=\"inline-flex shrink-0 items-center justify-center rounded-xl bg-zinc-900 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-400 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200\"\n disabled={!draft.trim()}\n >\n Send\n </button>\n </div>\n <p className=\"mt-2 text-xs text-zinc-500 dark:text-zinc-400\">\n Enter to send · Shift+Enter for new line\n </p>\n </div>\n );\n}\n","/**\n * Adhoc: lightweight typing affordance — **ARIA live region** so screen readers\n * hear when the assistant is working (pair with `aria-busy` on parent if needed).\n */\nexport function TypingIndicator() {\n return (\n <p\n className=\"rai-typing-indicator px-3 py-2 text-sm text-zinc-500 motion-safe:animate-pulse dark:text-zinc-400\"\n role=\"status\"\n aria-live=\"polite\"\n >\n AI is typing…\n </p>\n );\n}\n"]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-white:#fff;--spacing:.25rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.sticky{position:sticky}.m-0{margin:calc(var(--spacing) * 0)}.mx-auto{margin-inline:auto}.my-3{margin-block:calc(var(--spacing) * 3)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.h-dvh{height:100dvh}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-\[720px\]{max-height:720px}.max-h-\[800px\]{max-height:800px}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[2\.75rem\]{min-height:2.75rem}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-\[min\(100\%\,42rem\)\]{max-width:min(100%,42rem)}.max-w-none{max-width:none}.min-w-\[20rem\]{min-width:20rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.resize-y{resize:vertical}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-200\/80{border-color:#e4e4e7cc}@supports (color:color-mix(in lab, red, red)){.border-zinc-200\/80{border-color:color-mix(in oklab, var(--color-zinc-200) 80%, transparent)}}.border-zinc-300{border-color:var(--color-zinc-300)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-50\/80{background-color:#fafafacc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-50\/80{background-color:color-mix(in oklab, var(--color-zinc-50) 80%, transparent)}}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.9em\]{font-size:.9em}.text-\[0\.8125rem\]{font-size:.8125rem}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-600{color:var(--color-blue-600)}.text-white{color:var(--color-white)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.italic{font-style:italic}.underline{text-decoration-line:underline}.decoration-blue-600\/30{text-decoration-color:#155dfc4d}@supports (color:color-mix(in lab, red, red)){.decoration-blue-600\/30{-webkit-text-decoration-color:color-mix(in oklab, var(--color-blue-600) 30%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-blue-600) 30%, transparent);text-decoration-color:color-mix(in oklab, var(--color-blue-600) 30%, transparent)}}.underline-offset-2{text-underline-offset:2px}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-zinc-400\/40{--tw-ring-color:#9f9fa966}@supports (color:color-mix(in lab, red, red)){.ring-zinc-400\/40{--tw-ring-color:color-mix(in oklab, var(--color-zinc-400) 40%, transparent)}}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.first\:mt-0:first-child{margin-top:calc(var(--spacing) * 0)}.last\:mb-0:last-child{margin-bottom:calc(var(--spacing) * 0)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:decoration-blue-600:hover{-webkit-text-decoration-color:var(--color-blue-600);-webkit-text-decoration-color:var(--color-blue-600);text-decoration-color:var(--color-blue-600)}}.focus\:border-zinc-300:focus{border-color:var(--color-zinc-300)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-zinc-400:focus-visible{outline-color:var(--color-zinc-400)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (prefers-reduced-motion:no-preference){.motion-safe\:animate-pulse{animation:var(--animate-pulse)}}@media (min-width:40rem){.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}}@media (prefers-color-scheme:dark){.dark\:border-zinc-600{border-color:var(--color-zinc-600)}.dark\:border-zinc-700{border-color:var(--color-zinc-700)}.dark\:border-zinc-700\/80{border-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.dark\:border-zinc-700\/80{border-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.dark\:border-zinc-800{border-color:var(--color-zinc-800)}.dark\:bg-zinc-100{background-color:var(--color-zinc-100)}.dark\:bg-zinc-800{background-color:var(--color-zinc-800)}.dark\:bg-zinc-800\/80{background-color:#27272acc}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-800\/80{background-color:color-mix(in oklab, var(--color-zinc-800) 80%, transparent)}}.dark\:bg-zinc-900{background-color:var(--color-zinc-900)}.dark\:bg-zinc-900\/40{background-color:#18181b66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-900\/40{background-color:color-mix(in oklab, var(--color-zinc-900) 40%, transparent)}}.dark\:bg-zinc-950{background-color:var(--color-zinc-950)}.dark\:bg-zinc-950\/90{background-color:#09090be6}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-950\/90{background-color:color-mix(in oklab, var(--color-zinc-950) 90%, transparent)}}.dark\:text-blue-400{color:var(--color-blue-400)}.dark\:text-zinc-50{color:var(--color-zinc-50)}.dark\:text-zinc-100{color:var(--color-zinc-100)}.dark\:text-zinc-200{color:var(--color-zinc-200)}.dark\:text-zinc-300{color:var(--color-zinc-300)}.dark\:text-zinc-400{color:var(--color-zinc-400)}.dark\:text-zinc-900{color:var(--color-zinc-900)}.dark\:decoration-blue-400\/30{text-decoration-color:#54a2ff4d}@supports (color:color-mix(in lab, red, red)){.dark\:decoration-blue-400\/30{-webkit-text-decoration-color:color-mix(in oklab, var(--color-blue-400) 30%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-blue-400) 30%, transparent);text-decoration-color:color-mix(in oklab, var(--color-blue-400) 30%, transparent)}}.dark\:placeholder\:text-zinc-500::placeholder{color:var(--color-zinc-500)}@media (hover:hover){.dark\:hover\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.dark\:hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}}.dark\:focus\:border-zinc-600:focus{border-color:var(--color-zinc-600)}.dark\:focus\:ring-zinc-600\/40:focus{--tw-ring-color:#52525c66}@supports (color:color-mix(in lab, red, red)){.dark\:focus\:ring-zinc-600\/40:focus{--tw-ring-color:color-mix(in oklab, var(--color-zinc-600) 40%, transparent)}}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes pulse{50%{opacity:.5}}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-ai-chat-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable AI chat UI primitives for React (ChatGPT-style) — messages, markdown, code blocks, and composer.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react",
|
|
9
|
+
"ai",
|
|
10
|
+
"chat",
|
|
11
|
+
"chatgpt",
|
|
12
|
+
"markdown",
|
|
13
|
+
"tailwind",
|
|
14
|
+
"components"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"**/*.css"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"require": "./dist/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./styles.css": "./dist/styles.css"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup && npx @tailwindcss/cli -i ./src/styles/index.css -o ./dist/styles.css --minify",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"demo": "npm run build && npm install --prefix examples/demo && npm run dev --prefix examples/demo",
|
|
38
|
+
"test": "npm run typecheck && npm run build && npm install --prefix examples/demo && npm run build --prefix examples/demo",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"prepublishOnly": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react": "^19.0.0",
|
|
44
|
+
"react-dom": "^19.0.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"react-markdown": "^10.1.0",
|
|
48
|
+
"remark-gfm": "^4.0.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@tailwindcss/cli": "^4.3.0",
|
|
52
|
+
"@types/react": "^19.2.15",
|
|
53
|
+
"@types/react-dom": "^19.2.3",
|
|
54
|
+
"react": "^19.2.6",
|
|
55
|
+
"react-dom": "^19.2.6",
|
|
56
|
+
"tailwindcss": "^4.3.0",
|
|
57
|
+
"tsup": "^8.5.1",
|
|
58
|
+
"typescript": "^5.8.3"
|
|
59
|
+
}
|
|
60
|
+
}
|