iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Mobile Development

《30 天 Flutter:跨平台 AI 行程規劃 App》系列 第 28

Day 28:從雛形到產品,讓 AI 編輯器不再只是展示品

  • 分享至 

  • xImage
  •  

昨天將設計藍圖化為實際可運作的 UI,並透過假資料模擬了 AI 聊天編輯器的核心功能,今天,將迎來另一個關鍵時刻:串接真實的 AI API

與假資料的無痛串接不同,真實的網路請求充滿了不確定性,需要考慮:

  • 網路延遲:真實的 API 回應可能需要幾秒鐘,甚至更久,必須在 UI 上給予使用者回饋。
  • 錯誤處理:網路請求可能失敗,或 API 回傳非預期的錯誤訊息,需要妥善處理這些例外情況。
  • 串流處理:為了模擬 AI 逐字回覆的體驗,後端通常會以串流方式推送資料,需要能夠正確地解析並更新 UI。

因此,今天的文章將聚焦於如何將 FakeChatRepository 替換為 ApiChatRepository,並為各種可能發生的狀態(如載入中、錯誤、成功)設計對應的 UI 顯示,確保使用者能獲得流暢且穩定的體驗。


從假資料到真實 API

還記得 Day 26 設計的 ChatRepository 抽象類別嗎?這個設計現在派上用場了!不需要修改任何 ChatController 或 UI 邏輯,只需要新增一個實作 ChatRepository 的類別,並在 Riverpod 中將其注入即可,萬分感謝前天的自己讓我可以速速完成~

1. 建立 ApiChatRepository

首先,建立一個新的 ApiChatRepository 類別來處理真實的 API 請求。這裡將使用 Dio 套件來發送 HTTP 請求,並處理來自後端的串流回應。

程式碼範例

// repositories/chat_repository.dart

class ApiChatRepository implements ChatRepository {
  final Dio _dio;

  ApiChatRepository(this._dio);

  @override
  Stream<ChatMessage> streamReply({
    String? sessionId,
    required String text,
    Trip? itinerary,
  }) async* {
    final response = await _dio.post(
      '/api/ai-chat',
      data: {
        'itinerary': itinerary?.toJson(),
        'text': text,
        'sessionId': sessionId,
      },
      options: Options(responseType: ResponseType.stream),
    );

    await for (final chunk in response.data.stream) {
      yield ChatMessage.fromJson(chunk);
    }
  }
}

這個 streamReply 方法會發送一個 POST 請求到後端 API,並將 itinerary 和使用者輸入的 text 一併送出。由於後端是以串流方式回傳資料,我們也將 responseType 設定為 stream,並使用 await for 語法來逐塊處理收到的資料。

2. 更新 chatRepositoryProvider

現在,只需要簡單地修改 chatRepositoryProvider,讓它從原本的 FakeChatRepository 指向新建立的 ApiChatRepository

程式碼範例

// providers/chat_provider.dart

final chatRepositoryProvider = Provider<ChatRepository>((ref) {
  // 將假資料替換為真實 API 實作
  // return FakeChatRepository(); 
  return ApiChatRepository(ref.read(dioProvider));
});

透過這種抽象化的設計,就可以成功地在不改動任何 UI 程式碼的前提下,完成了資料源的切換。


狀態與錯誤處理

當串接真實 API 後,必須在 UI 上處理各種狀態,避免畫面無回饋感或無預警崩潰。

1. 處理「載入中」狀態

AI 回應可能需要幾秒鐘,這段期間 UI 應該顯示一個明確的載入狀態。可以在 ChatControllersendText 方法中,在開始網路請求時,為 AI 訊息加上一個 pending 標記。

程式碼範例

// providers/chat_provider.dart

Future<void> sendText(String text) async {
  // 1. 添加使用者訊息
  // 2. 添加 AI 佔位泡泡,並標記為 pending
  final aiPending = ChatMessage(
    sender: ChatSender.ai,
    timestamp: DateTime.now(),
    text: '',
    pending: true,
  );
  state = [...state, aiPending];

  // 3. 開始串流,並更新狀態
  // ...
}

在 UI 層,可以根據 ChatMessage 中的 pending 標記,在訊息泡泡上顯示一個小的 Loading 動畫。

2. 處理「錯誤」狀態

當網路請求失敗或伺服器回傳錯誤時,需要明確地通知使用者。在 StreamSubscriptiononError 回呼中,可以將最後一則 AI 訊息的狀態更新為「錯誤」,並顯示一個「重試」按鈕。

程式碼範例

// providers/chat_provider.dart

.listen(
  (chunk) {
    // 成功時的更新邏輯
  },
  onError: (e, st) {
    // 錯誤時的處理
    final lastIndex = state.lastIndexWhere((m) => m.id == aiPending.id);
    if (lastIndex == -1) return;

    final updated = state[lastIndex].copyWith(
      pending: false,
      isError: true,
      text: '發生錯誤,請稍後再試。', // 提供使用者友善的錯誤訊息
    );
    final newList = [...state];
    newList[lastIndex] = updated;
    state = newList;
  },
);

成果展示

在完成了上述的實作後,現在的 AI 聊天編輯器已經是一個能應對真實世界挑戰了。

  • AI 處理中:當 AI 正在思考時,訊息會顯示 Loading 狀態,告訴使用者正在處理中。
  • 預覽行程:AI 回應後,可以立即看到生成的行程,並選擇預覽或繼續編輯。
  • 錯誤處理:即使網路中斷,畫面也不會崩潰,而是會顯示友善的錯誤訊息。

回應成功

調整現有行程

回應失敗

點擊刷新按鈕重新送


上一篇
Day 27 - 讓 AI 行程編輯器動起來:畫面實作與假資料串接
下一篇
Day 29 - 藍圖旅人的誕生:從命名、Icon 到啟動畫面的全紀錄
系列文
《30 天 Flutter:跨平台 AI 行程規劃 App》29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言