# 字幕复制按钮与主窗口语音联动实现总结

## 背景

`voice-assistant` 是 `kylin-aiassistant` 的从属语音助手窗口。两者都会使用麦克风输入或语音输出能力，因此需要在启动独立语音助手窗口时避免和主窗口正在执行的朗读、语音输入发生冲突。

本次改动包含两个功能：

1. `voice-assistant` 字幕页中，AI 回答完成后，在 AI 气泡底部追加 32px 操作区，并在左侧显示复制按钮。
2. `kylin-aiassistant` 主窗口点击语音聊天按钮唤起 `voice-assistant` 前，主动中断主窗口正在进行的朗读或语音输入。

## 字幕复制按钮

### 涉及文件

- `voice-assistant/chatwidget.h`
- `voice-assistant/chatwidget.cpp`
- `voice-assistant/mainwindow.cpp`
- `voice-assistant/src.qrc`
- `voice-assistant/image/icon/copy.svg`

### 实现说明

字幕气泡当前不再直接依赖 `QLabel + setStyleSheet`，而是使用 `ChatWidget` 内部的自绘气泡控件。为了在 AI 气泡下方追加操作区，AI 气泡改为复合结构：

- 外层为 `bubbleContainer`。
- 上方为 AI 回复文本 `QLabel`。
- 下方为固定高度 32px 的 `actionBar`。
- `actionBar` 默认隐藏，只在 AI 回答完成后显示。

当前字幕页布局规则如下：

- 用户气泡靠右对齐，始终保留右侧 8px 边距。
- AI 气泡靠左对齐，始终保留左侧 8px 边距。
- 气泡宽度按内容自适应，不再固定拉满整行。
- 气泡最大宽度限制为字幕页可视区域宽度减去左右边距 `8 + 8`。
- 对话列表空白时从顶部开始布局；清空会话后重新进入新会话时仍从顶部开始追加。
- 字幕页文本右键菜单已关闭，避免出现默认文本上下文菜单。

滚动条实现当前已回退为最初版本，继续使用 `chatwidget.cpp` 中原有的 `verticalScrollBar()->setStyleSheet(...)`，没有保留后续尝试过的 `drawScrollBarGroove`、透明调色板或 `QProxyStyle` 方案。

复制按钮使用资源路径：

```cpp
:/image/icon/copy.svg
```

并在 `voice-assistant/src.qrc` 中注册：

```xml
<file>image/icon/copy.svg</file>
```

### 显示时机

`MainWindow::handleAiReplyFinished()` 在收到 AI 回复完成信号后，根据最终可读文本 `textToRead` 判断是否显示操作区：

```cpp
if (!textToRead.trimmed().isEmpty()) {
    ui->page_chat->showLatestAIBubbleActions();
}
```

因此按钮不会在 AI 流式回复过程中出现，只在本轮回答完成后出现。

### 交互状态

复制按钮包含：

- 默认态：透明背景。
- Hover 态：浅色背景。
- Pressed 态：更深的浅色背景。
- Tooltip：默认显示 `Copy`。
- 点击后：将当前 AI 气泡文本复制到剪贴板，并弹出 `kdk::KBallonTip` 提示“复制成功”。

复制成功提示当前实现细节：

- 使用 `KBallonTip`，不是 tooltip。
- 提示文本为“复制成功”。
- 提示框使用无边框、透明背景窗口属性。
- 显示位置按 `voice-assistant` 主窗口中心点计算，在窗口中心附近弹出。
- 当前代码里额外设置了 `setContentsMargins(16, 16, 16, 16)` 和 `setTipTime(3000)`。

### 审查修复

自审发现 AI 气泡文本区在操作栏显示后仍保留底部圆角，可能导致文本区和 32px 操作区衔接处出现视觉断层。

已修复为：

- 操作栏未显示时，AI 文本气泡四角均为 10px 圆角。
- 操作栏显示后，AI 文本气泡底部圆角切换为 0px，底部圆角交给操作栏承载。

同时，错误气泡和普通 AI 气泡共用同一套自绘气泡结构：

- 普通 AI 气泡背景色为 `#F7F7F7`。
- 用户气泡背景色为 `#C4E1FF`。
- 错误气泡文字色为 `#D92D20`。
- 所有气泡继续使用 `Qt::PlainText` 显示文本。

## 主窗口语音联动

### 涉及文件

- `kylin-aiassistant/msgpane.cpp`
- `kylin-aiassistant/global/voiceusingmanager.h`
- `kylin-aiassistant/global/voiceusingmanager.cpp`

### 实现说明

主窗口标题栏的语音聊天按钮调用：

```qml
msgPane.startVoiceChat()
```

本次在 `CMsgPane::startVoiceChat()` 开始处新增音频占用中断：

```cpp
VoiceUsingManager::getInstance()->setCurVoiceWaitingUsingMoudule(VoiceUsingManager::VoiceChatInput);
Q_EMIT VoiceUsingManager::getInstance()->sigInterruptUsing(true);
```

这样在判断 `voice-assistant` 是否已运行、调用 DBus `ShowWindow()` 或启动独立进程前，会先通知主窗口内所有语音占用模块中断当前操作。

### 联动范围

当前主窗口已有模块会监听 `VoiceUsingManager::sigInterruptUsing`：

- `ReaderControl`：关闭朗读会话，中断朗读。
- `RecorderControl`：停止语音识别并关闭语音识别会话，中断语音输入。

因此主窗口正在朗读、正在语音输入，或两者任一正在执行时，点击语音聊天按钮都会先释放主窗口音频占用，再唤起 `voice-assistant`。

## 验证结果

在临时构建目录中完成编译验证：

```bash
qmake /home/kylin/Dev/kylin-aiassistant/voice-assistant/voice-assistant.pro
make -j4
```

结果：`voice-assistant` 编译并链接成功。

```bash
qmake /home/kylin/Dev/kylin-aiassistant/kylin-aiassistant/kylin-aiassistant.pro
make -j4
```

结果：`kylin-aiassistant` 编译并链接成功。

构建过程中存在仓库既有 warning，本次改动未引入编译错误。
