LY Corporation Tech Blog

支持 LY Corporation 和 LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam) 服務,宣傳技術和開發文化。

跟 AI 聊天就能記帳:用 CopilotKit × AG-UI × Next.js 打造 AI 記帳 App

一、前言:AI 應用開發的思維轉變

大家好~
我是LINE的Front End Engineer — Eric

2024 年開始,隨著生成式 AI 的蓬勃發展與崛起,App 世界中正進行著一場「操作變革」

過去我們花許多時間在設計使用者介面(User Interface)、操作流程(User Workflow),並在各種場景下要求使用者填寫不同的表單(Form)以完成不同的任務

不過在 AI 時代,「對話」正在取代「點擊」

許多的App都開始把 AI 融入進既有的 App服務流程中,成為「AI app」,讓AI在App中,成為使用者的協作者,而AI App 的核心是「協作」與「目標/意圖驅動的互動」。使用者不必遵守固定流程,只要 AI 可以理解使用者的目標/意圖、主動提供建議、根據上下文決定下一步行為,使用者就能透過與AI互動,更方便、更快速的完成目標

這也意味著,產品開發要思考的不再只是畫面怎麼設計,而是AI 如何理解目前的狀態、介入決策,並與使用者互動。如果開發者只會呼叫 LLM API,把回傳結果顯示在頁面上,那麼這個「AI App」還只是傳統 App 加一個聊天框,並不能發揮 AI 的真正價值

而這篇文章的目的是希望能和大家分享,身為一個前端工程師,要打造一個基本的AI App,通常會使用哪些工具、這些工具大概會怎麼使用

二、我想要一個 AI 記帳的Web App

這篇文章會以簡單的AI記帳服務作為範例,透過pseudo code / sample code的方式讓大家比較好理解

這個AI記帳服務的核心功能分為3個部分:

  • 上傳信用卡帳單(PDF / 圖片):使用者可以直接上傳各種銀行或信用卡帳單,AI會解析文件,將文字或表格抽取出來,使用者就不用再手動輸入每一筆支出
  • AI 自動解析、分類消費:上傳後,AI 會自動解析帳單內容,將支出分類(餐飲、交通、訂閱等),並將資訊整理成可查詢的結構化數據。透過這個方式,使用者不必再整理原始帳單,AI 會主動把資訊整理好
  • 使用者可以用「對話/問問題」的方式查詢消費紀錄:使用者可以直接問 AI:「上個月花最多的是哪一類?」、「今年和去年同月份消費差多少?」AI 會根據現有的資料,直接分析、整理好,並且提供結果

三、核心技術&工具:

要打造流暢的 AI App協作體驗,我們需要一些工具,讓 LLM 與前端組件進行溝通:

CopilotKit:讓 AI 變成 App 的一部分

CopilotKit 的核心功能是將 AI 與 App 狀態的行為進行整合, AI 能讀取並執行某些預先定義好的行為,更改你的應用程式資料與狀態,基本的操作包含:

  • 把App state 轉換成 System Prompt,讓 LLM 能夠讀取目前的 App state
  • 透過 LLM 的 Tool Call 執行前端定義好的 action / function
  • 透過 LLM 的 Tool Call 在對話中顯示前端定義好的客製化的UI

對 AI 記帳 App 來說,CopilotKit 可以讓 AI 理解資料、操作應用、提供分析圖表

Agent–User Interaction (AG-UI) Protocol:AI 與 UI 的通訊標準
代理–使用者互動(AG-UI)協定:AI 與 UI 的通訊標準

如果說 REST 是「客戶端與伺服器」的溝通標準,那麼 AG-UI 就是「使用者、前端介面與 AI Agent」之間的三方溝通標準,常見的功能包含:

  • 雙向同步的狀態管理 (State Management):當 Agent 在後端更新了其內部狀態(如:更新了使用者的預算水位),AG-UI 會自動同步至前端 UI,確保使用者看見的是最新的「意圖結果」
  • 標準化 Agent 生命週期:AG-UI 定義了 Agent 在執行任務時的標準狀態機,像是 thinking (正在進行 LLM 推理)、call:tool_name(正在執行特定的 Action)、awaiting_comfirm(暫停執行,進入 Human-in-the-Loop (HITL) 模式,等待使用者點擊 UI 上的確認),前端開發者只需根據協議狀態切換 UI
  • 支援各種Generative UI Protocol:AG-UI 支援主流的 Generative UI Protocol,包含A2UI、Open-JSON-UI、MCP-UI,這些Protocol規定了 AI 如何在對話中發送「Component Rendering Command」,讓 AI 在回傳的串流中帶上參數,讓前端能在介面上畫出圓餅圖、核對清單、交易表單 等豐富的使用者介面

