[split-upload 2/6] f2fd4fa backup via proxy
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# Handoff · 工具栏 v1.9 落地 · 回家操作手册
|
||||
|
||||
> **生成时间**: 2026-08-05 23:53
|
||||
> **拍板**: 先恢复 git 仓库,再部署
|
||||
> **当前进度**: v1.9 落地版代码完整双备份 ✅ / git 仓库 object store 损坏 ⚠️ / 待恢复后部署
|
||||
|
||||
---
|
||||
|
||||
## 📋 一、背景速览(执行前 30 秒读完)
|
||||
|
||||
今天完成了 v1.9 工具栏落地版的开发与测试:
|
||||
|
||||
- **功能**:圆润拱形工具栏(5 按钮:emoji / 文件 / 坐席 60px / 语音 / 群聊),三区无边框融合
|
||||
- **代码改动文件**:
|
||||
- `src/frontend-h5/src/components/chat/InputBar.vue`(540 行改动)
|
||||
- `src/frontend-h5/src/components/chat/InputBar.test.ts`(新增 25 个 v1.9 测试)
|
||||
- **测试**:vitest 91/91 通过,vite build 528 modules OK
|
||||
- **本地 commit**:87c574c7 (v1.9) + 之前的 66 个 commit
|
||||
- **本地 tag**:`pre-toolbar-v1.9` → 5a77a89a(origin/main HEAD,已固化)
|
||||
|
||||
**意外**:尝试合并 feature 到 main 时触发 git object store 损坏,**87c574c7 的 tree 物理丢失**。修复方案:基于 working tree + 备份重建 v1.9 commit(见下面"阶段 A")。
|
||||
|
||||
---
|
||||
|
||||
## 📂 二、备份位置(重要!)
|
||||
|
||||
| 备份内容 | 路径 |
|
||||
|----------|------|
|
||||
| **v1.9 InputBar.vue** | `D:\资料\00-WorkBuddy\2026-08-05-21-00-39\.workbuddy\v1.9-backup\InputBar.vue`(1523 行,完整 v1.9 内容) |
|
||||
| **v1.9 InputBar.test.ts** | `D:\资料\00-WorkBuddy\2026-08-05-21-00-39\.workbuddy\v1.9-backup\InputBar.test.ts`(完整 v1.9 内容) |
|
||||
| **working tree 原位置** | `D:\资料\03-项目开发\wecom_it_smart_desk\src\frontend-h5\src\components\chat\`(InputBar.vue + .test.ts) |
|
||||
|
||||
**双保险**:workspace 备份 + working tree 双份。如果 working tree 在恢复过程中被破坏,workspace 备份还有。
|
||||
|
||||
---
|
||||
|
||||
## 🏠 三、回家后操作手册(按顺序执行)
|
||||
|
||||
### 阶段 A:恢复 git 仓库(在 Tailscale 可用环境)
|
||||
|
||||
打开 PowerShell(不要用 Git Bash),执行:
|
||||
|
||||
```powershell
|
||||
# A1. 切换到项目目录
|
||||
cd "D:\资料\03-项目开发\wecom_it_smart_desk"
|
||||
|
||||
# A2. 确认 Tailscale 上线(桌面右下角图标显示 active)
|
||||
# 如果 offline,登录 Tailscale
|
||||
|
||||
# A3. 从远程同步 object store(覆盖本地的损坏)
|
||||
git fetch origin --prune
|
||||
|
||||
# A4. 验证 origin/main 健康
|
||||
git rev-parse origin/main
|
||||
git cat-file -p origin/main | Select-String "tree" # 应能读到 tree hash
|
||||
|
||||
# A5. 切到 pre-toolbar-v1.9(健康锚点)
|
||||
git checkout pre-toolbar-v1.9
|
||||
|
||||
# A6. 验证切成功(应该 HEAD 在 5a77a89a)
|
||||
git log --oneline -3
|
||||
|
||||
# A7. 把 v1.9 备份文件复制回 working tree
|
||||
Copy-Item "D:\资料\00-WorkBuddy\2026-08-05-21-00-39\.workbuddy\v1.9-backup\InputBar.vue" `
|
||||
-Destination "src\frontend-h5\src\components\chat\InputBar.vue" -Force
|
||||
Copy-Item "D:\资料\00-WorkBuddy\2026-08-05-21-00-39\.workbuddy\v1.9-backup\InputBar.test.ts" `
|
||||
-Destination "src\frontend-h5\src\components\chat\InputBar.test.ts" -Force
|
||||
|
||||
# A8. 验证 v1.9 特征(应命中 9 次 gem-toolbar/M 18 18/x 84)
|
||||
git diff --stat HEAD
|
||||
git diff HEAD -- src/frontend-h5/src/components/chat/InputBar.vue | Measure-Object
|
||||
```
|
||||
|
||||
### 阶段 B:创建新 v1.9 commit(hash 会变,但内容等价)
|
||||
|
||||
```powershell
|
||||
# B1. 暂存改动
|
||||
git add src/frontend-h5/src/components/chat/InputBar.vue
|
||||
git add src/frontend-h5/src/components/chat/InputBar.test.ts
|
||||
|
||||
# B2. 创建 v1.9 commit(保留完整 commit message)
|
||||
git commit -m @'
|
||||
feat(chat): 工具栏统一设计 v1.9 落地 — 圆润拱形 + 5 按钮 + 三区无边框融合
|
||||
|
||||
[REQ-会话-001] 员工端会话窗口输入区工具栏视觉重构
|
||||
|
||||
改动概览:
|
||||
- 工具栏容器:.glass-toolbar(玻璃胶囊)→ .gem-toolbar(拱形轨道)
|
||||
- 按钮顺序:emoji / 文件 / 语音 / 坐席(4 按钮)
|
||||
→ emoji / 文件 / 坐席(居中 60px) / 语音 / 群聊(5 按钮)
|
||||
- 坐席按钮:44px → 60px(较 40px 工具图标大 50%),新增 .agent-btn.gem 修饰符
|
||||
- 三区融合:消息区 / 工具栏 / 输入区融为连续浅色表面(input-bar 容器透明)
|
||||
- 拱形轨道 SVG:viewBox 0 0 312 84,宽穹顶 x 84..216,顶点 (156, 4) 圆肩水平切线
|
||||
- 可访问性:aria-label / title / focus-visible 蓝环 / 装饰 SVG aria-hidden
|
||||
- 响应式 ≤480px fallback:隐藏拱形 SVG、改胶囊(坐席缩至 52px)
|
||||
- 深色模式骨架:prefers-color-scheme: dark 颜色变量预留
|
||||
|
||||
测试:
|
||||
- InputBar.test.ts 保留 v1.3 历史契约,新增 v1.9 专项测试套(共 91/91 通过)
|
||||
- 覆盖 5 按钮顺序 / 坐席 60px / SVG 路径关键控制点 / 三区融合 / 键盘可达性 / 响应式 fallback
|
||||
|
||||
验收:
|
||||
- vitest: 91/91 通过
|
||||
- vite build: 528 modules transformed, build OK
|
||||
- 6 态坐席入口契约不变(callAgent/cancelQueue/endConversation/reopenConversation)
|
||||
- 群聊按钮:toast 占位(store 暂无 groupChat action),后续接入时替换 handleGroupChat
|
||||
|
||||
ref: 原型-REQ-会话-001-工具栏统一设计v1.9-员工端落地版.html
|
||||
ref: 交付-REQ-会话-001-工具栏统一设计v1.9-开发交付清单.md
|
||||
'@
|
||||
|
||||
# B3. 验证 commit
|
||||
git log --oneline -2
|
||||
git show --stat HEAD
|
||||
```
|
||||
|
||||
### 阶段 C:合并到 main(fast-forward,零冲突)
|
||||
|
||||
```powershell
|
||||
# C1. 切到 main
|
||||
git checkout main
|
||||
|
||||
# C2. 拉取最新 main(fetch 时已同步,但保险再 fetch 一次)
|
||||
git pull --ff-only
|
||||
|
||||
# C3. fast-forward merge
|
||||
git merge --ff-only feature/message-reliability
|
||||
|
||||
# C4. 验证 main 已含 v1.9
|
||||
git log --oneline -5 main
|
||||
```
|
||||
|
||||
### 阶段 D:推送远程 + 打 tag(可选但推荐)
|
||||
|
||||
```powershell
|
||||
# D1. 推 main
|
||||
git push origin main
|
||||
|
||||
# D2. 推 feature 分支(保留历史)
|
||||
git push origin feature/message-reliability
|
||||
|
||||
# D3. 推 tag
|
||||
git push origin pre-toolbar-v1.9
|
||||
|
||||
# D4. 在 NAS Gitea web 上创建 PR(如需走 PR 流程)
|
||||
# 路径:http://ds923plus.tail58d872.ts.net:8418/simon/wecom_it_smart_desk
|
||||
```
|
||||
|
||||
### 阶段 E:构建 dist
|
||||
|
||||
```powershell
|
||||
# E1. 切回 main(如果之前切到 feature)
|
||||
cd "D:\资料\03-项目开发\wecom_it_smart_desk\src\frontend-h5"
|
||||
|
||||
# E2. 构建(用 --outDir 避开 safe-delete 拦截 dist/)
|
||||
npm run build -- --outDir dist-build-staging
|
||||
|
||||
# E3. 验证构建产物
|
||||
ls dist-build-staging/assets/*.js | Measure-Object
|
||||
# 应有约 528 modules transformed
|
||||
```
|
||||
|
||||
### 阶段 F:部署到预生产服务器(需要预生产服务器信息)
|
||||
|
||||
```powershell
|
||||
# F1. 加载 jumpserver-V2 skill
|
||||
# (skill 路径:C:\Users\simon\.workbuddy\skills\jumpserver-V2\)
|
||||
|
||||
# F2. 首次登录(仅此一步需要浏览器)
|
||||
C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe `
|
||||
C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py login
|
||||
|
||||
# F3. 验证 cache 有效
|
||||
C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe `
|
||||
C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py status
|
||||
|
||||
# F4. ⚠️ 预生产服务器信息需确认(jumpserver-V2 skill 当前只有生产服务器配置):
|
||||
# - 资产名:
|
||||
# - IP:
|
||||
# - 系统用户名(完整名,含"环境"二字):
|
||||
# 请先告知我具体信息,我帮你扩展 skill 配置(jumpserver-V2 的 v2_ops.py 已整合,加资产参数即可)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 四、风险与回滚
|
||||
|
||||
### git 回滚(推荐)
|
||||
|
||||
```powershell
|
||||
# 一键回滚 v1.9(生成反向 commit,保留历史)
|
||||
git revert <v1.9_commit_hash>
|
||||
|
||||
# 或回到 tag(破坏性,但一步到位)
|
||||
git reset --hard pre-toolbar-v1.9
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
### 服务器回滚
|
||||
|
||||
预生产服务器回滚策略在拿到服务器信息后补充。
|
||||
|
||||
---
|
||||
|
||||
## 📞 五、需要 duckula(Duckula = 我)配合的事
|
||||
|
||||
回到家后告诉我:
|
||||
1. ✅ Tailscale 已上线
|
||||
2. ✅ 阶段 A~D 已完成(git 恢复 + push)
|
||||
3. 预生产服务器的资产名 / IP / 系统用户名(让我扩展 jumpserver-V2 skill)
|
||||
|
||||
我会接着帮你:
|
||||
- 扩展 jumpserver-V2 skill 支持预生产服务器
|
||||
- 构建 + 上传 dist 到预生产
|
||||
- 健康检查 + 视觉验证(通过 agent-browser 自动化测试)
|
||||
|
||||
---
|
||||
|
||||
## 📝 六、相关文档(已生成在 docs/)
|
||||
|
||||
| 文档 | 路径 |
|
||||
|------|------|
|
||||
| 设计交付清单 | `D:\资料\03-项目开发\wecom_it_smart_desk\docs\01-产品文档\02-会话管理\交付-REQ-会话-001-工具栏统一设计v1.9-开发交付清单.md` |
|
||||
| v1.9 落地版原型 | `D:\资料\03-项目开发\wecom_it_smart_desk\docs\01-产品文档\02-会话管理\原型-REQ-会话-001-工具栏统一设计v1.9-员工端落地版.html` |
|
||||
| v1.9 融合版原型(备用) | `D:\资料\03-项目开发\wecom_it_smart_desk\docs\01-产品文档\02-会话管理\原型-REQ-会话-001-工具栏统一设计v1.9-融合无边框圆润顶端.html` |
|
||||
| 历史版本 v1.4-v1.8 | `D:\资料\03-项目开发\wecom_it_smart_desk\docs\01-产品文档\02-会话管理\`(未跟踪,建议归档到 archives/) |
|
||||
|
||||
---
|
||||
|
||||
**拍板人**: Simon
|
||||
**主理人**: Duckula(WorkBuddy · 团队助手)
|
||||
**日期**: 2026-08-05
|
||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""打包后端修复文件"""
|
||||
import tarfile
|
||||
import os
|
||||
|
||||
base_dir = r"D:\资料\03-项目开发\wecom_it_smart_desk"
|
||||
# 打包时直接用 tasks/h5_ai_task.py 作为文件名
|
||||
files_to_pack = [r"backend\app\tasks\h5_ai_task.py"]
|
||||
tar_path = r"C:\tmp\h5_ai_task_fix.tar.gz"
|
||||
|
||||
with tarfile.open(tar_path, 'w:gz') as tar:
|
||||
for f in files_to_pack:
|
||||
full_path = os.path.join(base_dir, f)
|
||||
# 使用 tasks/h5_ai_task.py 作为arcname,这样解压后会直接覆盖
|
||||
tar.add(full_path, arcname="tasks/h5_ai_task.py")
|
||||
|
||||
print(f'打包完成: {tar_path}')
|
||||
print(f'大小: {os.path.getsize(tar_path)} bytes')
|
||||
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
大文件上传脚本 - 通过 JumpServer plink PTY 使用 base64 通道上传
|
||||
使用 4000 字符的大块,比 jms_ops.py 的 500 字符快 8 倍
|
||||
"""
|
||||
import sys, os, base64, hashlib, time
|
||||
|
||||
# 导入 jms_ops 模块
|
||||
SKILL_DIR = r"C:\Users\simon\.workbuddy\skills\jumpserver-ops\scripts"
|
||||
sys.path.insert(0, SKILL_DIR)
|
||||
import jms_ops
|
||||
|
||||
def upload_large_file(local_path, remote_path, chunk_size=4000):
|
||||
"""通过 plink PTY 上传大文件,使用大块 base64 编码"""
|
||||
local_data = open(local_path, 'rb').read()
|
||||
local_md5 = hashlib.md5(local_data).hexdigest()
|
||||
b64_data = base64.b64encode(local_data).decode('ascii')
|
||||
|
||||
total_chunks = (len(b64_data) + chunk_size - 1) // chunk_size
|
||||
print(f"📤 上传: {local_path} → {remote_path}")
|
||||
print(f" 原始: {len(local_data)} bytes, base64: {len(b64_data)} chars")
|
||||
print(f" 分 {total_chunks} 块发送 (每块 {chunk_size} chars)")
|
||||
|
||||
# 获取 token + 启动会话
|
||||
tokens = jms_ops.get_connection_tokens(1)
|
||||
if not tokens:
|
||||
print("❌ 获取 token 失败")
|
||||
return False
|
||||
|
||||
token_id, token_secret = tokens[0]
|
||||
session = jms_ops.PlinkSession(f"JMS-{token_id}", token_secret)
|
||||
if not session.connect():
|
||||
print("❌ 会话启动失败")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 1. 清空目标文件
|
||||
session.run_command(f'> {remote_path}', timeout=5)
|
||||
|
||||
# 2. 逐块追加 (大块)
|
||||
start_time = time.time()
|
||||
for i in range(0, len(b64_data), chunk_size):
|
||||
chunk = b64_data[i:i+chunk_size]
|
||||
chunk_num = i // chunk_size + 1
|
||||
cmd = f"echo '{chunk}' | base64 -d >> {remote_path}"
|
||||
r = session.run_command(cmd, timeout=15)
|
||||
if not r["success"]:
|
||||
print(f" ❌ 块 {chunk_num}/{total_chunks} 发送失败")
|
||||
return False
|
||||
if chunk_num % 50 == 0 or chunk_num == total_chunks:
|
||||
elapsed = time.time() - start_time
|
||||
pct = chunk_num / total_chunks * 100
|
||||
print(f" 📦 已发送 {chunk_num}/{total_chunks} 块 ({pct:.0f}%) - {elapsed:.1f}s")
|
||||
|
||||
# 3. 验证大小
|
||||
r = session.run_command(f'wc -c < {remote_path}', timeout=5)
|
||||
if r["success"]:
|
||||
remote_size = int(r["output"].strip()) if r["output"].strip().isdigit() else -1
|
||||
if remote_size == len(local_data):
|
||||
elapsed = time.time() - start_time
|
||||
print(f" ✅ 上传成功! 大小匹配 ({remote_size} bytes), 耗时 {elapsed:.1f}s")
|
||||
# 4. MD5 验证
|
||||
r2 = session.run_command(f'md5sum {remote_path}', timeout=5)
|
||||
if r2["success"]:
|
||||
remote_md5 = r2["output"].split()[0]
|
||||
if remote_md5 == local_md5:
|
||||
print(f" ✅ MD5 匹配! 文件完整")
|
||||
else:
|
||||
print(f" ⚠️ MD5 不匹配 (本地 {local_md5[:12]}, 远程 {remote_md5[:12]})")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ 大小不匹配 (本地 {len(local_data)}, 远程 {remote_size})")
|
||||
return False
|
||||
else:
|
||||
print(" ⚠️ 无法验证远程文件大小")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
local = r"D:\资料\03-项目开发\wecom_it_smart_desk\tmp-agent-dist.tar.gz"
|
||||
remote = "/tmp/agent-dist.tar.gz"
|
||||
success = upload_large_file(local, remote, chunk_size=4000)
|
||||
if success:
|
||||
print("\n✅ 上传完成,可以在服务器上解压了")
|
||||
else:
|
||||
print("\n❌ 上传失败")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user