Agile育成ブログ
未来を変える喜びを
未分類

AI-Storyteller-GPTScript-Nextjs-Tailwindcss-main

{} で括る場合と括らない場合の違いは、 ES6モジュールのエクスポート形式 に関連しています。

1. {} で括る場合

{} で括る場合は、そのモジュールが 名前付きエクスポート (Named Export) を使用していることを意味します。

名前付きエクスポートの特徴

  • モジュールから複数のエクスポートが可能。
  • 必要なエクスポートを指定してインポートする。
  • インポート時に、エクスポート名とインポート名が一致している必要がある(別名を付けるには as を使う)。

例: 名前付きエクスポート

モジュールのエクスポート側:

javascriptコードをコピーする// utils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

モジュールのインポート側:

javascriptコードをコピーするimport { add, subtract } from './utils';

console.log(add(2, 3)); // 5
console.log(subtract(5, 3)); // 2

名前を変更する場合:

javascriptコードをコピーするimport { add as addition } from './utils';

console.log(addition(2, 3)); // 5

2. {} で括らない場合

{} で括らない場合は、そのモジュールが デフォルトエクスポート (Default Export) を使用していることを意味します。

デフォルトエクスポートの特徴

  • モジュールから 1 つだけエクスポート可能。
  • インポート時にエクスポート名を指定する必要はなく、好きな名前を付けてインポートできる。
  • 主に、モジュールの「メイン」的な機能を提供する場合に使われる。

例: デフォルトエクスポート

モジュールのエクスポート側:

javascriptコードをコピーする// utils.js
export default function multiply(a, b) {
  return a * b;
}

モジュールのインポート側:

javascriptコードをコピーするimport multiply from './utils';

console.log(multiply(2, 3)); // 6

名前を変更しても問題なし:

javascriptコードをコピーするimport times from './utils'; // 名前を自由に指定可能

console.log(times(2, 3)); // 6

実際のコード例の違い

デフォルトエクスポート

javascriptコードをコピーする// logo.png をデフォルトエクスポートとして指定した場合
import Logo from "@/images/logo.png";

// 他の名前でもOK
import MyLogo from "@/images/logo.png";

名前付きエクスポート

javascriptコードをコピーする// button.js の中で名前付きエクスポート
export const Button = () => {
  return <button>Click me</button>;
};

// 名前付きエクスポートをインポート
import { Button } from "@/components/ui/button";

// 名前を変更する場合
import { Button as MyButton } from "@/components/ui/button";

{} の有無が決まる要因

  1. モジュールのエクスポート形式に依存
    • デフォルトエクスポート なら {} は不要。
    • 名前付きエクスポート なら {} が必要。
  2. インポートする際のルール
    • デフォルトエクスポート は任意の名前でインポート可能。
    • 名前付きエクスポート は名前を正確に一致させるか、as で変更する。

order-1 は、CSSフレックスボックス (Flexbox) やグリッドレイアウトに関連した 順序を指定するプロパティ を操作するためのクラス名です。


1. フレックスボックスやグリッドの順序を指定するプロパティ

CSSで、order プロパティを使うと、要素の表示順序を指定できます。

CSSの例

cssコードをコピーする.item {
  order: 1;
}

order の値を設定することで、その要素の 並び順 を指定できます。デフォルトの値は 0 です。


2. Tailwind CSS での order-1

Tailwind CSS では、order-1 は次のように解釈されます:

  • order-1: CSSの order: 1; を適用する。
  • これにより、その要素が同じフレックスボックスやグリッド内の他の要素と比較して、 表示順序が1に設定される

3. フレックスボックスでの使用例

htmlコードをコピーする<div class="flex">
  <div class="order-2">Item 1</div>
  <div class="order-1">Item 2</div>
  <div class="order-3">Item 3</div>
</div>

結果:

  • order-1 を持つ「Item 2」が一番最初に表示されます。
  • 他の要素(order-2, order-3)の順序に基づいて並びます。

表示順序:

  1. Item 2 (order-1)
  2. Item 1 (order-2)
  3. Item 3 (order-3)

4. グリッドでの使用例

グリッドレイアウトでも同様に使用可能です。

htmlコードをコピーする<div class="grid grid-cols-3">
  <div class="order-2">Box 1</div>
  <div class="order-1">Box 2</div>
  <div class="order-3">Box 3</div>
</div>

5. order-* のバリエーション (Tailwind CSS)

