☰
Operit 粘贴超长文本自动转文件:聊天输入框大文本保护机制与源码级实现解析
2026/9/28 8:36:58 网站建设 项目流程
  • AI Agent
  • 人工智能
  • 大模型
  • AI 应用
  • 工具调用
  • 本地部署
  • MCP Clients
  • Agent 记忆

【免费下载链接】Operit

The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent

项目地址:https://gitcode.com/gh_mirrors/op/Operit
点击查看免费下载

Operit 的聊天输入框默认会原样保留用户粘贴的全部文本,当一次性粘贴数千甚至数万字符时,大量文本会让编辑器卡顿、界面响应迟缓。为缓解这一问题,Operit 引入「超长粘贴文本转文件」能力:当纯文本粘贴长度超过用户设定的阈值时,自动将文本写入 UTF-8 编码的.txt临时附件并加入待发送列表,同时清空该次粘贴产生的输入内容。本文基于 docs/TODO/paste-long-text-as-file 系列设计文档,结合仓库源码与单元测试,讲解该功能的设计边界、配置项、粘贴识别算法、附件落盘流程与验证方式,帮助读者完整理解这一输入保护机制的实现原理。

功能定位与设计边界

本功能的初衷非常明确:只处理聊天输入框的粘贴增量,不改变其他任何输入路径。根据设计文档的约束,本次改动的作用域包括:

  • 聊天输入控件(Compose 文本输入框)的粘贴事件处理;
  • 持久化设置(启用开关 + 字符阈值);
  • 临时附件创建(写入清理目录的.txt文件);
  • 中英文资源(设置项名称、说明与转换提示)。

同时明确不涉及以下内容:消息协议格式、已有附件处理流程的替换、手动选择附件的路径。也就是说,手动附件、普通输入(键盘打字)、以及未达到阈值的粘贴操作,都维持原有行为不变。这一设计边界在源码中也得到印证:attachPastedText是 AttachmentDelegate.kt 中独立新增的方法,与既有的 URI 附件解析、MIME 类型解析路径完全解耦。

设置项与持久化:开关 + 阈值

新增偏好键

功能对应的设置持久化实现在 UserPreferencesManager.kt 中,新增了两个 DataStore 偏好键:

// 功能总开关 private val CONVERT_LONG_PASTED_TEXT_TO_FILE = booleanPreferencesKey("convert_long_pasted_text_to_file") // 字符阈值 private val LONG_PASTED_TEXT_FILE_THRESHOLD = intPreferencesKey("long_pasted_text_file_threshold")

默认值

从源码可以看出两个默认值:

  • 功能开关默认开启:preferences[CONVERT_LONG_PASTED_TEXT_TO_FILE] ?: true(见convertLongPastedTextToFileFlow 实现);
  • 阈值默认3000 字符:由常量DEFAULT_LONG_PASTED_TEXT_FILE_THRESHOLD = 3000定义(见 UserPreferencesManager.kt),与设计文档01-settings-and-paste-conversion.md中“默认阈值为 3000”的描述一致。

对应的 Flow 与保存方法对外暴露为:

val convertLongPastedTextToFile: Flow<Boolean> val longPastedTextFileThreshold: Flow<Int> suspend fun saveConvertLongPastedTextToFile(enabled: Boolean) suspend fun saveLongPastedTextFileThreshold(threshold: Int)

设置界面与可调范围

设置入口位于“显示设置”页面(GlobalDisplaySettingsScreen.kt),包含一个开关项和一个阈值编辑项:

  • 开关项:标题「超长粘贴文本转文件」,说明「粘贴超过设定字符数的文本时,自动作为文本附件添加」;
  • 阈值项(仅在开关开启时显示):标题「转换阈值」,使用EditableNumberSetting组件,可编辑范围为1000~100000 字符(valueRange = 1_000f..100_000f),单位显示为「字符」。

值得注意:界面允许的可调范围下限(1000)高于默认值(3000),上限为 100000,说明该设置面向“大文本粘贴”场景,普通小段粘贴不会触发转换。

粘贴识别算法:从输入增量中提取粘贴文本

