generated from kevisual/vite-react-template
Auto commit: 2026-03-19 01:46
This commit is contained in:
@@ -49,7 +49,7 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
||||
path: '',
|
||||
license: '',
|
||||
description: '',
|
||||
visibility: 'Public'
|
||||
visibility: 'public'
|
||||
})
|
||||
}
|
||||
}, [open, reset])
|
||||
@@ -104,16 +104,16 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
||||
<Controller
|
||||
name="visibility"
|
||||
control={control}
|
||||
defaultValue="Public"
|
||||
defaultValue="public"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select onValueChange={onChange} value={value}>
|
||||
<SelectTrigger id="visibility">
|
||||
<SelectValue placeholder="选择可见性" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Public">公开 (public)</SelectItem>
|
||||
<SelectItem value="Private">私有 (private)</SelectItem>
|
||||
<SelectItem value="Protected">保护 (protected)</SelectItem>
|
||||
<SelectItem value="public">公开 (public)</SelectItem>
|
||||
<SelectItem value="private">私有 (private)</SelectItem>
|
||||
<SelectItem value="protected">保护 (protected)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
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 { useShallow } from 'zustand/shallow'
|
||||
import { get, set } from 'idb-keyval'
|
||||
import { gitea } from '@/agents/app';
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const SYNC_REPO_STORAGE_KEY = 'sync-repo-mapping'
|
||||
|
||||
export function SyncRepoDialog() {
|
||||
const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore(useShallow((state) => ({
|
||||
syncDialogOpen: state.syncDialogOpen,
|
||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
||||
selectedSyncRepo: state.selectedSyncRepo,
|
||||
buildSync: state.buildSync,
|
||||
})))
|
||||
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)
|
||||
}
|
||||
const onCreateRepo = async () => {
|
||||
if (!toRepo.trim()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await gitea.repo.createRepo({ name: toRepo })
|
||||
if (res.code !== 200 && res.code !== 409) {
|
||||
// 409 表示仓库已存在,可以继续同步
|
||||
throw new Error(`${res.message}`)
|
||||
}
|
||||
if (res.code === 200) {
|
||||
toast.success('仓库创建成功,正在同步...')
|
||||
} else {
|
||||
toast.warning('仓库已存在,正在同步...')
|
||||
}
|
||||
handleSync()
|
||||
} catch (error) {
|
||||
console.error('创建仓库失败:', error)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Dialog open={syncDialogOpen} onOpenChange={setSyncDialogOpen}>
|
||||
<DialogContent className="sm:max-w-125">
|
||||
<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={onCreateRepo} disabled={!toRepo.trim()}>
|
||||
先创建仓库再同步
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSync}
|
||||
disabled={!toRepo.trim()}
|
||||
>
|
||||
开始同步
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { RepoCard } from './components/RepoCard'
|
||||
import { EditRepoDialog } from './modules/EditRepoDialog'
|
||||
import { CreateRepoDialog } from './modules/CreateRepoDialog'
|
||||
import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog'
|
||||
import { SyncRepoDialog } from './modules/SyncRepoDialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ExternalLinkIcon, Plus, RefreshCw, Search, Settings } from 'lucide-react'
|
||||
@@ -176,7 +175,6 @@ export const CommonRepoDialog = () => {
|
||||
onOpenChange={setShowCreateDialog}
|
||||
/>
|
||||
<WorkspaceDetailDialog />
|
||||
<SyncRepoDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,28 +57,11 @@ export const createDevConfig = (params: { repo?: string, event?: string, branch?
|
||||
return `##### 配置开始,保留注释 #####
|
||||
.common_env: &common_env
|
||||
env:
|
||||
# 使用环境变量管理密钥,推荐使用密钥仓库管理密钥, 详情见 readme.md
|
||||
# 使用仓库密钥时,注释
|
||||
## 可选 API-Key 配置(按需取消注释)
|
||||
# MINIMAX_API_KEY: '' # Minimax 模型
|
||||
# ZHIPU_API_KEY: '' # 智谱 AI
|
||||
# BAILIAN_CODE_API_KEY: '' # 阿里云百炼
|
||||
# VOLCENGINE_API_KEY: '' # 火山引擎
|
||||
# CNB_API_KEY: '' # CNB API
|
||||
# CNB_COOKIE: '' # 可选配置,用cnb.cool的cookie
|
||||
|
||||
# 可选应用配置
|
||||
# FEISHU_APP_ID: '' # 飞书应用 ID
|
||||
# FEISHU_APP_SECRET: '' # 飞书应用密钥
|
||||
|
||||
USERNAME: root
|
||||
ASSISTANT_CONFIG_DIR: /workspace/kevisual # ASSISTANT_CONFIG_DIR 环境变量指定了配置文件所在的目录
|
||||
# CNB_KEVISUAL_ORG: kevisual # 私密仓库使用环境配置(默认即可,默认为当前用户组CNB_GROUP_SLUG)
|
||||
# CNB_KEVISUAL_APP: assistant-app # 可选配置(默认即可)
|
||||
# CNB_OPENCLAW: openclaw # 仓库名(默认即可)
|
||||
# CNB_OPENWEBUI: open-webui # 仓库名(默认即可)
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||
- https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/.env
|
||||
# - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh.yml
|
||||
# - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh-config.yml
|
||||
|
||||
##### 配置结束 #####
|
||||
|
||||
@@ -89,19 +72,29 @@ ${branch}:
|
||||
services:
|
||||
- vscode
|
||||
- docker
|
||||
runner:
|
||||
cpus: 16
|
||||
#tags: cnb:arch:amd64:gpu
|
||||
imports: !reference [.common_env, imports]
|
||||
env: !reference [.common_env, env]
|
||||
runner:
|
||||
cpus: $RUN_CPU
|
||||
#tags: cnb:arch:amd64:gpu
|
||||
stages:
|
||||
- name: 安装dev-cnb的仓库代码模块
|
||||
script: |
|
||||
cd /workspace && find . -mindepth 1 -delete
|
||||
git init
|
||||
git remote add origin https://cnb.cool/kevisual/dev-cnb
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
- name: 启动nginx
|
||||
script: nginx
|
||||
- name: 启动搜索服务
|
||||
script: zsh -i -c 'bun src/cli.ts init start-meilisearch'
|
||||
- name: 初始化开发机
|
||||
script: zsh /workspace/scripts/init.sh
|
||||
script: zsh -i -c 'bun run start'
|
||||
- name: 启动当前工作区
|
||||
script: zsh -i -c 'cloud cnb keep-alive-current-workspace'
|
||||
# endStages:
|
||||
# - name: 结束阶段
|
||||
# script: bun /workspace/scripts/end.ts
|
||||
|
||||
# script: zsh -i -c 'bun run end'
|
||||
`
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { query } from '@/modules/query';
|
||||
import { toast } from 'sonner';
|
||||
import { queryApi as cnbApi } from '@/modules/cnb-api'
|
||||
import { WorkspaceInfo } from '@kevisual/cnb'
|
||||
import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build';
|
||||
import { createCommitBlankConfig, createDevConfig } from './build';
|
||||
import { useLayoutStore } from '@/pages/auth/store';
|
||||
import { Query } from '@kevisual/query';
|
||||
interface DisplayModule {
|
||||
@@ -94,7 +94,6 @@ type State = {
|
||||
setSyncDialogOpen: (open: boolean) => void;
|
||||
selectedSyncRepo: Data | null;
|
||||
setSelectedSyncRepo: (repo: Data | null) => void;
|
||||
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
|
||||
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
||||
getItem: (repo: string) => Promise<any>;
|
||||
buildConfig: BuildConfig | null;
|
||||
@@ -525,28 +524,6 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
}
|
||||
},
|
||||
workspaceLink: {},
|
||||
buildSync: async (data, params) => {
|
||||
const repo = data.path!;
|
||||
const toRepo = params.toRepo;
|
||||
const fromRepo = params.fromRepo;
|
||||
if (!toRepo && !fromRepo) {
|
||||
toast.error('请选择同步的目标仓库或来源仓库')
|
||||
return;
|
||||
}
|
||||
let event = toRepo ? 'api_trigger_sync_to_gitea' : 'api_trigger_sync_from_gitea';
|
||||
const res = await cnbApi.cnb['cloud-build']({
|
||||
repo: toRepo! || fromRepo!,
|
||||
branch: 'main',
|
||||
env: {} as any,
|
||||
event: event,
|
||||
config: createBuildConfig({ repo: toRepo! || fromRepo! }),
|
||||
})
|
||||
if (res.code === 200) {
|
||||
toast.success('同步提交成功')
|
||||
} else {
|
||||
toast.error(res.message || '同步提交失败')
|
||||
}
|
||||
},
|
||||
buildUpdate: async (data) => {
|
||||
const res = await cnbApi.cnb['cloud-build']({
|
||||
repo: data.path!,
|
||||
|
||||
Reference in New Issue
Block a user