Tailwind CSSでは、以下のようなクラスで order を設定できます:

クラスCSSプロパティ説明
order-firstorder: -9999;最初に表示される
order-lastorder: 9999;最後に表示される
order-noneorder: 0;デフォルト順序(変更なし)
order-1order: 1;順序を1に設定
order-2order: 2;順序を2に設定
order-3order: 3;順序を3に設定

{Array.from({ length: 10 }, (_, i) => ( <SelectItem key={i} value={String(i + 1)}> {i + 1} {i === 0 ? "page" : "pages"} </SelectItem> ))} 

コードの構造と意味

1. Array.from({ length: 10 }, (_, i) => ...)

  • Array.from:
    • 配列を生成するためのメソッドです。
    • 第一引数にオブジェクト({ length: 10 })を渡すと、その長さの配列を作ります(今回は10個の要素)。
    • 第二引数にはコールバック関数を渡して、配列の各要素をカスタマイズできます。
  • (_, i) => ...:
    • コールバック関数の引数:
      • _:この位置には配列の値が入ります(今回は空なので未使用)。
      • i:インデックス番号(0から始まる整数)。
  • 結果:
    • 長さ10の配列を生成し、各要素のインデックスiを使って加工します。

2. <SelectItem key={i} value={String(i + 1)}>

  • SelectItem:
    • カスタムコンポーネント(ドロップダウンの選択肢)です。
    • key:
      • Reactで必要なプロパティ。
      • 各要素にユニークな識別子を持たせるために使われます。
      • ここではインデックスiを使用。
    • value={String(i + 1)}:
      • value属性には、選択肢の値(文字列型)を設定。
      • i + 1により1から10までの数字が生成されます(String(i + 1)で文字列化)。

3. {i + 1} {i === 0 ? "page" : "pages"}

  • テンプレートリテラル:
    • 表示内容を動的に生成。
    • {i + 1}:
      • 現在のページ番号(1から10)。
    • {i === 0 ? "page" : "pages"}:
      • ページ数が1(i === 0)のときは単数形”page”を表示。
      • それ以外(i !== 0)のときは複数形”pages”を表示。

生成される出力例

このコードを実行すると、以下のようなSelectItem要素がレンダリングされます:

生成されるJSX構造(例)

jsxコードをコピーする<SelectItem key={0} value="1">
  1 page
</SelectItem>
<SelectItem key={1} value="2">
  2 pages
</SelectItem>
<SelectItem key={2} value="3">
  3 pages
</SelectItem>
...
<SelectItem key={9} value="10">
  10 pages
</SelectItem>

全体の流れ

  1. Array.fromで配列を作成:
    • [0, 1, 2, ..., 9]というインデックスを持つ配列を生成。
  2. コールバック関数で加工:
    • 各インデックスiを基にSelectItem要素を作成。
  3. 動的にレンダリング:
    • ドロップダウンリストの選択肢として使用。

用途

このコードは、動的にドロップダウンの選択肢を生成したいときに便利です。例えば、「ページ数の選択」や「数値の範囲の選択」など、値が規則的で複数ある場合に役立ちます。


 <Select onValueChange={(value) => setPages(parseInt(value))}>

<Select> コンポーネントのプロパティ onValueChange に関数を渡しています。この関数は、ユーザーが <Select> の中で選択肢を選んだときに実行されます。この関数では、選択された値を parseInt 関数で整数に変換し、その結果を setPages 関数で状態 pages にセットしています。

GPTScript

npm i @gptscript-ai/gptscript

スクリプト定義

javascriptコードをコピーするconst script = "app/api/run-script/story-book.gpt";
  • 実行対象となるスクリプトのパスを定義。
  • "story-book.gpt" というスクリプトファイルを利用して処理を行います。

スクリプトファイル

全体概要

このスクリプトは、以下の3つのツール (story-writer, story-illustrator, mkdir) を使用して、以下のプロセスを管理します:

  1. ストーリーの作成: 子供向けの適切なストーリーを生成。
  2. ディレクトリの作成: ストーリーの保存場所を設定。
  3. 挿絵の生成: ストーリーに基づいたイラストを作成し、保存。

セクション 1: メインスクリプト

目的
  • 子供向けの本を作成するためのプロセス全体を管理します。
