generated from kevisual/vite-react-template
feat: 添加仓库管理页面和 AI 功能,优化路由和导航
- 新增仓库列表页面,支持查看和管理 CNB 仓库 - 添加 AI 代理系统和状态管理 - 新增 tags-input、popover、textarea、tooltip 等 UI 组件 - 更新依赖:@kevisual/cnb 升级至 0.0.22,添加 idb-keyval - 改进路由守卫:未配置 API Key 时自动跳转配置页 - 优化 Dialog 遮罩层样式和整体布局 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
85
src/app/repo/modules/SyncRepoDialog.tsx
Normal file
85
src/app/repo/modules/SyncRepoDialog.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useRepoStore } from '../store'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { get, set } from 'idb-keyval'
|
||||
|
||||
const SYNC_REPO_STORAGE_KEY = 'sync-repo-mapping'
|
||||
|
||||
export function SyncRepoDialog() {
|
||||
const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore()
|
||||
const [toRepo, setToRepo] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const loadSavedMapping = async () => {
|
||||
if (syncDialogOpen && selectedSyncRepo) {
|
||||
const currentPath = selectedSyncRepo.path || ''
|
||||
// 从 idb-keyval 获取存储的映射
|
||||
const mapping = await get<Record<string, string>>(SYNC_REPO_STORAGE_KEY)
|
||||
// 如果有存储的值,使用存储的值,否则使用当前仓库路径
|
||||
setToRepo(mapping?.[currentPath] || currentPath)
|
||||
}
|
||||
}
|
||||
loadSavedMapping()
|
||||
}, [syncDialogOpen, selectedSyncRepo])
|
||||
|
||||
const handleSync = async () => {
|
||||
if (!selectedSyncRepo || !toRepo.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存映射到 idb-keyval
|
||||
const currentPath = selectedSyncRepo.path || ''
|
||||
const mapping = await get<Record<string, string>>(SYNC_REPO_STORAGE_KEY) || {}
|
||||
mapping[currentPath] = toRepo
|
||||
await set(SYNC_REPO_STORAGE_KEY, mapping)
|
||||
|
||||
await buildSync(selectedSyncRepo, { toRepo })
|
||||
setSyncDialogOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={syncDialogOpen} onOpenChange={setSyncDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>同步仓库到 Gitea</DialogTitle>
|
||||
<DialogDescription>
|
||||
将仓库 <span className="font-semibold text-neutral-900">{selectedSyncRepo?.path}</span> 同步到目标仓库
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="toRepo">目标仓库路径</Label>
|
||||
<Input
|
||||
id="toRepo"
|
||||
placeholder="例如: kevisual/my-repo"
|
||||
value={toRepo}
|
||||
onChange={(e) => setToRepo(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-neutral-500">
|
||||
格式: owner/repo-name
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setSyncDialogOpen(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSync}
|
||||
disabled={!toRepo.trim()}
|
||||
>
|
||||
开始同步
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user