👾

自分専用ChatGPTを構築してみた

2023/04/04に公開

自分専用ChatGPTを構築してみた

たまたま見つけた

を利用したら、ChatGPTっぽいUIが比較的簡単に構築できそうだったので、リポジトリをForkしつつ、自分専用ChatGPTを構築してみました。

本記事は簡単な開発ログです。

できたものはこちら:
https://github.com/danimal141/chatbot-ui

ChatGPTライクなUIを構築する

基本的に https://github.com/mckaywrigley/chatbot-ui のREADMEに従えば簡単に環境構築ができます。Next.js + TypeScriptで構築されていて、必要なライブラリや環境変数のセットをするだけでシュッと動きました。

今回は後にログイン必須で使えるようにすることを想定しているので、リポジトリをForkして開発をしていきます。

Auth0の導入 (インフラ編)

まず初めに、Next.jsのフロントエンドにAuth0認証機能を導入する際、以下の選択肢がありました。

こちらの記事を参考にしたのと、自分自身 auth0-reactを使った経験があったので今回は auth0-react を採用していきます。

Auth0の構築自体はこちらを参考にすればすぐできるのですが、念の為コマンドラインでAuth0にデプロイできるようにしていきます。auth0-deploy-cliを使えば実現できることは知っていたので、そちらも導入します。

新たにauth0というディレクトリを作り、そこにAuth0用のpackage.jsonを作成しておきます。

{
  "private": true,
  "devDependencies": {
    "auth0-deploy-cli": "^7.17.1"
  },
  "scripts": {
    "a0deploy": "npx a0deploy deploy -c config.json -i tenant.yaml",
    "export": "npx a0deploy export"
  }
}

そしてAuth0のダッシュボード側でMachine to Machineのdeploy専用アプリを作っておき、auth0/config.jsonに以下の情報をセットしておきます。

{
  "AUTH0_DOMAIN": "xxx",
  "AUTH0_CLIENT_ID": "xxx",
  "AUTH0_CLIENT_SECRET": "xxx"
}

あとは 初回のみ npm run exportをして、tenant.yamlを取得し、あとはそれを使って、npm run a0deployでAuth0へのdeployができるようになります。新たに今回のNext.jsアプリ用にAuth0側にSPAのアプリケーション環境を構築し、開発環境、本番環境用の許可するcallback URL、logout URL、web origins URLなどを登録しておきます (詳しくはGitHubの tenant.yamlを見てみてください)。

Auth0の導入 (フロントエンド編)

.env.localに新たに

NEXT_PUBLIC_AUTH0_DOMAIN=''
NEXT_PUBLIC_AUTH0_CLIENT_ID=''

を追加し、pages/_app.tsxAuth0Providerを絡めていきます。

function App({ Component, pageProps }: AppProps<{}>) {
  const onRedirectCallback = (appState?: AppState) => {
    // Use Next.js's Router.replace method to replace the url
    Router.replace(appState?.returnTo || '/');
  };

  return (
    <Auth0Provider
      domain={process.env["NEXT_PUBLIC_AUTH0_DOMAIN"] || ""}
      clientId={process.env["NEXT_PUBLIC_AUTH0_CLIENT_ID"] || ""}
      authorizationParams={{
        redirect_uri:
          typeof window !== 'undefined' ? window.location.origin : undefined,
      }}
      onRedirectCallback={onRedirectCallback}
    >
      <div className={inter.className}>
        <Toaster />
        <Component {...pageProps} />
      </div>
    </Auth0Provider>
  );
}

export default appWithTranslation(App);

あとは pages/index.tsx側で

// Auth  --------------------------------------------
const { isAuthenticated, loginWithRedirect } = useAuth0();

return (
  <>
    <Head>
      <title>Chatbot UI</title>
      <meta name="description" content="ChatGPT but better." />
      <meta
        name="viewport"
        content="height=device-height ,width=device-width, initial-scale=1, user-scalable=no"
      />
      <link rel="icon" href="/favicon.ico" />
    </Head>
    {isAuthenticated && selectedConversation && (
      <main
        className={`flex h-screen w-screen flex-col text-sm text-white dark:text-white ${lightMode}`}
      >
      ...
    }
    {!isAuthenticated && (
      <div className='flex justify-center h-screen w-screen items-center'>
        <button type="button" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800" onClick={() => loginWithRedirect()}>Login</button>
      </div>
    )}

みたいなノリで、Userの認証有無によって分岐してやればやりたいことは実現できました。

認証Userの情報やログアウトボタンもせっかくなのでつけたかったので、components/Chatbar/ChatbarSettings.tsxを少し改良。

export const ChatbarSettings: FC<Props> = ({
  lightMode,
  apiKey,
  conversationsCount,
  onToggleLightMode,
  onApiKeyChange,
  onClearConversations,
  onExportConversations,
  onImportConversations,
}) => {
  const { t } = useTranslation('sidebar');
  const { logout, user } = useAuth0();

  return (
    <div className="flex flex-col items-center space-y-1 border-t border-white/20 pt-1 text-sm">
      {user && (
      <SidebarButton
        text={user.nickname || user.name || 'Anonymus'}
        icon={<IconUser size={18} />}
        onClick={() => void(0)}
      />
      )}
      ... 
      <SidebarButton
        text="Logout"
        icon={<IconLogout size={18} />}
        onClick={() => logout()}
      />
      ...
    </div>
  );
};

Vercelでデプロイ

これはVercelへの登録さえ終わっていれば、必要なAuth0やOpenAI API keyなどを環境変数にセットしておくだけでめちゃめちゃ簡単にデプロイできるので割愛。

まとめ

Next.jsアプリケーションにAuth0を導入するだけで簡単にやりたいことが実現できました。

特にFork元の https://github.com/mckaywrigley/chatbot-ui の実装が神で pages/api/chat.tsあたりの実装はOpenAIのtiktokenなどをこう使うのか、だったりstreamの扱いをどう実装しているか、などが垣間見えてめっちゃ勉強になります。

私はまだOn waitlistなのですが、API経由でGPT-4が開放されればさらに精度も期待できそうです。

Discussion