超长文本转换的核心前提是准确识别一次粘贴操作产生了哪些新增字符。如果简单地用“文本总长度超过阈值”来判断,用户手动输入的超长文本也会被误转换。因此 PastedTextAttachment.kt 中实现了专门的内置函数extractClipboardPastedText:

internal fun extractClipboardPastedText( previous: TextFieldValue, proposed: TextFieldValue, clipboardText: String, ): String?

该函数的识别策略分为四步:

  1. 剪贴板判空:clipboardText.isEmpty()时直接返回null,没有复制内容就不存在“粘贴”语义;
  2. 区间定位:取previous(变化前输入框状态)中选区范围的insertionStart/insertionEnd,将旧文本切分为prefix(前缀)与suffix(后缀);
  3. 增量校验:只有proposed(变化后输入框状态)文本同时以prefix开头、以suffix结尾,才认为新增区间确实是插入到原光标/选区位置的;然后通过proposed.text.substring(prefix.length, pastedEnd)提取新增文本;
  4. 剪贴板比对:将提取出的文本与系统剪贴板内容做换行归一化后比对——normalizeLineEndings会把\r\n与\r统一归一化为\n,只有完全一致才认定这是一次真实的粘贴,否则返回null。

这一步非常关键:它同时排除了“用户手动打字输入的长文本”和“粘贴内容与剪贴板不一致”的干扰,保证了只有真正来自剪贴板的粘贴增量才会被转成文件。

附件创建与落盘:text/plain 临时文件

识别出超长粘贴文本后,由 AttachmentDelegate.kt 的attachPastedText完成附件创建。其流程为:

  1. 在 IO 调度器(Dispatchers.IO)中执行;
  2. 获取OperitPaths.cleanOnExitDir()(即Download/Operit/cleanOnExit目录,定义见 OperitPaths.kt),目录不存在则创建,并写入.nomedia标记文件,避免生成的文本文件进入系统媒体库扫描;
  3. 通过File.createTempFile("pasted_text_", ".txt", externalDir)创建随机命名的临时文件,文件名形如pasted_text_XXXX.txt;
  4. 以UTF-8 编码写入文本内容:textFile.writeText(text, Charsets.UTF_8),与设计文档“创建 UTF-8 文本附件”的约定一致;
  5. 构造AttachmentInfo(包含绝对路径、文件名、mimeType = "text/plain"、文件字节大小),调用appendAttachment将其加入待发送附件列表;
  6. 发送 Toast 提示chat_pasted_text_attached(中文为「已将粘贴文本作为附件添加:%1$s」);
  7. 失败时记录AppLogger错误日志并发送attachment_add_failed失败提示,返回false供上层回退处理。

从createTempFileFromUri的注释可知,cleanOnExit目录是外部存储Download/Operit下的清理目录而非应用缓存目录,其定位是“退出时清理的临时工作区”,适合承载这类生命周期短暂的粘贴文本附件。

输入框集成:保留草稿直到附件创建成功

UI 侧的接入点位于聊天主界面 AIChatScreen.kt 的handleUserMessageChange,它同时监听设置流与阈值:

val convertLongPastedTextToFile by userPreferences.convertLongPastedTextToFile.collectAsState(initial = true) val longPastedTextFileThreshold by userPreferences.longPastedTextFileThreshold.collectAsState( initial = UserPreferencesManager.DEFAULT_LONG_PASTED_TEXT_FILE_THRESHOLD )

每次输入框内容变化时:

  1. 读取系统剪贴板首项文本(clipboardManager.primaryClip);
  2. 若功能开启且剪贴板非空,调用extractClipboardPastedText提取粘贴增量;
  3. 若提取成功,且pastedText.length > longPastedTextFileThreshold(严格大于阈值才触发),且当前存在有效会话(currentChatId非空),则异步调用viewModel.attachPastedText(pastedText);
  4. 关键细节:触发转换时不立即提交输入变化——注释明确说明“Keep the draft unchanged until the attachment has been created, so I/O errors do not lose text”,即先创建附件、成功后才丢弃输入框内容;若附件创建失败(返回false),则回退执行commitUserMessageChange(value),让文本保留在输入框中,避免 I/O 错误导致用户文本丢失;
  5. 未触发转换的普通输入走原有commitUserMessageChange路径,维持既有行为。