ps. CopilotKit底層也使用了 AG-UI 來和Agent進行溝通!
ps. CopilotKit 底層也使用了 AG-UI 來和 Agent 進行溝通!

Next.js:成熟的 Web App 開發框架

Next.js 在 Web App 開發上提供許多強大的工具,其中AI App比較常用到的有:

  • Server-side 運算與 RAG 整合:AI App有時候會需要涉及大量的資訊檢索、API呼叫,透過Next.js 的 Server Components 與 Server Actions,能在 Server 端執行 LLM 呼叫與資訊檢索,並且很容易地將執行結果與前端的使用者介面進行整合
  • HTTP Streaming: AI 的生成過程通常需要時間。Next.js 原生支援HTTP Streaming,配合 React 的 Suspense 組件,能讓 AI 的回應以「串流」方式逐字產出。這種「打字機效果」讓 UI 能隨著資料流即時更新,提供流暢的即時互動體驗

四、快速動手做:打造 AI 記帳 App (Self-Hosted)

目標會是建立一個像是這樣的記帳App,包含記帳列表,能夠讓使用者透過手動建立、上傳帳單去建立帳單紀錄,並且能夠針對每一個帳單紀錄進行編輯

image

1. 建立專案

Step 1: 安裝copilotkit  步驟一:安裝副駕駛套件

npm install @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime

Step 2: 設定後端 AI Runtime

透過 Next.js Route Handler 在伺服器端建立一個api endpoint,負責處理 AI 的request,這邊以Open AI gpt-4o 為使用的處理模型

// app/api/copilotkit/route.ts
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/runtime";

export async function POST(req: Request) {
  const runtime = new CopilotRuntime();
  // 使用 OpenAI 作為底層的LLM
  return runtime.response(req, new OpenAIAdapter({ model: "gpt-4o" }));
}


而在 api/copilotkit/route.ts 中,OpenAIAdapter 預設會讀取 OPENAI_API_KEY 環境變數,所以要記得確認你的 .env.local 或是其他環境變數檔案已經有包含OPENAI_API_KEY環境變數

另外,由於我們使用的是 Self-hosted Runtime,所有的 LLM 請求都會經過你的 Next.js Server,建議在 Route Handler 中加入權限驗證 (Auth),避免 API 被濫用

Step 3: 設定前端 Provider

在Next.js的 Layout中加入CopilotKit 作為provider,並指向剛剛建立的 API 端點

// app/layout.tsx
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";

export default function RootLayout({ children }) {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit">
      {children}
    </CopilotKit>
  );
}

Step 4: 在App中加入 CopilotKit 聊天視窗

 page.tsx 中導入 CopilotKit 提供的開源聊天視窗(下圖紅框),這個聊天視窗中已經預先整合了 AG-UI 協議,所以能夠自動處理思考中(Thinking)與工具執行(Action)的狀態

import { CopilotSidebar } from "@copilotkit/react-ui";

export default function Home() {
  return (
    <main>
      {/* 你的 App 內容 */}
      <CopilotSidebar 
        defaultOpen={true}
        instructions="你是一個專業的財務助理,幫助使用者分析收支。"
        labels={{
          title: "帳單助手",
          initial: "您好!我是您的帳單助手。今天有什麼我可以幫您的嗎?",
        }}
        imageUploadsEnabled
        inputFileAccept="application/pdf"
      />
    </main>
  );
}

image

2. 上傳信用卡帳單(PDF / 圖片)& AI 自動解析、分類消費

由於一筆一筆建立帳單紀錄太麻煩了,我們希望使用者可以直接上傳各種銀行或信用卡帳單

上傳後,AI 會自動解析帳單內容,將支出分類(餐飲、交通、訂閱等),並將資訊整理成可查詢的結構化數據,並且透過客製化的UI交給使用者預覽、確認(Human-In-The-Loop,HITL)

透過這個方式,使用者就不必再整理原始帳單,可以透過AI 主動把資訊整理好

