---
title: "對話階段 - Clean Architecture in TypeScript"
date: 2025-10-24T00:00:00+08:00
publishDate: 2025-10-24T00:00:00+08:00
lastmod: 2025-11-21T10:38:12+08:00
tags: ["TypeScript","Clean Architecture","架構","經驗","AI"]
series: "clean-architecture-in-ts"
toc: true
permalink: "https://blog.aotoki.me/posts/2025/10/24/clean-architecture-in-ts-conversation-stage/"
language: "zh-tw"
---



前面的實作都是將對話編號寫死為 `default` 接下來我們要讓每個使用者開啟後都能夠產生一個新的對話，而且持續 60 分鐘都可以使用相同的對話串來進行購物。

<!--more-->

## 對話編號{#chat-id}

目前我們的設計是對話跟購物車都使用相同的 ID 辨識，這邊可以統一成叫做 Session ID 的概念，要支援這個功能我們需要有幾個地方做改動。

一個是前端需要能夠持續一段時間保存 Session ID 來使用，並且在後續所有 API 呼叫都使用這個 Session ID 來進行，後端我們還有一些假的資料，也需要清除以免有使用者遇到錯誤資料的狀況。

我們一樣使用 Claude Code 來進行修改，只需要指出相關的檔案就會自動搜尋跟調整。

```markdown
Let's update `src/view/Chat.tsx` to provide make chatId depend on the session.

Use Hono JSX instead of React JSX, and create a session context to manage the session ID.

## Session Context

Create a new `src/view/session.tsx` to provide a session context.

- Define a `SessionContext` using `createContext`.
- Use LocalStorage to store the session ID, make it expire after 60 minutes.
- The session ID should be a random UUID via `crypto.randomUUID()`.
- Add `useSession` hook to access the session context.

## App Component

- Update `src/view/App.tsx` to use the `SessionContext`.

## Chat Component

- Update `src/view/Chat.tsx` to use the `useSession` hook.
- Replace the hardcoded `chatId` with the session ID from the context.

## Backend

Remove the mock data for hardcoded `chatId` in the backend.
```

這邊有一個小細節，因為我們提到像是 `useContext` 等等概念，AI 可能會預設我們要用 React 來處理，但這邊要使用 Hono JSX 作為 React 的替代，因此特別提醒後，就可以減少出錯的情況。

其他部分則詳細描述我們需要的元件以及行為，像是要定義一個叫做 `SessionContext` 的 Context 以及提供 `useSession` 的 Hook 用來取得 Session ID 等等。

等待 AI 實作完畢後，我們稍微檢查沒問題，就可以使用全新的對話進行購物。

## 建立對話{#new-session}

要測試是否有正確使用 Session ID 來切換，如果每次都等待 60 分鐘會太久，以及沒有將 ID 顯示出來都不太好觀察，我們可以加入一個 Top Bar 在畫面上方顯示 ID 以及一個按鈕用於產生新的 ID，那麼就可以快速驗證功能是否順利被實作。

```markdown
Add a top bar in chat view to display the session ID and a button to regenerate it.
```

基本上這個階段我們不需要給 Claude Code 太精確的說明，因為 Chat View 已經非常明顯是 `src/view/Chat.tsx` 的前提下，很快就可以找到對應的檔案並且進行修改。

有時候可能會把原本的功能改錯，像是將對話紀錄捲動到最底端的處理，在這一次的修改中發生問題，會把整個畫面全部捲動，我們可以給另一個提示來修正。

```markdown
The scroll to bottom will scroll all page, we expect only chat messages to scroll.
```

調整一小部分實作後就正常，因為 AI 的隨機性，所以並不一定會遇到這樣的問題。大多數情境只要對話紀錄還是延續的，只需要簡單說明就可以修正。

但也要評估一下當下那個問題是否很難處理，像是前端顯示的錯誤等等，如果無法精確描述外觀，大多很難修好，應該考慮直接手動修改或者提供範例。

## 保存對話{#save-data}

目前的實作都是將對話紀錄、購物車儲存在記憶體裡面，只要伺服器關閉後就會遺失資料。我們可以修改為使用 Cloudflare KV 來儲存資料，相對使用 D1 簡單也能快速提供過期機制。

````markdown
Currently, we save the data in memory. Let's refactor to use Cloudflare KV for persistence.

## wrangler.json

Add new `kv_namespaces` to `wrangler.json`:

```json
{
  "kv_namespaces": [
	{
	  "binding": "STORAGE",
	  "id": "your-kv-namespace-id"
	}
  ]
}
```

Run `npm cf-typegen` to update the types.

## Repository

Update the repository to use Cloudflare KV for data storage.

We can use `import { env } from "cloudflare:workers";` to access bindings directly.

For different repository, use `chat:` as prefix for keys, e.g., `chat:sessionId`.

When set value, the TTL is set to 60 minutes.
````

為了讓實作簡單，我們使用新版的 `import { env } from "cloudflare:workers";` 來處理讀取 KV Binding 的部分，後續我們再利用 Claude Code 幫我們將它改為依賴注入的版本。

修改完畢後，可能會遇到 `cloudflare:workers` 不存在的問題，我們最初的設定是手動將 Vite 和 Wrangler 整合起來，這邊可以改為使用 `@cloudflare/vite-plugin` 來處理，基本上調整完 `vite.config.mts` 後就能夠正常測試。

現在即使將伺服器關閉，原本的對話紀錄也會持續保存，直到該筆紀錄超過一小時都沒有變動為止（提示中要求 TTL 設定為 60 分鐘）
