2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Svelte で ChatGPT 風なチャット画面を作成した

Posted at

はじめに

以下の投稿では入力ボックスだけを作成しました。

プログラマの性でしょうか、結局、メッセージの送信・表示もできるチャット画面全体を作成しちゃいました。

ここでは、実装にあたってポイントとなった箇所を記載していきます。

最終成果物

SvelteREPL で作成しました。

見た目はこんな感じです。右側に今回作成したチャット画面が表示されます。
image.png
適当にメッセージを入力して遊んでみてください。

尚、AIの応答メッセージ(dummy.json)は ChatGPT :robot:に作成してもらいました。

実装時のポイント

  1. カスタムストア を使って AI とのやり取りを隠蔽
  2. tick を使って再描画後の値を確実に参照

カスタムストアを使って AI とのやり取りを隠蔽

カスタムストア を使うことで AI とのやり取りを store.js ファイル内にまとめています。こうすることによって、コンポーネント側(~.svelteファイル)では AI とのやり取りの具体的な内容を意識することなく実装できました。

App.svelte では store.js から以下をインポートしています。

  • messages ストア
  • isInquiring ストア
  • inquiry 関数
App.svelte
<script>
	import { createSession } from './store.js';
	import ChatArea from './ChatArea.svelte';

	const { messages, isInquiring, inquiry } = createSession();

	const handleSubmitMessage = (text) => inquiry("あなた", text);
</script>

<ChatArea	messages={$messages}
					handleSubmitMessage={handleSubmitMessage}
					isInquiring={$isInquiring} />

メッセージを送信するとどんなことが起きるかを簡単な図で表してみました。

実際には、<ChatArea> 全体が再描画されるわけではなく、更新が必要な箇所だけが再描画されます。

App.svelte で実装するときに意識するのは、messagesisInquiring がストアであることと、実際の値を参照するために $ を付けること、ぐらいです。これだけでリアクティブな仕組みに対応できてしまいます。

こうなると、store.js 内で dummy.json を使っているのか、もしくは何等かの Web API を使っているのかの違いは、コンポーネントの実装に一切影響を与えません。store.js を修正するだけで本格的な(?)アプリケーションに移行できちゃいます。

tick を使って再描画後の値を確実に参照

ChatArea.svelte では、メッセージが追加されると自動的に最下部までスクロールする仕様になっています。このスクロールをスムーズに見せるために tick を使っています。

tick は再描画が完了するのを待ってから処理を実行するための仕組みです。

説明するのがむずかしいので、tick を使う場合と使わない場合を動画で比較してみます。

tick を使う場合。
ChatArea_tick有.gif

tick を使わない場合。
ChatArea_tick無.gif

tick を使わない場合はスクロールが若干遅れていますね。tick を使うことでこの遅れを解消する効果があるわけです。

実装はこんな感じです。

ChatArea.svelte
...
	$: {
		// 自動的に最下部までスクロールさせる
		messages;
		!!mainElement && tick().then(() => mainElement.scrollTo(0, mainElement.scrollHeight));
	}
...

tick()Promise を返すので await できるのですが、$: ステートメント内だと await できませんでした。仕方なく then() で対応しました。

いけてない実装だと思うところ

MessageInput.svelte では、text 変数が変更されたら resize() を実行して自動的に高さを変えるようにしています。
こんな感じです。

MesageInput.svelte
...
	$: {
		text;
		resize();
	}
...

text; の行は普通なら「何の意味もない行があるじゃん」って思いますよね...。
ただ、この行が無いと text 変数が変更されたことを検知できないので、自動的に高さを変えることができなくなってしまうのです。(前述の ChatArea.svelte の自動スクロールの実装も同様に messages; という行があります。)

以下の様に強引に if 文を設ける方法がありますが、これだと空文字になったときに resize() が実行されないのです...。

MesageInput.svelte
...
	$: {
		if (text) { // 😥 text が空文字の場合は false 扱いなので resize() は実行されない
			resize();
		}
	}
...

なんか、もやっとしますよね...
もっとうまい実装方法があったらぜひ教えてほしいです!:wave:

REPL の Tips

REPL では「JS output」というタブをクリックすれば、コンポーネントがコンパイルされた後の JavaScript を確認できます。

例えば以下のように、実際には $messages という変数が定義されていて、component_subscribe 関数によって更新を検知したら $messagesvalue を再代入していることが分かります。
image.png
こういうデータを見せてくれるように設計されている点がこの REPL の良いところだと思います。

他にも「CSS output」で CSSを、「AST output」で AST(構文木)を確認できます。

まとめ

Svelte を使って ChatGPT 風なチャット画面を作成できました。

Svelteカスタムストア を使うことでコンポーネントを修正することなく挙動を変えられるように実装できました。また、tick を使うことでより丁寧な UI/UX を実現できることがわかりました。

Svelte はフロントエンド開発にとって便利な機能がシンプルに実現されていると思います。Svelte の開発者の方々の設計力はすばらしいですね。:smiley:

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?