ruststream 0.7.3

Async messaging framework for Rust: broker-agnostic traits, router, codecs, and a conformance harness for broker authors.
Documentation
import React, {useEffect, useRef, useState, type ReactNode} from 'react';
import Markdown, {type Components} from 'react-markdown';
import remarkGfm from 'remark-gfm';
import CodeBlock from '@theme/CodeBlock';
import {translate} from '@docusaurus/Translate';
import {LIBRARY_PAGE} from './fallback';
import {useAskAiSession} from './session';
import styles from './styles.module.css';

// Fenced code goes through the site's own code block, so an answer is highlighted by the same
// Prism setup and theme as the pages.
const markdownComponents: Components = {
  pre: ({children}) => <>{children}</>,
  code: ({className, children}) => {
    const text = String(children ?? '');
    const language = /language-(\S+)/.exec(className ?? '')?.[1];
    if (!language && !text.includes('\n')) {
      return <code>{children}</code>;
    }
    return <CodeBlock language={language}>{text.replace(/\n$/, '')}</CodeBlock>;
  },
};

export default function Panel(): ReactNode {
  const {open, turns, streaming, unavailable, ask, restart, close} = useAskAiSession();
  const [draft, setDraft] = useState('');
  const input = useRef<HTMLTextAreaElement>(null);
  const log = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (open) {
      input.current?.focus();
    }
  }, [open]);

  useEffect(() => {
    log.current?.scrollTo({top: log.current.scrollHeight});
  }, [turns]);

  useEffect(() => {
    if (!open) {
      return undefined;
    }
    const onKey = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        close();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, close]);

  const submit = () => {
    const question = draft.trim();
    if (question && !streaming) {
      setDraft('');
      ask(question);
    }
  };

  const title = translate({id: 'askAi.title', message: 'Ask AI'});
  return (
    <aside className={styles.panel} role="dialog" aria-label={title}>
      <header className={styles.header}>
        <span className={styles.title}>{title}</span>
        <button
          type="button"
          className={styles.headerButton}
          onClick={() => {
            restart();
            input.current?.focus();
          }}>
          {translate({id: 'askAi.newChat', message: 'New chat'})}
        </button>
        <button
          type="button"
          className={styles.headerButton}
          onClick={close}
          aria-label={translate({id: 'askAi.close', message: 'Close'})}>
          ✕
        </button>
      </header>
      <div className={styles.log} ref={log} aria-live="polite">
        {turns.length === 0 && !unavailable && (
          <p className={styles.hint}>
            {translate({
              id: 'askAi.welcome',
              message:
                'Ask a question about RustStream. Answers are generated by Context7 from this documentation and may contain mistakes.',
            })}
          </p>
        )}
        {turns.map((turn, index) => (
          <div key={index} className={turn.role === 'user' ? styles.question : styles.answer}>
            {turn.role === 'user' ? (
              turn.text
            ) : turn.text ? (
              <Markdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
                {turn.text}
              </Markdown>
            ) : (
              <span className={styles.typing}>…</span>
            )}
          </div>
        ))}
        {unavailable && (
          <p className={styles.notice} role="status">
            {translate({
              id: 'askAi.unavailable',
              message:
                'The assistant cannot answer here right now. The Context7 widget is opening in the corner of the page; the same assistant is also on the library page:',
            })}{' '}
            <a href={LIBRARY_PAGE} target="_blank" rel="noopener noreferrer">
              context7.com/powersemmi/ruststream
            </a>
          </p>
        )}
      </div>
      <form
        className={styles.form}
        onSubmit={(event) => {
          event.preventDefault();
          submit();
        }}>
        <textarea
          ref={input}
          className={styles.input}
          rows={2}
          value={draft}
          placeholder={translate({id: 'askAi.placeholder', message: 'Ask about RustStream'})}
          onChange={(event) => setDraft(event.target.value)}
          onKeyDown={(event) => {
            if (event.key === 'Enter' && !event.shiftKey) {
              event.preventDefault();
              submit();
            }
          }}
        />
        <button type="submit" className={styles.send} disabled={streaming || !draft.trim()}>
          {translate({id: 'askAi.send', message: 'Send'})}
        </button>
      </form>
    </aside>
  );
}