JOURNAL コラム
2024.06.06 エンジニア

ブラウザのテキストエリアでもGitHub Copilotのような文章補完がほしい

最近のコーディング環境ではGitHub Copilotがとても便利ですね…途中まで書いてTABを押すとその後のコードを補完してくれるのでタイポやタイプ数削減に繋がります。
ChatGPTのチャットなどを使い、コードの全体を生成させようとすると意図とは違うコードを出力してきたり、正確に生成してもらうために細かくプロンプトで指定する必要があり億劫なことが多いです。その点、Copilotは少し書いて補完…少し書いて補完…と細かく確認できるのでとても楽に書くことができます。

それだけ便利なら本記事のようなブログを書くときも同じように少し入力するだけで補完してもらうのが楽なのではないかと思うわけです。
というわけでやってみました。

全2回として今回は入力内容に対して補完内容をサジェストする部分を実装していきます。

実装

今回の実装はすでに試されている方がいらっしゃいますがNext.jsで実装されていたので、今回は最近自分がハマっているSvelteを使って作成します。また文章の補完にはChatGPTのAPIを使用していきます。本記事ではSvelteの環境構築やChatGPTのAPIキー準備については触れません。
まずはChatGPTを叩いてもらう用のAPIを生やします。このAPIでは入力されたテキストを入れたプロンプトをChatGPTに投げ、ChatGPTから返ってきた続きの文字列を返す役割をしています。プロンプトについては先駆者さんのものを参考にさせてもらいました。この部分はご自身の用途に合わせて調整が必要になると思います。
/src/routes/api/gpt.ts

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import OpenAI from 'openai';

const callChatGPT = async (text: string) => {
    const openai = new OpenAI({
        apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // .envを使うなりしてAPIキーを記述する...ここではベタ書き
    });

    const chatCompletion = await openai.chat.completions.create({
        messages: [
            {
                role: 'user',
                content: `「${text}」に続く文章を考えてください。`,
            },
            {
                role: 'user',
                content: `続きの文章のみを短く日本語で返却してください。`,
            },
        ],
        model: 'gpt-3.5-turbo',
    })
    const message = chatCompletion.choices[0].message?.content
    return (message && text && message.startsWith(text)) ? message.slice(text.length) : message;
}

export const GET: RequestHandler = async ({ url }) => {
    const text = url.searchParams.get('text');
    const message = await callChatGPT(String(text));
    return new Response(String(message));
}

export const POST: RequestHandler = async ({ request }) => {
    const { text } = await request.json();
    const message = await callChatGPT(text);
    return json({ text: message });
};

次にフロントエンドの処理を書いていきます。今回の動きとしてはテキストエリアに入力されたらAPIを叩き、結果をサジェストとして表示する。サジェストを確定させるならTabキーを押すという流れになります。
/src/+page.svelte

    let textarea = '';
    let suggestions = '';
    let timer:any = 0;
    async function handleInputValue() {
        clearTimeout(timer);
        if (textarea.length > 0) {
            timer = setTimeout(async () => {
                setSuggestion('入力中...');
                const response = await fetch('/api/gpt', {
                    method: 'POST',
                    body: JSON.stringify({ text: textarea }),
                    headers: {
                        'Content-Type': 'application/json',
                    },
                })
                response.json().then((data) => {
                    setSuggestion(data.text);
                });
            }, 1000);
        } else {
            setSuggestion('');
        }
    };
    const setSuggestion = (value: string) => {
        suggestions = value;
    };
    const commitSuggestions = () => {
        textarea += suggestions;
        setSuggestion('');
    };
    const handleSuggestions = (e: KeyboardEvent) => {
        if (e.key === 'Tab') {
            e.preventDefault();
            commitSuggestions();
        }
        setSuggestion('');
    };

最後にテキストエリアを作成します。テキストエリアと表現していますがtextareaタグではなくdivタグにcontenteditable属性を付与して使用します。こちらも先駆者さんからの受け売りとなりますが、このように実装するほうが要素を組み合わせやすく、サジェストの動作を実現しやすいです。ただこちらは次回テキストエディタのフレームワークを導入する段階で不要になったのでどちらでも大丈夫でした。
/src/+page.svelte


{suggestions}

ここまできたら下記のように動作するテキストエリアが完成します。


終わりに

というわけで今回はサジェスト部分を作成しました。ChatGPTのAPIも特に使いにくいところはなく、思っていたよりも簡単に実装できたと思います。とはいえ現状GPT3.5を使用していたり、サジェストを作成するためのプロンプトを調整したりとまだ改善点は残っており、より精度をあげる旅はまだまだこれからです。
次回はサジェストをインラインで表示するようにして、もう少しColilotらしくより使いやすく、していきたいと思います。

CCG WORKING HEADSでは、サイト制作やウェブデザインなど、デジタル領域のクリエイティブを軸に、お客様の想いをカタチにします。
お問い合わせから、お気軽にご連絡ください。

このコラムに関連するタグ

Share

  • facebook
  • twitter
  • B!
  • Feedly