---
title: "助手對話介面 - Clean Architecture in TypeScript"
date: 2025-08-22T00:00:00+08:00
publishDate: 2025-08-22T00: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/08/22/clean-architecture-in-ts-assistant-dialog-interface/"
language: "zh-tw"
---



透過 AI 協力開發，我們可以節省許多以往需要花費力氣的時間。不過開發的流程並不會有太大的變化，這次我們會採用可以馬上確認成果的方式進行開發，因此會從前端的介面開始著手。

<!--more-->

## 初始畫面{#init-view}

我們希望在初期先建構一個可以跟助手（Assistant）對話的介面，基本上會需要訊息區域、對話輸入的區域，這部分我們會直接撰寫程式碼來作為提示。

我們先更新 `src/client.tsx` 把主畫面 `App.tsx` 引用進來，可以根據專案習慣使用像是 `@/view` 這種 Alias 設定，後續的範例都會直接使用這種風格。

```ts
import { render } from "hono/jsx/dom";

import { App } from "@/view/App";

// @ts-ignore - This is a client-side only file
const root = document.getElementById("root");
if (root) {
	render(<App />, root);
}
```

接下來將 `src/view/App.ts` 也建立起來，裡面預計會引用一個 `Chat` 元件，用於後續我們製作的對話視窗。

```ts
import { FC } from "hono/jsx/dom";

import Chat from "@/view/Chat"
import "@/view/style.css"

export const App: FC = () => {
	return (
		<Chat />
	);
};
```

接著將 TailwindCSS 的基礎設定放到 `src/view/style.css` 裡面

```css
@import "tailwindcss";
```

最後先放一個 Hello World 的訊息到 `src/view/Chat.tsx` 裡面，確認 Hono JSX 的設定都正確運作，而且我們所需要的功能都按照預期。

```ts
import { FC } from "hono/jsx/dom";

export const Chat: FC = () => {
	return (
		<div>
			<h1>Chat</h1>
			<p>Welcome to the chat!</p>
		</div>
	);
}
```

上述的任務可以利用 GitHub Copilot 的程式碼補完協力，雖然不是像代理模式（Agent Mode）那樣完全自動，但這可以幫助我們在過程中反思設計。

## 樣式設定{#theme-config}

因為我們已經有 `DESING.md` 可以用作樣式的參考，所以可以先將 `src/view/style.css` 進行更新，利用 AI 來撰寫相應的設定。

受限於 TailwindCSS 4 比較新，我們會需要讓 AI 預先閱讀過 [Theme 設定文件](https://tailwindcss.com/docs/theme) 以 [Aider](https://aider.chat/) 為例子，會給予以下指示。

```
/read DESIGN.md
/web https://tailwindcss.com/docs/theme
```

完成之後，給予下面的提示詞，就會自動將 `@theme` 的內容產生出來。

```
According to `DESING.md` to add TailwindCSS theme config to `src/view/style.css`
```

根據使用的模型、編輯器不同結果可能會有差異，但我們預期至少要有類似下面這段 CSS 實作。

```css
@theme {
  /* 顏色 */
  --color-primary: #4F46E5;
  --color-secondary: #6B7280;
  --color-background: #F9FAFB;
  --color-card: #FFFFFF;
  --color-text-primary: #1F2937;
  --color-text-secondary: #6B7280;
  
  /* 排版 */
  --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
  --text-base: 1rem;
  --font-weight-medium: 500;
  --font-weight-bold: 700;
  --leading-normal: 1.5;
  
  /* 間距 */
  --spacing-0: 0;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-6: 1.5rem;
  --spacing-8: 2rem;
  
  /* 邊框 */
  --radius-md: 0.375rem;
  
  /* 陰影 */
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
```

透過這種方式我們可以在編輯樣式上節省不少時間，必要時可能會需要自己去微調來確保符合預期，不一定要完全依賴使用 AI 編輯，有時候成本會遠大於自己修改一兩個字。
## 對話介面{#chat-view}

接下來就要實際製作對話介面，我們預定要在 `Chat` 元件中增加 `ChatMessage` 和 `ChatInput` 兩個元件，此時腦中應該有大致上該有的實作雛形，但是細節我們可以先不考慮。

```ts
import { FC } from "hono/jsx/dom";

export const Chat: FC = () => {
	// TODO: Manage Chat Message State

	return (
		<>
			{ /* TODO: Chat Message */ }
			{ /* TODO: Chat Input */ }
		</>
	);
}
```

因為不需要自己實作，所以我們只需要把預期看到的成果用註解的方式記錄下來，在這個階段我們需要的是清楚知道 `Chat` 元件會保存對話訊息的狀態，而呈現則由 `ChatMessage` 和 `ChatInput` 兩個元件負責。

完成這個階段的處理後，先 `git commit` 把實作記錄下來，以免後續 AI 接手處理發生問題時，我們無法退回最後一次正常的狀態。

接下來根據你使用的 AI 工具給指示，目前已經設定 `readonly: DESIGN.md` 和 `editable: src/view/Chat.tsx` 兩組設定，明確限制我們只會改動到 `Chat` 元件，而且可以參考 `DESIGN.md` 的設計規範來套用介面。

```
Implement `ChatMessage` and `ChatInput` component for `Chat`.

* Create new component file
* Apply style according design token use TailwindCSS
* Use English in UI
* Use conventional commit in English
```

因為 AI 工具是具有隨機性的，上述的提示詞不一定能很好的運作。以撰寫這個段落時的情況，即使要求 Commit 訊息使用英文最後還是用中文撰寫，這可能是工具的 System Prompt 改變所造成。

但是在 AI 生成後，我們應該預期要能看到如下可操作的介面。

![對話介面展示](images/2025-08-22-demo.png)
以往可能需要先學會怎麼管理 React 的狀態，以及實作對應的邏輯在調整樣式才能夠有這樣的效果，但我們可以在短短數分鐘內的幾此嘗試就取得這樣的成果，已經比過去開發更有效率不少。

> 生成的實作中仍可能會有一些問題，像是使用了 `setTimeout` 來模擬發送訊息的效果但沒有考慮 React 會重新產生的特性，一方面是因為我們並沒有交代不需要這樣的機制，另一方面也反應出 AI 不一定能判斷這種細節。
