コンテンツにスキップ

チャット

hc-chat は会話のトランスクリプトを、時系列の DOM 順のままの素の <ol> として描画します — 支援技術の読み上げ順を逆転させてしまう column-reverse のトリックは使いません。ルートは role="log" の ライブリージョンなので、サーバが追記した完成したメッセージは それぞれ一度だけ読み上げられます。ストリーミング中のメッセージは aria-busy="true" を持ち、これが読み上げを保留します: スクリーンリーダーのユーザーはトークンごとのノイズを聞かされず、 最終スワップが属性を外したときに完成したメッセージを聞きます。 最新メッセージへの追従も CSS の仕事ではありません — それは installChatScroll() が担います。

別名: 会話 UI、メッセージスレッド。

  1. AB
    Show me the failing checks.
  2. Context switched to project “ops”.
  3. Two checks are red:

    lint: ok
    unit: FAIL

リストに max-block-size を与えて(または .hc-chat コンテナを サイズして)スクロールさせてください。アバターと __meta の行は 任意です — アバターのないメッセージは最初のグリッドカラムを畳みます。 __body にはサーバ描画の HTML が入ります。中のコードブロックは素の hc-code マークアップです。

メッセージdata-role が配置とバブルの色を決めます:

data-role意味見た目
user本人が入力した内容。アクセントのバブル、inline-end 寄せ。
assistant応答。ミュートのバブル、inline-start 寄せ。
systemコンテキストの注記 — 参加、モード切り替えなど。中央寄せのミュートの行、バブルなし。

配置は論理プロパティによるため、RTL は自動的に反転します。ロールは あくまで視覚的な速記です — 話者はメッセージのテキスト(または アバターのラベル)で、色に頼らず分かるようにしてください。

応答の生成中は、メッセージに data-state="streaming" aria-busy="true" を付けます。スタイルシートが本文の後ろに キャレットを描きます — ::after 疑似要素による純 CSS で、点滅 アニメーションは prefers-reduced-motion 下で除去されます。最終 コンテンツが届いたら、メッセージを置き換えるスワップが両属性を 落とします。

  1. The unit failure is
  2. Generation failed — connection lost.

data-state="error" は共有の ステータストークンでバブルを 色づけします — ストリームの中断や送信失敗に使ってください。

トランスクリプトは、読者が最下部にいる間は最新メッセージに追従し、 読み返そうと上へスクロールした瞬間に追従をやめるべきです。 installChatScroll() はまさにそれを行います:

  • インストール時に、すべての .hc-chat のリストが最下部にピン されます。
  • 新しいコンテンツはリストを再ピンします — MutationObserverchildList characterData を監視するため、ストリームされた テキストチャンクも対象です — ただしピン中のみです。
  • 最下部から約 24 px を超えて上にスクロールするとピンが解除され、 下へ戻ると再び有効になります。
  • ピン状態は .hc-chat ルートに data-stuck="true|false" として 反映されます。スタイルシートは "false" のときだけ .hc-chat__jump ボタンを表示し、クリックで再ピンします。 ビヘイビアなしでは属性が存在せず、ボタンは隠れたままです。
<div class="hc-chat" role="log" aria-label="Conversation">
<ol class="hc-chat__list" tabindex="0" style="max-block-size:24rem">
</ol>
<button class="hc-chat__jump hc-button" data-size="sm" type="button">↓ Latest</button>
</div>
import { installChatScroll } from '@hypermedia-components/core';
installChatScroll();

installChatScroll() は冪等で、アンインストーラを返します。ゼロ設定の @hypermedia-components/core/behaviors エントリが自動インストールし、 htmx でスワップされた内容も自動で拾います。ネットワークには決して 触れません — スクロールのピン留めだけが仕事です。

メッセージはサーバ描画の <li class="hc-chat__message"> フラグメント として届き、beforeend でリストに追記されます:

<form data-hx-post="/chat/messages"
data-hx-target="#chat-log" data-hx-swap="beforeend">
<input class="hc-input" type="text" name="message" aria-label="Message">
<button class="hc-button" data-variant="primary" type="submit">Send</button>
</form>
<div class="hc-chat" role="log" aria-label="Conversation">
<ol class="hc-chat__list" tabindex="0" id="chat-log"></ol>
</div>

コンポーザーの往復(1 回の POST でユーザーメッセージ + aria-busy の プレースホルダーを追記)は chat-messages レシピが 所有します。プッシュチャネル — アシスタントの応答がサーバから届く 経路 — は sse-updates レシピを 参照してください。応答契約(chunk / done / error)は streaming-response レシピが 所有します。

  • ルートが role="log" です(このロールは <ol> には許可されません。リストは素のリスト意味論を保ち、スクロール領域へキーボードで到達できるよう tabindex="0" を持ちます)— 追記された完成したメッセージは、 フォーカスを動かさずに polite に読み上げられます。会話には .hc-chataria-label で名前を与えてください(リストは <ol> のまま: ここでは順序が意味です)。
  • ストリーミング中のメッセージは aria-busy="true" を持ち、最終 スワップがそれを外すまで読み上げを保留します — トークンごとでは なく、メッセージごとに一度の読み上げです。キャレットは CSS の 疑似要素で、純粋に視覚的なものです。
  • DOM 順は時系列です — このキットは、支援技術の読み上げ順を逆転させる column-reverse を決して使いません。
  • ジャンプコントロールは本物の <button type="button"> です — 表示されている間はフォーカス可能でキーボード操作できます。

component トークン(component.tokens.json):

トークンパス用途
chat.gapリストのパディングとメッセージ間のギャップ。
chat.bubble-radius / bubble-max-inlineバブルの角丸 / メッセージの最大幅。
chat.bubble-padding-block / bubble-padding-inlineバブルのパディング。
chat.user-bg / user-fgユーザーのバブル。
chat.assistant-bg / assistant-fgアシスタントのバブル。
chat.system-fgシステム行の文字。
chat.meta-fgメタ行(タイムスタンプ)の文字。
chat.caret-colorストリーミングのキャレット。
chat.error-bg / error-fgエラー状態のバブル。
生成される CSS 変数を表示
--hc-chat-gap
--hc-chat-bubble-radius | -bubble-max-inline | -bubble-padding-block | -bubble-padding-inline
--hc-chat-user-bg | -user-fg
--hc-chat-assistant-bg | -assistant-fg
--hc-chat-system-fg | -meta-fg | -caret-color
--hc-chat-error-bg | -error-fg
  • タイムライン — 対話ではないイベントの履歴をリストで。
  • アバター — メッセージの最初のカラムに入る話者のアイデンティティ。
  • コード — メッセージ本文内のコードブロック。
  • トースト — トランスクリプトに属さない一時的な通知。
  • スクロールエリア — チャット以外の領域にも同じ細いテーマ付きスクロールバーを。

レシピでの利用: チャットメッセージ · ストリーミングレスポンス