个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A chat interface with rich formatting: Markdown, LaTeX math, and
syntax-highlighted code in every language, rendered as you type.
The interface is the leptos-rich-chat crate, a set
of Leptos components published to crates.io. Its
README is the full guide; this page is the
short one. A live demo is at.查看完整 README ↗A chat interface with rich formatting: Markdown, LaTeX math, and syntax-highlighted code in every language, rendered as you type.
The interface is the leptos-rich-chat crate, a set
of Leptos components published to crates.io. Its
README is the full guide; this page is the
short one. A live demo is at https://wpm.github.io/Rich-Chat/.
[dependencies]
leptos = { version = "0.8", features = ["csr"] }
leptos-rich-chat = "0.2"
use leptos::prelude::*;
use leptos_rich_chat::{Chat, Message, RichChatStyle};
#[component]
fn App() -> impl IntoView {
let messages = RwSignal::new(Vec::<Message>::new());
let send = move |text: String| {
let id = messages.read_untracked().len().to_string();
messages.update(|all| all.push(Message::new(id, "user", text)));
};
view! {
<RichChatStyle />
<Chat messages=messages on_send=send />
}
}
Chat is the whole window, a scrolling transcript over a text box, and
fills its container, so give the parent a height. RichChatStyle
injects the stylesheets and fonts once; place it anywhere. A message is
a Message: Message::new(id, name, markdown) for finished text,
Message::streaming(id, name, signal) for text still arriving, which
renders as a draft while it grows, and Message::view(id, name, view)
for a body you draw yourself, in place of the bubble. A streaming
message whose text is still empty is thinking: its bubble shows three
bobbing dots until the first character arrives, then fills with text in
place. A host with nothing to send, a viewer of a conversation between
others, renders Transcript instead: Chat without the text box.
Everything about the crate is set in one of four places: props on
Chat, props on RichChatStyle, CSS custom properties, and Cargo
features.
Chat props| Prop | Type | Default | Controls |
|---|---|---|---|
messages | Signal<Vec<Message>> | required | The transcript, oldest first. |
on_send | Callback<String> | required | Receives the text of each message sent. |
draft | RwSignal<String> | the composer's own | The text in the box, held by you: to prefill it, read it, or keep it across unmounting. |
placeholder | String | "Write a message…" | The text box's placeholder. |
hint | String | explains the keys | The line under the text box. "" leaves it out. |
preview | bool | true | Whether the composer shows a live preview of the draft. |
preview_expanded | bool | false | Whether the preview starts expanded rather than collapsed to its heading. The reader's toggle holds after that. |
preview_label | String | "Preview" | The heading over the preview. |
preview_name | Signal<String> | "", the theme's tint colors | Whose bubble the preview looks like, by name. |
send | view function | the word "Send" | The send button's content, so it can be an icon. |
empty | String | nothing | Shown in the transcript while it has no messages. |
show_names | Signal<bool> | false | Writes each message's name over its bubble, as a group chat does. |
sender | Callback<String, AnyView> | the name as spelled | Draws what goes over a bubble, given its name: a name and a role, an avatar, a badge. |
Signal-typed props take a plain value or a signal, so busy=true and
busy=is_waiting both work. Attributes on Chat land on the window,
.rc-chat; a host that needs an attribute on the text box itself
composes the window from the pieces below.
RenderOptions is a plain struct; build it with ..Default::default():
| Field | Default | Off |
|---|---|---|
math | true | $…$ and $$…$$ are text. |
highlight | true | Fenced code is plain. Also needs the highlight feature. |
images | true | Only the alt text shows. Off keeps remote images from revealing a reader's address. |
smart_punctuation | false | On, straight quotes, -- and ... become typographic characters. |
draft | false | On, constructs left open at the end are closed for display. |
RichChatStyle props| Prop | Type | Default | Controls |
|---|---|---|---|
theme | bool | true | The default look: colors, fonts, spacing, radii. Off, you style the rc-* classes yourself. |
highlight | bool | true | The code colors. Off, serve your own; the crate's highlight_css example generates one from any syntect theme. |
fonts | bool | true | The @font-face rules for the bundled math fonts. |
names | Signal<Names> | user right, assistant left | Where each name's bubbles sit and what colors they have. |
A Names table gives every name a Look: a Position (Left,
Center, Right) and, optionally, a background and a text color, both
as CSS values, so var(--…) and light-dark(…, …) work:
use leptos_rich_chat::{Look, Names, Position};
let names = Names::none()
.name("me", Look::at(Position::Right).background("#ddf4ff"))
.name("alice", Look::at(Position::Left).background("var(--alice)"))
.name("notice", Look::at(Position::Center).background("transparent").foreground("var(--rc-muted)"));
view! { <RichChatStyle names=names /> }
A name with no entry gets the plain bubble on the left. A Look can
also carry custom properties, .property("--side", "var(--good)"), set
on the name's messages for your own rules to read, such as
.rc-sender { color: var(--side); }.
The crate's stylesheets are in cascade layers,
so any unlayered rule in your own stylesheet wins over it, whatever its
specificity. rich-chat.structure is what the components need to work
and is always injected; rich-chat.theme is the look, the code colors,
and the names' rules, the parts the props above turn off.
The theme declares these on :root. Override any of them on :root,
on .rc-chat, or on anything between; the nearest declaration wins, and
a token you take over is yours in both light and dark mode. Dark mode
follows prefers-color-scheme unless the document sets
data-theme="light" or "dark" on its root element.
| Property | Sets |
|---|---|
--rc-font, --rc-mono, --rc-math, --rc-math-text | The prose, code, math, and math-text font stacks. |
--rc-font-size, --rc-line-height | The base type size (15px) and leading. |
--rc-radius, --rc-tail | A bubble's corner radius, and the smaller radius of the corner on its side. |
--rc-bubble-max-width | The widest a bubble gets, min(85%, 76ch). One value for the whole chat; the crate does not clamp it. |
--rc-bg | The neutral surface: the window, the composer strip and its text box, button hover fills. |
--rc-chat-bg | The window alone, the ground behind the bubbles. Undeclared by default, so it follows --rc-bg. A gradient or image works too. |
--rc-fg, --rc-muted | The text color, and the color of the name over a bubble and the empty transcript's message. |
--rc-bubble-bg, --rc-bubble-fg | The plain bubble. |
--rc-tint-bg, --rc-tint-fg | The tinted bubble the default names give user, and the composer's preview. |
--rc-surface, --rc-border | Table headers and code block bars, and the edges of tables, code blocks, and the composer. |
--rc-accent, --rc-accent-fg | The send button, focus rings, and task-list checkboxes, and text on the accent. |
--rc-code-bg, --rc-inline-code-bg | Fenced and inline code backgrounds. |
--rc-selection | The selection highlight. |
--rc-alert-note, -tip, -important, -warning, -caution | The five GitHub alert colors. |
--rc-error | A math error, where an equation would not parse. |
--rc-thinking-dot, , |
.rc-chat { --rc-accent: #7c3aed; --rc-tint-bg: #ede9fe; --rc-chat-bg: #fff8e7; }
For a value chosen at runtime, set it as an attribute:
<Chat … attr:style="--rc-chat-bg: #fff8e7" />. The size of the name
over a bubble is a rule of yours: .rc-sender { font-size: 1em; }.
| Feature | Default | Costs | Without it |
|---|---|---|---|
highlight | on | about a megabyte of grammars | Code blocks are plain. |
bundled-fonts | on | about half a megabyte of fonts | Serve style::FONT_FILES yourself and inject style::font_faces_from("/fonts"). |
Chat is built from parts that stand alone, with the same props where
they share them:
| Component | Renders |
|---|---|
Composer | The text box, its collapsible preview, and send button; attributes reach the box. |
MessageView | One message, placed and colored by its name, and named on request. |
RichText | Any Markdown, from a Signal<String>. |
CodeBlock | One highlighted block with language label and copy button. |
Thinking | The three bobbing dots, with a label for screen readers, for a body you draw yourself. |
The copy button's words are a CodeLabels { copy, copied } provided as
context, so one provide_context changes them everywhere. The render
module underneath (render_html, render_blocks, complete_draft) is
plain Rust with no DOM dependency, for tests and for hosts that are not
Leptos.
The app crate is just a demo of leptos-rich-chat: a chat
window that opens on a tour of what renders, with a bar above it for
trying its settings, and a Tauri shell in
app/src-tauri that puts the same page in a desktop
window. The hosted copy at https://wpm.github.io/Rich-Chat/ is this
build.
Requirements: Rust with the wasm32-unknown-unknown target,
Trunk, Tauri's CLI and its
platform prerequisites.
rustup target add wasm32-unknown-unknown
cargo install trunk tauri-cli --locked
cd app && cargo tauri dev
In the browser instead of a window: cd app && trunk serve and open
http://localhost:1420.
cargo test -p leptos-rich-chat # the library, natively
cargo clippy --workspace --all-targets
cd app && trunk build --release # the frontend, to app/dist
cd app/e2e && npm install && npm test # the built app in headless Chromium
.github/scripts/browser-tests.sh cargo test # the code that needs a DOM, in headless Chrome; needs wasm-bindgen-cli and chromedriver
cargo run -p leptos-rich-chat --example highlight_css -- OneHalfDark dark # regenerate assets/highlight.css, see the library README
cargo llvm-cov -p leptos-rich-chat --open # test coverage, as a report in the browser; needs cargo-llvm-cov
.github/scripts/coverage.sh # test coverage as CI counts it, native and in the browser together
The CI workflow runs the library's tests
natively, builds the frontend and drives it in headless Chromium, checks
the desktop shell on every platform, and measures the test coverage,
native and in the browser, which goes to Codecov
and is the badge above; codecov.yml says how coverage
is judged.
Licensed under either of MIT or Apache-2.0, at your option. The bundled Latin Modern fonts are under the GUST Font License; see leptos-rich-chat/assets/fonts/LICENSE.md.
disabled | Signal<bool> | false | The composer off: the text box is disabled, there is no preview, nothing sends. The draft is kept. |
busy | Signal<bool> | false | A reply in flight: the text box and preview carry on, but nothing sends until it clears. The draft is kept. |
busy_label | String | "Sending is paused" | What a screen reader is told while busy holds a send back. |
thinking_label | String | "Thinking" | What a screen reader calls the dots of a message that is thinking: live, with no text yet. |
options | RenderOptions | everything on | What the Markdown renderer does; see below. |
on_link | Callback<String> | links open in a new browsing context | Receives a link's destination instead of following it. A Tauri window hands this to its opener plugin. |
warm_up | bool | true | Compiles the common languages' grammars in idle time after mount, so the first code block does not pause. |
-gap-period| The thinking dots' size, the space between them, and how long one bob takes. |