為了達到這個成果,我們需要定義一個「批次處理記帳」的 Copilot Action,讓 AI 解析完 PDF/圖片後,在對話視窗中請使用者預覽解析的結果,使用者確定之後才會存入資料庫

// components/BillUploader.tsx
import { useCopilotAction } from "@copilotkit/react-core";

export const BillUploader = () => {
  useCopilotAction({
    name: "importTransactions",
    description: "從帳單中提取多筆帳單紀錄並進行批次記帳",
    parameters: [
      {
        name: "transactions",
        type: "object[]",
        description: "提取出來的帳單列表",
        attributes: [
          { name: "date", type: "string", description: "日期 (YYYY-MM-DD)" },
          { name: "desccription", type: "string", description: " 說明" },
          { name: "amount", type: "number", description: "金額" },
          { name: "category", type: "string", description: "類別" },
        ],
      },
    ],
    // Render預覽清單
    render: ({ args, status, handler }) => {
      return (
        <div className="p-4 border rounded-lg bg-white shadow-sm">
          <h3 className="font-bold mb-2">📋 帳單解析結果</h3>
          <ul className="text-sm space-y-1 mb-4">
            {args.transactions?.map((tx, i) => (
              <li key={i} className="border-b pb-1">
                {tx.date} - {tx.merchant}: <span className="font-mono text-blue-600">${tx.amount}</span>
              </li>
            ))}
          </ul>
          {status === "inProgress" && (
            <button 
              onClick={() => handler(args)}
              className="w-full bg-blue-500 text-white py-2 rounded"
            >
              確認無誤,幫我匯入資料庫
            </button>
          )}
        </div>
      );
    },
    handler: async ({ transactions }) => {
      // 這裡呼叫你的 API 存入資料庫
      await bulkSaveTransactions(transactions);
      return `已成功匯入 ${transactions.length} 筆紀錄!`;
    },
  });

  return (
    <div className="p-4 border-2 border-dashed border-gray-300 rounded-lg">
      <p className="text-center text-gray-500">
        💡 提示:你可以直接把帳單圖片拖入對話框,或說「幫我分析這張帳單」
      </p>
    </div>
  );
};

另外,我們也要同步更新 AI 的 System Prompt,讓AI知道,如果今天使用者上傳了一個檔案,要做哪些事情、應該如何處理

// app/page.tsx
<CopilotSidebar
  instructions={`
    你是一位精通財務的助理。
    當使用者上傳圖片或 PDF 時,請執行以下步驟:
    1. 仔細辨識文件中的日期、店家名稱、交易金額。
    2. 自動根據店家名稱判斷消費類別(如:全家 -> 餐飲、中油 -> 交通)。
    3. 呼叫 importTransactions 工具,展示解析後的清單供使用者確認。
    請確保金額數字準確,不要遺漏任何一筆明細。
  `}
  ...
/>


3. 使用者可以用「對話/問問題」的方式查詢消費紀錄

使用者可以直接問 AI:「上個月花最多的是哪一類?」、「今年和去年同月份消費差多少?」AI 會根據現有的資料,直接分析、整理好,並且提供結果

而AI 要能回答問題,必須先知道我們有哪些帳單資料。我們使用 useCopilotReadable Hook 將記帳清單提供給 AI,讓 AI 能在需要的時後讀取

// components/TransactionList.tsx
import { useCopilotReadable } from "@copilotkit/react-core";

interface Transaction {
  id: string;
  date: string;
  description: string;
  amount: number;
  category: string;
}

export const TransactionList = ({ transactions }: { transactions: Transaction[] }) => {
  
  // 讓AI能在需要的時候讀取當前的交易資料
  useCopilotReadable({
    description: "使用者所有的消費紀錄列表,包含日期、說明、金額、類別",
    value: transactions,
  });

  return (
    <div className="mt-4">
      {/* 帳單列表 UI */}
    </div>
  );
};

一樣的,我們也要同步更新 AI 的 System Prompt,讓AI知道,如果今天使用者問了帳單分析的問題,應該如何處理

// app/page.tsx
<CopilotSidebar
  instructions={`
    ...
    當使用者詢問關於消費紀錄的問題時:
    1. 檢索透過 useCopilotReadable 提供給你的交易清單。
    2. 根據時間(如:上週、昨天)、類別(如:餐飲、交通)進行篩選與加總。
    3. 如果使用者詢問趨勢,請條列出重點支出。
    4. 回答要簡潔,並適時使用表情符號讓財務報告看起來不枯燥。
    ...
  `}
