AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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. |