AI 应用导入向导中新增 ChatGPT 数据导出 归档源#1373
Open
Liyu199808 wants to merge 4 commits into
Open
Conversation
Add ChatGPT data export as a new archive source in the external agent-session import wizard, mirroring the local-first Claude export flow. The daemon parses conversations.json, linearizes the branch tree by following current_node back to root, preserves titles, timestamps, roles, text and asset placeholders, and generates stable session/message IDs so re-import stays idempotent. Archive kind is threaded explicitly through the OpenAPI contract (archiveKind: claude | chatgpt) instead of routing ChatGPT through a fake Claude/Codex provider identity. ChatGPT imports are non-resumable and never leak local archive paths in user-visible errors. Reuses Claude's archive size, entry-count and decompressed-size limits and JSON-stream/ZIP validators. Adds en/zh-CN i18n copy that is branded per archive kind across the whole wizard (title, scan/scan-failed, empty, search, selection and result summary), and daemon, API and desktop tests covering parsing, branching, idempotency, hostile archives and end-to-end import. Closes tutti-os#1270 Signed-off-by: liyu <Admin@MacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
…hives ChatGPT archive imports intentionally redact the local archive path and emit an empty sourcePath, but the ExternalAgentImportSession schema marked sourcePath as required with minLength:1, so the daemon returned data that violated its own published contract. Update tuttid.v1.yaml to make sourcePath optional and nullable (mirroring the existing ExternalAgentImportError.sourcePath), regenerate the Go/TS clients, and only populate sourcePath when a non-empty path is present. Claude and Codex imports still surface their real path; archive-only imports now omit the field instead of sending an empty string. Signed-off-by: liyu <Admin@MacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
OpenAI's current data export nests Conversations__*-chatgpt-*.zip with sharded conversations-NNN.json files instead of a root conversations.json. Discover and stream those shards while keeping legacy root conversations.json support. Stop keying ProviderSessionID on the current_node leaf id, which changed on every linear message append and created duplicate imported sessions. Use fork-decision hashing (matching the Claude export importer) so linear conversation growth appends to the same session while regenerated branches remain separate. Signed-off-by: liyu <Admin@MacBook-Pro.local>
Signed-off-by: liyu <Admin@MacBook-Pro.local>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题背景
Tutti 已支持导入 Codex、Claude Code 本地记录与 Claude 数据导出 ZIP,但无法
导入用户在 ChatGPT 中的历史对话。手动复制会丢失标题、时间戳、消息结构,且
不适用于大体量归档。(Closes #1270)
解决方式
在现有「从 AI 应用导入」向导中新增 ChatGPT 数据导出 归档源,复用 Claude
导出的本地优先交互(选 ZIP → 本地扫描 → 检索/选择 → 导入)。
tuttid.v1.yaml新增archiveKind枚举(claude | chatgpt),显式区分归档类型,而非把 ChatGPT 伪装成 Claude/Codex provider;已重新生成
Go/TS 客户端。
external_import_chatgpt_export*.go):解析 conversations.json,以
current_node为叶子回溯 root 再反转得到确定性线性会话;保留标题、创建/更新时间、user/assistant 角色、文本与图片占位;用源 UUID/节点 ID 派生
稳定的 session/message ID 保证幂等。
仅在 GUI 目录补充展示标签,并修复导入选择过滤会误删归档源 provider 的问题。
ChatGPT 错误信息不回传本地绝对路径。
且归档态下的标题、扫描/失败提示、空态、搜索框、结果汇总等文案均按 archiveKind
切换为 ChatGPT 品牌(不再复用 Claude 文案)。
验证结果
go test ./services/tuttid/service/agent/ -run ChatGPT:9/9 PASS(解析、分支确定性、幂等、缺失/非数组/重复/超大恶意归档、路径不泄漏、e2e 持久化)
go test ./services/tuttid/api/:PASS(archiveKind 转发 + 非法值 400)check:api-generated/check:changed:PASS取舍与未完成事项
. 验收标准 → 测试映射与通过情况
issues的验收标准逐条对照(全部 ChatGPT 相关测试已通过):TestImportChatGPTExportArchivePersistsSessionsWithoutLeakingPath(直接读原始 ZIP)ExternalAgentSessionImportSourceStep.tsx)+ i18n keyexternalAgentSessionImportWizardModel.test.ts(archiveKind 贯穿 scan/import)TestScanChatGPTExportArchiveNormalizesRolesTextAndAssetPlaceholdersTestScanChatGPTExportArchiveFollowsCurrentNodeAndDropsAbandonedBranchTestScanChatGPTExportArchiveIsIdempotentAndBranchScoped+ e2e 二次导入AgentTargetID空、Resumable=falseRejectsMissingConversationsWithoutLeakingPath、RejectsNonArrayConversations、RejectsDuplicateConversationsEntry、新增TestChatGPTExportConversationStreamRejectsElementBeforeExceedingByteBudgeten.ts/zh-CN.ts新增 key(typecheck 校验键对齐)daemon_agent_sessions_external_import_test.go(forward + 非法 archiveKind 400)+ desktop 模型测试补充说明:
SourcePath置空,错误信息不含本地绝对路径,已由...WithoutLeakingPath系列测试断言。Claude 现状保留(本次按你的要求不改)。