/>

image

如果使用者問的問題是屬於「類別佔比或統計分析」類型的問題,通常會比較適合使用圖表來呈現

所以我們也可以告知AI我們有顯示圖表的工具,需要的話可以使用在對話視窗中顯示圖表

// components/AnalysisDashboard.tsx
import { useCopilotAction } from "@copilotkit/react-core";
import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";

export const AnalysisDashboard = () => {
  useCopilotAction({
    name: 'displaySpendingAnalysis',
    description: '當使用者詢問類別佔比或統計分析時,展示視覺化圖表。',
    parameters: [
      {
        name: 'data',
        type: 'object[]',
        description: '各類別的加總數據',
        items: {
          attributes: [
            { name: 'category', type: 'string' },
            { name: 'total', type: 'number' }
          ]
        }
      }
    ],
    handler: async () => {
      return '圖表已產生';
    },
    render: ({ args }) => {
      return (
        <div className="h-70 w-full rounded-xl border bg-white p-1">
          <h4 className="mb-2 text-center text-sm font-bold text-black">支出類別分析</h4>
          <div className="h-64">
            <ResponsiveContainer width="100%" height="100%">
              <PieChart>
                <Pie
                  data={args.data}
                  dataKey="total"
                  nameKey="category"
                  fill="#8884d8"
                  label={(props) => props.name}
                  labelLine={{ stroke: 'black' }}
                >
                  {args.data?.map((_: any, index: number) => (
                    <Cell
                      key={`cell-${index}`}
                      fill={['#0088FE', '#00C49F', '#FFBB28'][index % 3]}
                    />
                  ))}
                </Pie>
              </PieChart>
            </ResponsiveContainer>
          </div>
        </div>
      );
    }
  });
}

把這個Action Mount進CopilotKit的context裡面

// app/layout.tsx
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";
import { AnalysisDashboard } from '@components/AnalysisDashboard';

export default function RootLayout({ children }) {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit">
      <AnalysisDashboard/>
      {children}
    </CopilotKit>
  );
}

因為有了新的工具,必須同步更新 AI 的 System Prompt,讓AI知道,如果今天使用者問了帳單分析的問題,應該如何處理、什麼時候要顯示圖表

// app/page.tsx
<CopilotSidebar
  instructions={`
    ...
    當使用者詢問關於消費紀錄的問題時:
    1. 檢索透過 useCopilotReadable 提供給你的交易清單。
    2. 根據時間(如:上週、昨天)、類別(如:餐飲、交通)進行篩選與加總。
    3. 如果使用者詢問趨勢,請條列出重點支出。
    4. 回答要簡潔,並適時使用表情符號讓財務報告看起來不枯燥。
    
    ## 數據分析規範:
    1. 當使用者詢問「支出佔比」、「分類統計」或「分析預算」時,你必須使用 'displaySpendingAnalysis' 工具來呈現圖表。
    2. 在呼叫工具前,請先從提供的 'transactions' 數據中進行加總計算。
    3. 傳入工具的數據應為對象陣列,包含 'category' (類別名稱) 與 'total' (該類別總金額)。
    4. 除了展示圖表,請在對話中簡短總結分析結果,例如指出哪個類別消費最高。
    
    ## 限制:
    - 若數據中沒有相關紀錄,請誠實告知,不要捏造虛擬的消費
    ...
  `}
/>

image

五、結論

AI App 的蓬勃發展,代表著前端工程領域的開發重心已經從「視覺/流程實作」轉向「AI互動操作」的本質變革

過去前端工程致力於開發固定的使用者介面、操作流程以及對應的業務邏輯

現在前端工程師則必須學會利用 CopilotKit、AG-UI  Next.js 建構出一套能讓使用者與 AI 高效協作的互動方式,同時管理AI的互動流程以及隨機性

在未來,前端工程師將會有一大部分的工作,會專注於處理使用者與 AI 的互動/操作,前端工程師必須精確定義 AI 應該看見哪些狀態(Readables),並設計出一套容錯度高的工具(Actions)、確認過程中是否要使用者參與/如何參與,讓 AI 能夠在不偏離意圖的前提下精準操作 App,以達成使用者的目標

管理與操控 AI 的狀態、讓使用者意圖能被高效的處理,將成為未來前端開發者最核心的競爭力