ステップ解説
  1. 不適切なストーリーの中断:
    • 入力された story が子供向けではない場合、理由を示してプロセスを中断。
  2. タイトル生成:
    • ストーリーに基づいて適切なタイトルを生成。
    • タイトル内のスペースを - に置換してディレクトリを作成。
  3. ストーリー作成:
    • story がプロンプトであれば、story-writer ツールを呼び出して完全なストーリーを作成。
  4. ストーリー内容の確認:
    • 生成されたストーリーが子供向けでない場合、中断。
  5. 各ページの処理:
    • ページごとに内容をテキストファイルに保存し、story-illustrator を使用して挿絵を生成。
    • 挿絵は指定されたディレクトリに保存。
例外処理
  • イラスト生成が失敗した場合、最大2回まで再試行。

セクション 2: ツールの仕様

1. story-writer
  • 役割: 子供向けのストーリーを生成。
  • 引数:
    • prompt: ストーリーのアイデアやテーマ。
    • pages: ストーリーのページ数。
  • 出力:
    • ストーリー本文。
    • 挿絵スタイル。
    • 登場人物と舞台の詳細な説明。

2. story-illustrator
  • 役割: 挿絵を生成。
  • 引数:
    • text: 挿絵を生成するページの内容。
    • characters: キャラクターの外見。
    • settings: 舞台設定。
    • style: 挿絵スタイル。
  • プロンプト生成ルール:
    • キャラクターや舞台が含まれていない場合、それをプロンプトに含めない。
    • 子供向けでないプロンプトの場合、新しいプロンプトを生成。
  • 出力:
    • イラストのURL。

3. mkdir
  • 役割: ストーリーと挿絵を保存するディレクトリを作成。
  • 引数:
    • dir: 作成するディレクトリのパス。

重要なポイント

  1. 柔軟性: このスクリプトは、与えられた入力(ストーリーやページ数)に基づいて動作を変更できます。
  2. エラーハンドリング: 不適切なストーリーや挿絵生成の失敗に対して適切な処理を行います。
  3. 統合ツール: 各ツールの役割が明確で、全体のワークフローが円滑に進むように設計されています。

このスクリプトは、プロジェクトの自動化を通じて効率的な子供向けの本作成プロセスを実現します。

POSTリクエスト処理

