Compare commits
2 Commits
87cc15dd53
...
5e8b9af17b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e8b9af17b | ||
|
|
d3580a6a0b |
13
README.md
@@ -1,6 +1,6 @@
|
||||
# AI Chat Assistant Flutter Plugin
|
||||
|
||||
一个功能丰富的 AI 聊天助手 Flutter 插件,专为车载系统设计,支持语音识别、AI 对话、车控命令执行、语音合成等功能。
|
||||
一个功能丰富的 AI 聊天助手 Flutter 插件,支持语音识别、AI 对话、车控命令执行、语音合成等功能。
|
||||
|
||||
## 📱 功能特性
|
||||
|
||||
@@ -489,16 +489,6 @@ void main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 打开 Pull Request
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
|
||||
@@ -529,6 +519,5 @@ void main() {
|
||||
- 🎵 改进语音识别和TTS服务
|
||||
|
||||
#### 技术栈更新
|
||||
- 📦 Flutter SDK 升级到 ^3.5.0
|
||||
- 🔧 添加 Meta 注解支持
|
||||
- 🌐 保持与 basic_intl 国际化包的兼容
|
||||
|
||||
368
docs/AI_Chat_Assistant PRD.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# AI_Chat_Assistant PRD
|
||||
|
||||
# AI 聊天助手需求设计文档
|
||||
|
||||
### 文档介绍
|
||||
|
||||
| 项目 | 信息 |
|
||||
| --- | --- |
|
||||
| 产品名称 | AI Chat Assistant Flutter Plugin |
|
||||
| 版本 | v1.0.0+1 |
|
||||
| 文档版本 | v1.0 |
|
||||
| 创建日期 | 2025-09-23 |
|
||||
| 产品经理 | - |
|
||||
| 技术负责人 | - |
|
||||
| 目标平台 | Flutter (Android/iOS) |
|
||||
|
||||
### 修订记录
|
||||
|
||||
| 版本 | 日期 | 作者 | 内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1.0 | 2025-09-23 | - | AI助手需求整理 |
|
||||
|
||||
## **1. 引言**
|
||||
|
||||
### **1.1 文档目的**
|
||||
|
||||
本文档旨在定义“AI Chat Assistant Flutter Plugin”项目的详细功能需求、非功能需求、技术架构及项目规划。它为设计、开发、测试及项目相关人员提供了统一的参考依据和验收标准。
|
||||
|
||||
### **1.2 适用范围**
|
||||
|
||||
本文档目标读者为:AI Chat Assistant Flutter Plugin 的系统设计人员、开发人员、测试人员、产品经理以及项目管理人员。
|
||||
|
||||
### **1.3 名词解释**
|
||||
|
||||
| **序号** | **名词术语** | **名词解释** |
|
||||
| --- | --- | --- |
|
||||
| 1 | Flutter Plugin | 一种可复用的代码包,用于为Flutter应用添加特定的原生平台功能。 |
|
||||
| 2 | ASR | Automatic Speech Recognition,自动语音识别,将语音转换为文本。 |
|
||||
| 3 | TTS | Text-To-Speech,语音合成,将文本转换为语音。 |
|
||||
| 4 | SSE | Server-Sent Events,一种服务器向客户端推送数据的技术。 |
|
||||
| 5 | 车控命令 | 通过语音或界面触发的,用于控制车辆功能(如门锁、空调)的指令。 |
|
||||
|
||||
### **1.4 统一异常处理**
|
||||
|
||||
1. **网络异常**:语音识别或AI对话过程中出现网络错误,应有明确提示(如:“网络连接失败,请检查后重试”),并提供重试选项。
|
||||
2. **权限拒绝**:当未授予麦克风或音频录制权限时,应引导用户前往系统设置开启权限。
|
||||
3. **语音识别失败**:识别无结果或置信度过低时,应提示用户“抱歉,我没有听清,请再说一遍”。
|
||||
4. **命令执行失败**:车控命令下发后执行失败,应通过TTS和UI界面明确反馈失败原因。
|
||||
|
||||
## 2. 产品概述
|
||||
|
||||
### 2.1 产品简介
|
||||
|
||||
AI Chat Assistant 是一个功能丰富的 AI 聊天助手 Flutter 插件,提供完整的AI语音交互解决方案,支持语音识别、智能对话、车控命令执行和语音反馈等功能。
|
||||
|
||||
聊天助手现在分为车控助手和用车助手,车控助手支持 20+ 种车辆控制命令(空调、车窗、车门、座椅加热等);用车助手会根据用户手册的内容智能回复。
|
||||
|
||||
### 2.2 产品流程图
|
||||
|
||||
现有的产品主要流程是用户长按点击语音按钮,经过权限判断之后,需要将语音转化为文本,然后通过分类结果来判断是普通问答(知识库)还是车控指令。
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户点击语音按钮] --> B[startVoiceInput]
|
||||
B --> C[检查麦克风权限]
|
||||
C --> D[开始录音/ASR]
|
||||
D --> E[实时显示识别结果]
|
||||
E --> F[用户松开按钮]
|
||||
F --> G[stopAndProcessVoiceInput]
|
||||
G --> H[调用reply处理文本]
|
||||
H --> I[文本分类]
|
||||
I --> J{分类结果}
|
||||
J -->|车控命令| K[handleVehicleControl]
|
||||
J -->|普通问答| L[answerQuestion - ChatSseService]
|
||||
J -->|错误问题| M[answerWrongQuestion]
|
||||
K --> N[解析车控命令]
|
||||
N --> O[执行TTS播报]
|
||||
O --> P[执行车控命令]
|
||||
P --> Q[生成执行反馈]
|
||||
Q --> R[更新UI显示]
|
||||
L --> S[SSE流式对话]
|
||||
S --> T[实时TTS播报]
|
||||
T --> R
|
||||
```
|
||||
|
||||
### 2.3 产品目的
|
||||
|
||||
- 提供开箱即用的AI语音交互能力
|
||||
- 支持自然语言车控命令识别与执行
|
||||
- 轻量级架构,易于集成和定制
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 用户页面展示
|
||||
|
||||
#### **3.1.1 浮动图标组件(ChatFloatingIcon)**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
在主应用界面上提供一个常驻的、可随意拖拽的AI助手入口。
|
||||
|
||||
- **功能描述**
|
||||
|
||||
一个可拖拽的浮动按钮,点击展开全屏聊天界面,长按直接启动语音输入(只有聊天窗口)。
|
||||
|
||||
- **需求说明**
|
||||
- **拖拽吸附**:支持在屏幕内自由拖拽,松开时自动吸附到最近的屏幕边缘。
|
||||
- **交互响应**:
|
||||
- **点击**: 进入全屏的聊天页面,全屏模式为一个新的页面
|
||||
- **长按**:变为聆听模式,直接开始语音录制,出现聊天窗口。
|
||||
|
||||
聊天窗口的位置需要根据浮动按钮的位置变化,如果浮动按钮位置过于靠上,则聊天窗口显示在浮动按钮的下方,默认情况下显示在浮动按钮的上方
|
||||
|
||||
- **状态指示**:通过动画(如呼吸效果)表示待机、聆听、思考等不同状态。
|
||||
|
||||
默认为待机状态,长按会进入聆听状态,思考中的状态会在聊天显示思考中的状态聊天气泡
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### **3.1.2 浮动聊天窗口(ChatPopup)**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户与AI助手进行可视化的文本和语音交互。
|
||||
|
||||
- **功能描述**
|
||||
|
||||
提供弹出式聊天窗口,展示对话记录并提供输入方式。
|
||||
|
||||
- **需求说明**
|
||||
|
||||
长按浮动按钮即可出现浮动聊天窗口,长按状态下展示聆听中,放开后则聆听结束,变为思考中状态。
|
||||
|
||||
思考中状态可以手动点击 **停止回答** 可以终止AI对话,直到回答完成,则不可点击停止回答。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### **3.1.3 全屏聊天界面(ChatFullscreen)**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户与AI助手进行可视化的文本和语音交互。
|
||||
|
||||
- **功能描述**
|
||||
|
||||
提供全屏聊天界面,展示对话记录并提供输入方式。全屏的聊天页面功能更加丰富,分为不同的场景
|
||||
|
||||
- **需求说明**
|
||||
- 全屏聊天模式下会保留之前浮动聊天窗口的聊天记录
|
||||
- 不同的场景为不同的AI助手,现有两种场景,购车助手和用车助手
|
||||
- 顶部会有可配置的功能按钮区域(用户服务/购车服务)
|
||||
- 功能按钮下方会有快捷对话的区域,可以点击换一换来更换快捷指令
|
||||
- 聊天页面底部会有功能栏,分别为**删除当前对话**,**按住说话**,**切换文本输入**
|
||||
- 如果存在本地对话记录,点击 **显示历史对话记录** 会加载之前的5条对话记录,同样可以通过上拉来加载历史对话记录
|
||||
|
||||

|
||||
|
||||
### 3.2 AI对话场景
|
||||
|
||||
#### **3.2.1 购车助手场景**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户未绑定车辆的情况下,爱车页面变为购车页面,购车页面也会有AI助手
|
||||
|
||||
- **功能描述**
|
||||
|
||||
购车页面的AI助手没有聆听模式,只能点击进入全屏的聊天页面,首次的回复词也会有变化;功能按钮区域和快捷回复区域都会跟用车助手不同
|
||||
|
||||
- **需求说明**
|
||||
- 浮动按钮点击可进入全屏的AI助手页面,顶部的功能按钮为热门车型推荐,同样是icon+text的按钮,点击之后会快捷输入车型的名称,AI助手会回复车型的相关信息,同时快捷对话的区域会按照对话来进行变化;
|
||||
- 购车助手同样可以通过文本的语义来进行推荐车型,以及解读购车的优惠信息;
|
||||
- 如果在购车助手场景下,本身并没有绑定车辆,如果有车控的语音输入,则返回:您还未绑定车辆,无法使用车控指令;
|
||||
- 如果是用车助手场景下,包含购车助手的全部功能,同样可以回复相应车型来获取配置信息以及购车政策。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### **3.2.2 用车助手场景**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户在绑定车辆的情况下,进入爱车页面,使用AI助手则我用车助手场景
|
||||
|
||||
- **功能描述**
|
||||
|
||||
用车助手场景下的AI助手不同于购车场景,可以按照文本的输入分为三种:普通问答,车控指令,错误问题反馈
|
||||
|
||||
- **需求说明**
|
||||
- 用车场景下的功能按钮为官方推荐,内容包括智能场景推荐、数据埋点和运营位,其中智能场景推荐包括售后维保提醒,智能场景建议;数据埋点包括能耗报告,行为分析,用户习惯操作;运营位则是推荐的车型,用户复购建议;
|
||||
- 点击 常见功能按钮,会输入一个常见功能的聊天记录,返回的数据为经过大数据分析之后得到的常用功能,聊天气泡中显示多个按钮,可以点击进行快捷回复;
|
||||
- 点击 更多工具按钮跟常见功能按钮一样,会返回一条聊天记录,里面会包含多个按钮,点击按钮可以进行快捷回复;
|
||||
- 快捷回复区域是可以变化的,会根据对话的最后一个回复进行变化,快捷回复区域会跟在最后一个对话的底部;例如刚进入会跟在 功能按钮的底部,但是新的聊天记录出现后,就会自动拼接到聊天气泡的底部(聊天气泡类型分为多种,后面会详细介绍);
|
||||
|
||||

|
||||
|
||||
#### **3.2.3 用户唤醒场景**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
在开通语音唤醒功能之后,购车页面和爱车页面都可以使用语音唤醒
|
||||
|
||||
- **功能描述**
|
||||
|
||||
用户可以通过语音唤醒功能在不需要点击按钮的情况下激活AI助手,提高使用便捷性和用户体验。当用户说出设定的唤醒词(你好,众众)后,系统会自动启动AI助手并进入聆听模式。
|
||||
|
||||
- **需求说明**
|
||||
|
||||
用户可通过说出"你好,众众"唤醒AI助手,系统将自动进入聆听模式。语音唤醒功能需要用户在设置(App设置页面或AI助手的设置页面)中开启,并且可以在有较高环境噪音的情况下进行降噪处理。唤醒后,助手会给予明确的视觉和声音反馈,确保用户知道系统已经准备好接收指令。
|
||||
|
||||
语音唤醒功能需要一定的权限(例如麦克风访问权限),系统会在首次使用时提示用户授予必要权限。为保护用户隐私,语音数据会在本地进行初步处理,只有确认唤醒后才会发送到服务器。此功能在使用过程中会考虑电池消耗,在低电量模式下可能会自动降低敏感度或暂时关闭。
|
||||
|
||||
### 3.3 聊天模块
|
||||
|
||||
#### **3.3.1 聊天气泡类型**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户与AI助手对话过程中,会展示不同类型的聊天气泡以适应各种交互需求。
|
||||
|
||||
- **功能描述**
|
||||
|
||||
系统支持多种聊天气泡类型,包括文本气泡、卡片气泡、按钮气泡、链接气泡和富媒体气泡等,以满足不同场景下的交互需求。
|
||||
|
||||
- **需求说明**
|
||||
- 文本气泡:展示普通文字对话内容,完整支持Markdown格式;
|
||||
|
||||
Markdown支持包括普通文本格式化、表格布局以及图片嵌入,提供更丰富的内容展示方式
|
||||
|
||||
- 卡片气泡:用于展示结构化信息,如车辆信息、维保记录等;
|
||||
- 按钮气泡:包含可交互按钮,用于快速回复或执行操作;
|
||||
- 富媒体气泡:支持图片、视频、语音等多媒体内容展示;
|
||||
|
||||
富媒体气泡可能适用于显示特定的信息,或者是视频的教程,轮播的图片
|
||||
|
||||
- 链接气泡:用于显示一个链接,包含Icon,Title,Description,以及一个可以点击的链接;
|
||||
|
||||
点击链接可以进入webview,链接气泡主要是用于详细介绍某些活动或者查看详情
|
||||
|
||||
- 业务气泡:用于特定业务的定制化气泡,会根据对话的业务场景,显示不同的气泡。
|
||||
|
||||
业务气泡不同于其他的气泡,可以在用户询问金融方案后显示返回的计算结果,在结果后面还有一个按钮,可以进入金融计算器的页面;同时在用户说出我希望订购某款车型时,会出现车型的信息,并且有一个立即订购的按钮。业务气泡是根据返回的数据来判断具体的业务场景,混合显示的气泡类型。
|
||||
|
||||
|
||||
#### **3.3.2 普通问答流程**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户与AI助手对话过程中,AI助手会把语音转换为文本,通过文本的语义来判断场景,是普通问答或者是车控指令
|
||||
|
||||
- **功能描述**
|
||||
|
||||
普通问答现在只有用车助手这个场景,后续增加新的场景:购车助手;需要先通过文本语义判断是哪个场景,然后根据相应场景来回复问题。
|
||||
|
||||
- **需求说明**
|
||||
|
||||
不管是购车场景还是用车场景,或者是开放式对话,都有一个问答的流程,场景的区分不是前端来判断。现在流程是将语音转化的文本通过接口发给后台服务,然后后台会根据body里面的text和conversation_id 来返回一个文件流,客户端这边会把文件流的全部数据接受完成后合并为一个markdown语法的文本。
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
|
||||
A[文本数据] --> B{Chat api}
|
||||
B --> C[文件流] --> D{SSE Service} --> E[Markdown文本]
|
||||
E --> F{TTS} --> G[播放语音]
|
||||
E --> H{Message} --> I[显示]
|
||||
|
||||
```
|
||||
|
||||
现在有两个新的调整方案,一个是Socket连接,另外一个方案是在现有的流程基础上新增消息类型。
|
||||
|
||||
1. Socket方案
|
||||
|
||||
每次进入AI助手会话会创建一个随机的taskId,然后每次对话都会有一个conversationId,使用taskId 连接Socket成功后,就可以通过Socket发送语音或者文本消息。然后Socket 会返回三种不同的消息类型,`it消息 (识别文本)`,`gb消息 (回复文本)`,`tts消息 (语音数据)`。
|
||||
|
||||
流程图如下:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 客户端
|
||||
participant S as 服务器
|
||||
|
||||
Note over C, S: 第一步:建立WebSocket连接
|
||||
C->>S: WebSocket Handshake (携带认证信息)
|
||||
|
||||
Note over C, S: 第二步:客户端发送语音数据
|
||||
C->>S: 发送消息: {taskId, conversationId, audioData}
|
||||
|
||||
Note over C, S: 第三步:服务器异步返回多条消息
|
||||
par 并行处理与推送
|
||||
S-->>C: it消息 (识别文本)
|
||||
and
|
||||
S-->>C: gb消息 (回复文本)
|
||||
and
|
||||
S-->>C: tts消息 (语音数据)
|
||||
end
|
||||
|
||||
Note over C, S: 连接保持,可继续发送新语音或关闭
|
||||
```
|
||||
|
||||
1. HTTP方案
|
||||
|
||||
HTTP方案是保持原有流程的情况下进行扩展,chat API 不再是返回一个数据流,而是返回一个json数据,json数据里面会有不同的消息返回,对应上面不同的气泡类型,消息类似分为`stream`,`text`,`media`,`bussines`,`stream`消息就是之前的数据流的消息,通过`messageId`就可以获取到数据;`text`消息是普通的文本消息,可以实现上面的链接气泡,按钮气泡;`media`消息则可以实现负责的图文混排,或者是一个单纯的视频教程;`bussines`消息主要以业务相关的,会根据业务类型显示不一样的样式,内容和按钮也会有变化。
|
||||
|
||||
```json
|
||||
{
|
||||
"conversationId": 1,
|
||||
"taskId": "task_123",
|
||||
"code": 200,
|
||||
"scenario": "buying_assistant",
|
||||
"messageList": [
|
||||
{
|
||||
"messageId": "messageId_xxxxx",
|
||||
"type": "stream"
|
||||
},
|
||||
{
|
||||
"messageId": "messageId_xxxxx",
|
||||
"type": "text",
|
||||
"content": "xxxxx"
|
||||
},
|
||||
{
|
||||
"messageId": "messageId_xxxxx",
|
||||
"type": "media"
|
||||
},
|
||||
{
|
||||
"messageId": "messageId_xxxxx",
|
||||
"type": "bussines",
|
||||
"bussineType": "vehicle_commendation"
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### **3.3.3 车控指令流程**
|
||||
|
||||
- **用户场景**
|
||||
|
||||
用户与AI助手对话过程中,AI助手会把语音转换为文本,通过文本的语义来判断场景,是普通问答或者是车控指令
|
||||
|
||||
- **功能描述**
|
||||
|
||||
如果根据分类结果,将语音或者文本信息归类为车控指令,则会进入车控指令的流程。识别语音指令中的车控意图,解析参数,并调用相应的车辆控制接口。
|
||||
|
||||
- **需求说明**
|
||||
- **命令识别**:支持识别20+种车控命令类型。
|
||||
- **参数解析**:能从指令中解析出关键参数(如温度值、座椅位置)。
|
||||
- **执行反馈**:命令执行成功或失败,都必须有明确的TTS和UI反馈。
|
||||
- **可扩展性**:提供抽象类`VehicleCommandHandler`,允许开发者根据具体车型实现自定义控制逻辑。
|
||||
- **支持命令示例**:
|
||||
- **车门控制**:上锁、解锁
|
||||
- **车窗控制**:开启、关闭
|
||||
- **空调控制**:开启、关闭、温度调节、极速降温
|
||||
- **座椅控制**:主/副驾驶座椅加热、通风
|
||||
- **后备箱控制**:开启、关闭
|
||||
- **特殊功能**:鸣笛、车辆定位、一键备车、一键融雪
|
||||
|
||||
## 参考文件
|
||||
|
||||
[AI Assistant in One App.pdf](https://telekom-my.sharepoint.de/:b:/r/personal/guangfei_zhao_t-systems_com/Documents/Dokumente/AI%20Assistant%20in%20One%20App.pdf?csf=1&web=1&e=On4V2o)
|
||||
|
||||
[https://www.figma.com/design/YBuYyBdkRRyP4YCPuzstDX/AI智能体?node-id=21-738&t=1eLWMSwO6XrA6mFE-0](https://www.figma.com/design/YBuYyBdkRRyP4YCPuzstDX/AI%E6%99%BA%E8%83%BD%E4%BD%93?node-id=21-738&t=1eLWMSwO6XrA6mFE-0)
|
||||
|
||||
[分子送检系统需求规格书.docx](images/%E5%88%86%E5%AD%90%E9%80%81%E6%A3%80%E7%B3%BB%E7%BB%9F%E9%9C%80%E6%B1%82%E8%A7%84%E6%A0%BC%E4%B9%A6.docx)
|
||||
BIN
docs/images/Screenshot_20250923_162311.jpg
Normal file
|
After Width: | Height: | Size: 842 KiB |
BIN
docs/images/db1d92f4-107d-46c9-8154-fc58cb471acc.png
Normal file
|
After Width: | Height: | Size: 992 KiB |
BIN
docs/images/image 1.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/image 2.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/images/image 3.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
docs/images/image 4.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/images/image.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/images/分子送检系统需求规格书.docx
Normal file
@@ -80,13 +80,6 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
basic_intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../packages/basic_intl"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -539,6 +532,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
t_basic_intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../packages/basic_intl"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:ai_chat_assistant/models/chat_message.dart';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../enums/message_status.dart';
|
||||
import '../widgets/chat_box.dart';
|
||||
|
||||
@@ -37,8 +37,7 @@ class _MainScreenState extends State<MainScreen> {
|
||||
),
|
||||
),
|
||||
// FloatingIcon(),
|
||||
ChatPopup(
|
||||
)
|
||||
ChatPopup()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class LocationService {
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:async';
|
||||
import 'package:ai_chat_assistant/ai_chat_assistant.dart';
|
||||
import 'package:ai_chat_assistant/utils/common_util.dart';
|
||||
import 'package:ai_chat_assistant/utils/tts_util.dart';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
|
||||
@@ -13,8 +13,11 @@ class ChatWindowContent extends StatefulWidget {
|
||||
final double? maxHeight;
|
||||
final double? chatWidth;
|
||||
|
||||
final VoidCallback? onCloseWindow;
|
||||
|
||||
const ChatWindowContent({
|
||||
super.key,
|
||||
this.onCloseWindow,
|
||||
this.animationController,
|
||||
this.minHeight,
|
||||
this.maxHeight,
|
||||
@@ -55,6 +58,9 @@ class _ChatWindowContentState extends State<ChatWindowContent> {
|
||||
}
|
||||
|
||||
void _openFullScreen() async {
|
||||
if (widget.onCloseWindow != null) {
|
||||
widget.onCloseWindow!();
|
||||
}
|
||||
final messageService = context.read<MessageService>();
|
||||
|
||||
Navigator.of(context).push(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:ai_chat_assistant/utils/common_util.dart';
|
||||
import 'package:ai_chat_assistant/widgets/rotating_image.dart';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import '../enums/message_status.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import '../services/message_service.dart';
|
||||
import '../pages/full_screen.dart';
|
||||
import 'floating_icon_with_wave.dart';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import '../utils/assets_util.dart';
|
||||
|
||||
class ChatFloatingIcon extends StatefulWidget {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'assistant_avatar.dart';
|
||||
|
||||
|
||||
@@ -65,6 +65,20 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
});
|
||||
}
|
||||
|
||||
_openFullScreenPage() async {
|
||||
final messageService = MessageService.instance;
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService, // 传递同一个单例实例
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
@@ -89,16 +103,17 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
|
||||
Widget _buildPopupBackground() {
|
||||
return Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final messageService = MessageService.instance;
|
||||
_removeOverlay();
|
||||
messageService.abortReply();
|
||||
messageService.initializeEmpty();
|
||||
},
|
||||
child: SizedBox.expand()
|
||||
),
|
||||
);
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final messageService = MessageService.instance;
|
||||
_removeOverlay();
|
||||
messageService.abortReply();
|
||||
messageService.initializeEmpty();
|
||||
},
|
||||
child: SizedBox.expand(
|
||||
child: Container(
|
||||
color: Colors.black45.withValues(alpha: 0.1),
|
||||
))));
|
||||
}
|
||||
|
||||
Widget _buildPopupContent(BoxConstraints constraints) {
|
||||
@@ -112,6 +127,7 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
right: position.right,
|
||||
bottom: position.bottom,
|
||||
child: ChatWindowContent(
|
||||
onCloseWindow: _removeOverlay,
|
||||
animationController: _partScreenAnimationController,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight,
|
||||
@@ -131,20 +147,9 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
// 先执行外部传入的 onTap(如果有)
|
||||
if (widget.child!.onTap != null) {
|
||||
widget.child!.onTap!();
|
||||
} else {
|
||||
// 默认行为:关闭弹窗并打开全屏
|
||||
final messageService = context.read<MessageService>();
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService,
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// 默认行为:关闭弹窗并打开全屏
|
||||
await _openFullScreenPage();
|
||||
},
|
||||
onLongPress: () async {
|
||||
debugPrint('⏳ FloatingIcon onLongPress triggered! (using child properties)');
|
||||
@@ -195,17 +200,7 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
icon: widget.icon,
|
||||
onTap: () async {
|
||||
debugPrint('🖱️ FloatingIcon onTap triggered!');
|
||||
final messageService = context.read<MessageService>();
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService, // 传递同一个单例实例
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await _openFullScreenPage();
|
||||
},
|
||||
onLongPress: () async {
|
||||
debugPrint('⏳ FloatingIcon onLongPress triggered!');
|
||||
|
||||
@@ -5,7 +5,7 @@ import '../pages/full_screen.dart';
|
||||
import '../screens/part_screen.dart';
|
||||
import 'floating_icon_with_wave.dart';
|
||||
import 'dart:async';
|
||||
import 'package:basic_intl/intl.dart';
|
||||
import 'package:t_basic_intl/intl.dart';
|
||||
import '../utils/assets_util.dart';
|
||||
|
||||
class FloatingIcon extends StatefulWidget {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: basic_intl
|
||||
name: t_basic_intl
|
||||
description: Basic internationalization utilities for ai_chat_assistant
|
||||
version: 0.2.0
|
||||
|
||||
|
||||
14
pubspec.lock
@@ -73,13 +73,6 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
basic_intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/basic_intl"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -447,6 +440,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
t_basic_intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/basic_intl"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -19,7 +19,7 @@ dependencies:
|
||||
provider: ^6.1.5
|
||||
flutter_tts: ^4.2.0
|
||||
# basic_intl: 0.2.0
|
||||
basic_intl:
|
||||
t_basic_intl:
|
||||
path: packages/basic_intl
|
||||
# flutter_ingeek_carkey: 1.4.7
|
||||
# app_car:
|
||||
|
||||