# 首页图片选择功能实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 在首页聊天界面实现拍照/相册选择图片功能,最多3张,图片随文本一起发送 **Architecture:** 使用 image_picker 选择图片,通过 AG-UI 多模态消息格式发送到后端 **Tech Stack:** Flutter, image_picker, AG-UI Protocol --- ### Task 1: 添加 image_picker 依赖 **Files:** - Modify: `apps/pubspec.yaml` **Step 1: 添加依赖** 在 `dependencies` 节点下添加: ```yaml image_picker: ^1.0.7 ``` **Step 2: 安装依赖** Run: `cd apps && flutter pub get` Expected: image_picker 添加成功 --- ### Task 2: 实现 HomeSheet 图片选择功能 **Files:** - Modify: `apps/lib/features/home/ui/screens/home_sheet.dart:1-113` **Step 1: 添加 image_picker 导入和修改 HomeSheet** ```dart import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../core/theme/design_tokens.dart'; class HomeSheet extends StatelessWidget { final Function(List) onImagesSelected; const HomeSheet({super.key, required this.onImagesSelected}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( color: const Color(0x4D0F172A), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () {}, child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(28)), ), child: Column( children: [ Container( width: 36, height: 4, decoration: BoxDecoration( color: AppColors.slate300, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), _buildSheetContent(context), ], ), ), ), ], ), ), ); } Widget _buildSheetContent(BuildContext context) { return SizedBox( height: 280, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildOptionCard( context: context, icon: LucideIcons.camera, label: '拍照', onTap: () => _handleCameraTap(context), ), const SizedBox(width: 24), _buildOptionCard( context: context, icon: LucideIcons.image, label: '相册', onTap: () => _handlePhotoTap(context), ), ], ), ); } Widget _buildOptionCard({ required BuildContext context, required IconData icon, required String label, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: AppColors.blue50, borderRadius: BorderRadius.circular(16), ), child: Icon(icon, size: 32, color: AppColors.blue500), ), const SizedBox(height: 12), Text( label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.slate700, ), ), ], ), ); } Future _handleCameraTap(BuildContext context) async { final picker = ImagePicker(); final image = await picker.pickImage( source: ImageSource.camera, imageQuality: 80, ); if (image != null) { onImagesSelected([image]); } if (context.mounted) { Navigator.of(context).pop(); } } Future _handlePhotoTap(BuildContext context) async { final picker = ImagePicker(); final images = await picker.pickMultiImage( imageQuality: 80, limit: 3, ); if (images.isNotEmpty) { onImagesSelected(images); } if (context.mounted) { Navigator.of(context).pop(); } } } ``` **Step 2: 验证编译** Run: `cd apps && flutter analyze lib/features/home/ui/screens/home_sheet.dart` Expected: No errors --- ### Task 3: 修改 HomeScreen 添加图片预览区 **Files:** - Modify: `apps/lib/features/home/ui/screens/home_screen.dart:1-820` **Step 1: 添加导入和状态变量** 在文件顶部添加导入: ```dart import 'package:image_picker/image_picker.dart'; ``` 在 `_HomeScreenState` 类中添加状态变量: ```dart List _selectedImages = []; ``` **Step 2: 添加图片预览 Widget** 在 `_buildInputContainer` 方法之前添加: ```dart Widget _buildImagePreview() { if (_selectedImages.isEmpty) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.only( left: _inputPadding, right: _inputPadding, bottom: AppSpacing.sm, ), child: Wrap( spacing: AppSpacing.sm, runSpacing: AppSpacing.sm, children: _selectedImages.asMap().entries.map((entry) { final index = entry.key; final image = entry.value; return _buildImageThumbnail(image, index); }).toList(), ), ); } Widget _buildImageThumbnail(XFile image, int index) { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(AppRadius.md), child: Image.file( File(image.path), width: 80, height: 80, fit: BoxFit.cover, ), ), Positioned( top: 4, right: 4, child: GestureDetector( onTap: () => _removeImage(index), child: Container( width: 24, height: 24, decoration: const BoxDecoration( color: AppColors.red500, shape: BoxShape.circle, ), child: const Icon( LucideIcons.x, size: 14, color: AppColors.white, ), ), ), ), ], ); } void _removeImage(int index) { setState(() { _selectedImages.removeAt(index); }); } ``` **Step 3: 修改 _buildInputContainer 调用位置** 在 `_buildInputContainer` 调用之前插入图片预览: ```dart // 在 build 方法中修改 body: SafeArea( child: Column( children: [ _buildHeader(context), Expanded(child: _buildChatArea(context, state)), _buildImagePreview(), // 添加这行 _buildInputContainer(context, state), ], ), ), ``` **Step 4: 修改 _showBottomSheet 传递回调** 将 `_showBottomSheet` 方法修改为: ```dart void _showBottomSheet(BuildContext context) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) => HomeSheet( onImagesSelected: (images) { setState(() { // 限制最多3张 final remaining = 3 - _selectedImages.length; if (remaining > 0) { _selectedImages.addAll(images.take(remaining)); } }); }, ), ); } ``` **Step 5: 验证编译** Run: `cd apps && flutter analyze lib/features/home/ui/screens/home_screen.dart` Expected: No errors --- ### Task 4: 修改 AgUiService 支持多模态消息 **Files:** - Modify: `apps/lib/features/chat/data/services/ag_ui_service.dart:1-643` **Step 1: 添加 base64 导入** 在文件顶部添加: ```dart import 'dart:convert'; import 'package:image_picker/image_picker.dart'; ``` **Step 2: 修改 sendMessage 方法签名** 修改 `sendMessage` 方法接受可选的图片参数: ```dart Future sendMessage(String content, {List? images}) async { final streamToken = ++_activeStreamToken; final runInput = _buildRunInput(content: content, images: images); // ... 后续代码不变 } ``` **Step 3: 修改 _buildRunInput 方法** ```dart Map _buildRunInput({ required String content, List? images, }) { final threadId = _threadId ?? _newUuid(); final runId = _nextId(_runIdPrefix); // 构建多模态内容块 final contentBlocks = >[]; // 添加文本(如果有) if (content.isNotEmpty) { contentBlocks.add({'type': 'text', 'text': content}); } // 添加图片(如果有) if (images != null && images.isNotEmpty) { for (final image in images) { final bytes = await image.readAsBytes(); final base64 = base64Encode(bytes); contentBlocks.add({ 'type': 'image', 'source': { 'type': 'base64', 'media_type': 'image/jpeg', 'data': base64, }, }); } } // 根据内容块数量决定消息格式 final messageContent; if (contentBlocks.isEmpty) { messageContent = ''; } else if (contentBlocks.length == 1 && contentBlocks[0]['type'] == 'text') { // 纯文本使用简单格式(兼容现有逻辑) messageContent = contentBlocks[0]['text']; } else { // 多模态消息使用内容块数组 messageContent = contentBlocks; } return { 'threadId': threadId, 'runId': runId, 'state': {}, 'messages': [ {'id': _nextId('user_'), 'role': 'user', 'content': messageContent}, ], 'tools': _buildTools(), 'context': >[], 'forwardedProps': {}, }; } ``` **Step 4: 修改 _sendMessage 方法传递图片** 在 `home_screen.dart` 中修改 `_sendMessage` 方法: ```dart Future _sendMessage(BuildContext context) async { final content = _messageController.text.trim(); if (content.isEmpty && _selectedImages.isEmpty) return; // 保存图片引用 final images = List.from(_selectedImages); FocusScope.of(context).unfocus(); _messageController.clear(); // 清除图片 setState(() { _selectedImages.clear(); }); await context.read().sendMessage(content, images: images); // ... 后续代码不变 } ``` **Step 5: 需要修改 ChatBloc 接口** 检查 ChatBloc 的 sendMessage 方法签名,如果需要修改,添加 images 参数。 Run: `grep -n "sendMessage" apps/lib/features/chat/presentation/bloc/chat_bloc.dart` 根据结果修改 ChatBloc 和相关调用。 **Step 6: 验证编译** Run: `cd apps && flutter analyze lib/features/chat/data/services/ag_ui_service.dart` Expected: No errors --- ### Task 5: 测试验证 **Step 1: 运行 Flutter 分析** Run: `cd apps && flutter analyze` Expected: No errors **Step 2: 运行单元测试(如果有)** Run: `cd apps && flutter test` Expected: Tests pass --- ### 实施提示 1. Task 2 和 Task 3 可以并行开发(HomeSheet 和 HomeScreen 独立) 2. Task 4 需要在 Task 3 完成后进行,因为需要确定 ChatBloc 接口 3. 如果遇到编译错误,检查 ImagePicker 是否正确导入 4. AG-UI 格式可以参考: https://docs.ag-ui.com (如需要)