javascriptコードをコピーするexport async function POST(request: NextRequest) {
  const { story, pages, path } = await request.json();
  • POST メソッドをエクスポートして、API エンドポイントを定義。
  • リクエストの JSON ボディから story, pages, path を取得。
    • これらはスクリプトの入力として使用されるオプションです。

スクリプト実行オプションの構築

javascriptコードをコピーするconst opts: RunOpts = {
  disableCache: true,
  input: `${story ? ` --story ${story}` : ""} ${
    pages ? `--pages ${pages}` : ""
  } ${path ? `--path ${path}` : ""}`.trim(),
};
  • RunOpts 型のオプションを作成。
    • disableCache: キャッシュを無効化。
    • input:
      • リクエストのオプション story, pages, path をコマンド形式に変換。
      • 存在しないオプションは空文字列になる。

ストリームの作成

javascriptコードをコピーするconst encoder = new TextEncoder();
const stream = new ReadableStream({
  async start(controller) {
    try {
      const run = await g.run(script, opts);
  • TextEncoder:
    • テキストデータを UTF-8 エンコードするためのユーティリティ。
  • ReadableStream:
    • ストリーミング可能なデータを生成。
  • g.run(script, opts):
    • 定義されたスクリプトを指定されたオプションで実行。

イベントリスナーの設定

javascriptコードをコピーするrun.on(RunEventType.Event, (data) => {
  controller.enqueue(
    encoder.encode(`event: ${JSON.stringify(data)}\n\n`)
  );
});
  • run.on:
    • スクリプトの実行中にイベント(RunEventType.Event)を監視。
    • イベントデータをエンコードし、ストリームに送信。

ストリーム終了処理

javascriptコードをコピーするawait run.text();
controller.close();
  • run.text():
    • スクリプトの全出力をテキストとして取得(ストリーム終了を待つ)。
  • controller.close():
    • ストリームを閉じて処理を終了。

エラーハンドリング

javascriptコードをコピーするcontroller.error(error);
console.log("ERROR", error);
  • エラーが発生した場合、ストリームにエラーを通知し、コンソールにログを出力。

handleStream 関数は以下を行います:

  1. ストリームからデータを読み取る。
  2. バイナリデータ(Uint8Array)をテキストデータに変換する。
  3. データを解析し、状態を更新する。
  4. イベントの種類に応じて異なるアクションを実行する。

(b) デコードして文字列に変換

デコードとは、エンコードされたデータを元の形式に戻すプロセスのことを指します。具体的には、ある特定の形式や方法で符号化(エンコード)された情報を、人間やシステムが理解可能な元の形に変換する操作

const chunk = decoder.decode(value, { stream: true });
  • decoder.decode: バイナリデータを文字列に変換する。
  • { stream: true }: ストリーム内で部分的にデコードを行う(デコード結果を蓄積しない)。

イベントデータを解析

const eventData = chunk
  .split("\n\n")
  .filter((line) => line.startsWith("event: "))
  .map((line) => line.replace(/^event: /, ""));
  1. chunk.split("\n\n"): テキストデータを複数行のイベントデータに分割。
  2. filter((line) => line.startsWith("event: ")): “event: ” で始まる行だけを抽出。
  3. replace(/^event: /, ""): “event: ” を削除して純粋なデータ部分を取り出す。

(d) JSONデータを解析

JSONデータをオブジェクトに変換する理由は、アプリケーション内で扱いやすく、データの操作、解析、条件分岐、表示が効率的かつ簡単になるため

eventData.forEach((data) => {
  try {
    const parsedData = JSON.parse(data);
    // イベントの種類に応じた処理
  } catch (error) {
    console.error("Failed to parse JSON", error);
  }
});
  • JSON.parse(data): JSONデータをオブジェクトに変換。
  • try...catch: JSONのパースエラーを捕捉。

変換前(文字列形式):

javascriptコードをコピーするconst jsonData = '{"type":"callProgress","tool":{"description":"Story Generator"}}';
console.log(jsonData.tool.description); // エラーになる

変換後(オブジェクト形式):

javascriptコードをコピーするconst parsedData = JSON.parse(jsonData);
console.log(parsedData.tool.description); // "Story Generator" と出力される

(e) イベントの種類ごとの処理

if (parsedData.type === "callProgress") {
  setProgress(parsedData.output[parsedData.output.length - 1].content);
  setCurrentTool(parsedData.tool?.description || "");
} else if (parsedData.type === "callStart") {
  setCurrentTool(parsedData.tool?.description || "");
} else if (parsedData.type === "runFinish") {
  setRunFinished(true);
  setRunStarted(false);
} else {
  setEvents((prevEvents) => [...prevEvents, parsedData]);
}
  1. callProgress:
    • progress(進行状況)を更新。
    • currentTool(現在のツール)を設定。
  2. callStart:
    • ツールの説明を設定。
  3. runFinish:
    • 実行完了フラグ(runFinished)を true に設定。
    • 実行中フラグ(runStarted)を false に設定。
  4. その他のイベント:
    • イベントを events 状態に追加。

1. 概要

  • renderEventMessage は、event オブジェクトを受け取り、その type に応じた JSX を返します。
  • event の型は Frame で、@gptscript-ai/gptscript ライブラリからインポートされています。
  • イベントの種類ごとに異なる JSX 要素を生成し、適切なメッセージやデータを表示します。

2. 重要な構造とロジック

switch

  • event.type の値に基づいて条件分岐を行い、適切な JSX を返します。

3. 各ケースの動作

1. runStart

  • イベントが「ランの開始」を示す場合、start プロパティを使用して開始時間を表示。
tsxコードをコピーするcase "runStart":
  return <div>Run started at {event.start}</div>;

2. callStart

  • ツールが開始された場合、その説明 (description) を表示。
tsxコードをコピーするcase "callStart":
  return (
    <div>
      <p>Tool Starting: {event.tool?.description}</p>
    </div>
  );

3. callChat

  • チャット中の入力 (input) を表示。
tsxコードをコピーするcase "callChat":
  return (
    <div>
      Chat in progress with your input {">>"} {String(event.input)}
    </div>
  );

4. callProgress

  • プログレス情報が更新された場合。ただし、このケースでは何も表示しない(null を返す)。
tsxコードをコピーするcase "callProgress":
  return null;

5. callFinish

  • ツール呼び出しが終了した場合、出力内容 (output) をリスト形式で表示。
tsxコードをコピーするcase "callFinish":
  return (
    <div>
      Call finished:{" "}
      {event.output?.map((output) => (
        <div key={output.content}>{output.content}</div>
      ))}
    </div>
  );

6. runFinish

  • ランが終了した場合、終了時間を表示。
tsxコードをコピーするcase "runFinish":
  return <div>Run finished at {event.end}</div>;

7. callSubCalls

  • サブコール(内部呼び出し)を処理。サブコールの内容やツール ID、入力をリストとして表示。
tsxコードをコピーするcase "callSubCalls":
  return (
    <div>
      Sub-calls in progress:
      {event.output?.map((output, index) => (
        <div key={index}>
          <div>{output.content}</div>
          {output.subCalls &&
            Object.keys(output.subCalls).map((subCallKey) => (
              <div key={subCallKey}>
                <strong>SubCall {subCallKey}:</strong>
                <div>Tool ID: {output.subCalls[subCallKey].toolID}</div>
                <div>Input: {output.subCalls[subCallKey].input}</div>
              </div>
            ))}
        </div>
      ))}
    </div>
  );

8. callContinue

  • 継続中の呼び出しを表示。サブコールが存在すれば、それもリスト形式で表示。
tsxコードをコピーするcase "callContinue":
  return (
    <div>
      Call continues:
      {event.output?.map((output, index) => (
        <div key={index}>
          <div>{output.content}</div>
          {output.subCalls &&
            Object.keys(output.subCalls).map((subCallKey) => (
              <div key={subCallKey}>
                <strong>SubCall {subCallKey}:</strong>
                <div>Tool ID: {output.subCalls[subCallKey].toolID}</div>
                <div>Input: {output.subCalls[subCallKey].input}</div>
              </div>
            ))}
        </div>
      ))}
    </div>
  );

9. callConfirm

  • 呼び出し確認のデータを表示。サブコールがあれば詳細も表示。
tsxコードをコピーするcase "callConfirm":
  return (
    <div>
      Call confirm:
      {event.output?.map((output, index) => (
        <div key={index}>
          <div>{output.content}</div>
          {output.subCalls &&
            Object.keys(output.subCalls).map((subCallKey) => (
              <div key={subCallKey}>
                <strong>SubCall {subCallKey}:</strong>
                <div>Tool ID: {output.subCalls[subCallKey].toolID}</div>
                <div>Input: {output.subCalls[subCallKey].input}</div>
              </div>
            ))}
        </div>
      ))}
    </div>
  );

10. default

  • 上記以外の event.type が指定された場合、event のデータ全体を JSON 形式で表示。
tsxコードをコピーするdefault:
  return <pre>{JSON.stringify(event, null, 2)}</pre>;

指定されたディレクトリ(public/stories)からストーリー(Story)とそのページ(Page)データを読み取り、ストーリーごとの詳細情報を整理して返す機能を提供します。以下にコードの詳細な説明を示します。


コード全体の目的

  • ストーリーとそのページ(テキストファイルと画像ファイル)を指定のディレクトリから収集し、構造化されたデータとして返します。
  • 特定のストーリーを取得する関数 (getStory) も提供します。

各セクションの詳細説明

1. モジュールとディレクトリの設定

typescriptコードをコピーするimport fs from "fs";
import path from "path";
import { Story, Page } from "../types/stories";
import cleanTitle from "./cleanTitle";

const storiesDirectory = path.join(process.cwd(), "public/stories");
  • fs: ファイルシステムを操作するNode.jsのモジュール。ディレクトリやファイルを読み書きします。
  • path: ファイルパスを操作するためのNode.jsモジュール。
  • StoryPage: ストーリーとページのデータ構造(型定義)。詳細は ../types/stories に記載されていると思われます。
  • cleanTitle: ストーリーフォルダ名を正規化するユーティリティ関数。
  • storiesDirectory: ストーリーファイルが保存されているベースディレクトリ。

2. getAllStories 関数

この関数は、全てのストーリーフォルダとその中のページデータを収集します。

ディレクトリ存在チェック

typescriptコードをコピーするif (!fs.existsSync(storiesDirectory)) {
  return [];
}
  • storiesDirectory が存在しない場合は空の配列を返します。

ストーリーフォルダの取得

typescriptコードをコピーするconst storyFolders = fs.readdirSync(storiesDirectory);
  • storiesDirectory 内にあるすべてのフォルダ名を配列として取得。

ストーリーフォルダごとのページデータの処理

typescriptコードをコピーするconst stories: Story[] = storyFolders.map((storyFolder) => {
  const storyPath = path.join(storiesDirectory, storyFolder);
  const files = fs.readdirSync(storyPath);
  • 各ストーリーフォルダ内の全ファイルをリストアップ。

ページデータをグループ化
typescriptコードをコピーするconst pages: Page[] = [];
const pageMap: { [key: string]: Partial<Page> } = {};

files.forEach((file) => {
  const filePath = path.join(storyPath, file);
  const type = path.extname(file).substring(1);
  const pageNumber = file.match(/page(\d+)\./)?.;
  • pageMap:
    • ページ番号(pageNumber)ごとに txt(テキストデータ)と png(画像データ)をグループ化。
    • Partial<Page> は、型 Page の一部を持つオブジェクト。
  • type:
    • ファイル拡張子を取得(txt または png)。
  • pageNumber:
    • ファイル名から page1.txt のような形式でページ番号を抽出する正規表現。

データを読み込み
typescriptコードをコピーするif (pageNumber) {
  if (!pageMap[pageNumber]) {
    pageMap[pageNumber] = {};
  }

  if (type === "txt") {
    pageMap[pageNumber].txt = fs.readFileSync(filePath, "utf-8");
  } else if (type === "png") {
    pageMap[pageNumber].png = `/stories/${storyFolder}/${file}`;
  }
}
  • ページデータを pageMap に格納。
    • テキストファイルは fs.readFileSync で内容を文字列として読み込む。
    • 画像ファイルは /stories/... 形式のパスで格納。

完全なページデータの構築
typescriptコードをコピーするObject.keys(pageMap).forEach((pageNumber) => {
  if (pageMap[pageNumber].txt && pageMap[pageNumber].png) {
    pages.push(pageMap[pageNumber] as Page);
  }
});
  • テキストデータと画像データが両方存在するページのみをリストに追加。

ストーリーオブジェクトを構築
typescriptコードをコピーするreturn {
  story: cleanTitle(storyFolder),
  pages: pages,
};
  • ストーリーフォルダ名をクリーニングして story とし、ページデータを pages として格納。

ストーリーをフィルタリング

typescriptコードをコピーするconst storiesWithPages = stories.filter((story) => story.pages.length > 0);
return storiesWithPages;
  • ページが存在しないストーリーを除外。

3. getStory 関数

typescriptコードをコピーするexport const getStory = (story: string): Story | undefined => {
  const stories = getAllStories();
  return stories.find((s) => s.story === story);
};
  • 特定のストーリー名を持つストーリーを取得。

全体の役割

  1. ストーリーフォルダの収集:
    • ストーリーごとのディレクトリを解析。
  2. ページデータの収集:
    • ページ番号ごとにテキストデータと画像データを関連付ける。
  3. ストーリーオブジェクトの構築:
    • ストーリータイトルとページデータを統合。
  4. 特定のストーリーの取得:
    • 必要に応じて特定のストーリーを検索可能。

  1. 特殊文字をスペースに置き換えるtypescriptコードをコピーする.replace(/[-/\\[\]]/g, " ")
    • 正規表現 /[-/\\[\]]/g は、次の文字をマッチします:
      • -(ハイフン)
      • /(スラッシュ)
      • \(バックスラッシュ)
      • [(角括弧の開き)
      • ](角括弧の閉じ)
    • これらの文字をスペース " " に置き換えます。
  2. 複数のスペースを単一のスペースに置き換えるtypescriptコードをコピーする.replace(/\s+/g, " ")
    • 正規表現 /\s+/g は、1つ以上連続する空白文字(スペース、タブ、改行など)にマッチします。
    • これを単一のスペース " " に置き換えます。
  3. 文字列の前後の余分なスペースを削除typescriptコードをコピーする.trim()
    • trim() は、文字列の先頭および末尾のスペースを削除します。

関数の全体の流れ

例えば、次のようなタイトルが与えられた場合:

入力

const title = "  Hello---World/This\\is[a]test ";

処理

  1. 特殊文字をスペースに置き換える:typescriptコードをコピーする" Hello World This is a test "
  2. 複数スペースを単一スペースに置き換える:typescriptコードをコピーする" Hello World This is a test "
  3. 前後の余分なスペースを削除:typescriptコードをコピーする"Hello World This is a test"

出力

typescriptコードをコピーする"Hello World This is a test"

interface

  • TypeScriptで型を定義するための構文です。
interface StoryPageProps {
  params: {
    id: string;
  };
}

lib/stories.ts