ViewModel 侧则通过 ChatViewModel.kt 的attachPastedText简单委托给attachmentDelegate.attachPastedText(text),保持分层清晰。

最终用户可见的效果正如设计文档「预期」所述:粘贴超过阈值的文本后,输入框不会承载该文本,附件预览中会出现一个可发送的.txt文件,点击发送即可将整段文本作为附件发给 AI。

本地化资源

设置与转换提示的文案全部走 Android 资源系统,便于多语言扩展。中文默认资源位于 app/src/main/res/values/strings.xml:

<string name="chat_pasted_text_attached">已将粘贴文本作为附件添加:%1$s</string> <string name="long_pasted_text_to_file">超长粘贴文本转文件</string> <string name="long_pasted_text_to_file_desc">粘贴超过设定字符数的文本时,自动作为文本附件添加</string> <string name="long_pasted_text_threshold">转换阈值</string> <string name="long_pasted_text_threshold_desc">粘贴文本超过此字符数时转为文件</string> <string name="long_pasted_text_threshold_unit">字符</string>

仓库中同时维护了英文(values-en)、西班牙语(values-es)、马来语(values-ms)、罗马尼亚语(values-ro)等多语言资源目录,新增字符串可同步补充对应语言版本。

单元测试与静态核查

设计文档02-localization-and-verification.md明确要求“添加粘贴文本识别的单元测试”,仓库中对应的测试类为 PastedTextAttachmentTest.kt,共 4 个用例,覆盖了识别算法的核心分支:

测试方法验证场景预期结果
extractClipboardPastedText_returnsTextInsertedAtCursor在光标处插入文本(Before after→Before inserted after,剪贴板为inserted)返回inserted
extractClipboardPastedText_returnsTextReplacingSelection粘贴替换选中文本(first second选中前 5 个字符,替换为replacement)返回replacement
extractClipboardPastedText_acceptsNormalizedClipboardLineEndings剪贴板为first\r\nsecond(Windows 换行),输入框为first\nsecond归一化后返回first\nsecond
extractClipboardPastedText_ignoresTextThatDoesNotMatchClipboard输入增量与剪贴板内容不一致返回null

前两个用例验证了“光标插入”与“选区替换”两种常见粘贴形态;第三个用例验证了换行归一化(\r\n→\n)容错;第四个用例则验证了非粘贴增量会被正确排除。结合静态检查(设置文案引用、调用点核查),共同构成对该功能的回归保障。

使用建议与注意事项

  • 阈值语义:只有粘贴文本长度严格大于设定阈值(默认 3000 字符)才触发转换,等于阈值时不会触发;
  • 触发条件:需同时满足功能开启、当前有剪贴板内容、提取增量与剪贴板完全一致(换行归一化后)、当前存在有效会话;
  • 不触发的情况:手动键盘输入的长文本、未达到阈值的粘贴、无会话时的粘贴,均维持原有“文本直接进输入框”的行为;
  • 文本安全:附件创建失败时草稿内容会保留在输入框,不会因 I/O 错误丢失文本;成功转换后输入框中的粘贴内容会被清空,文本以text/plain附件形式进入待发送列表;
  • 文件位置:生成的.txt临时文件位于外部存储Download/Operit/cleanOnExit目录,并带有.nomedia标记,属于退出即清理的临时工作区。

整体而言,该功能是 Operit 针对“超大文本粘贴拖垮输入编辑器”这一真实使用痛点给出的轻量方案:不改变消息协议、不替换既有附件流程,仅通过一次输入增量比对、一次剪贴板校验与一次临时文件落盘,就将大文本从输入框安全地迁移到附件列表,兼顾了界面流畅性与文本不丢失的可靠性。

  • AI Agent
  • 人工智能
  • 大模型
  • AI 应用
  • 工具调用
  • 本地部署
  • MCP Clients
  • Agent 记忆

【免费下载链接】Operit

The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent

项目地址:https://gitcode.com/gh_mirrors/op/Operit
点击查看免费下载

相关推荐

上一篇:Axure RP中文汉化包:3分钟搞定专业界面本地化
下一篇:免费小说下载神器:轻松保存200+网站小说为TXT/EPUB格式

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询