feat(manager): align with product-package docs §10/§11
CORS unblock — add /api/heicode-auth/*proxyPath backend proxy to HEICODE_AUTH_BASE_URL. Frontend defaults to same-origin path so the browser never hits APIM directly. Sidebar — replace backend jargon (Git sources / Deployments / Events / Wallet / Available models / Profile) with the user-facing labels docs §10 mandates: 总览 / 准备清单 / 任务总览 / 审计 / 模型与余额 / 客户端 / 账号安全. /sk-sources rewritten as 4-card preparation wizard with progress meter; full Git form moves into a 高级补充 sheet. Drops JSON editor, permission manifest, snapshots and resource-grant pills. /deployments simplified to 任务总览: objective + status + last update. Drops risk / budget / scope / secret_ref pills and the RunDetailPanel; manifest details only in audit/advanced views. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HeicodeAuthProxy transparently forwards browser calls to the upstream Heicode
|
||||
// identity service (APIM). The frontend cannot call APIM directly because that
|
||||
// host does not include code.xinghanlab.com in its CORS allow-list, so we
|
||||
// terminate the request same-origin and re-emit it server-side.
|
||||
//
|
||||
// Mounted at /api/heicode-auth/*proxyPath. The trailing path (everything after
|
||||
// /api/heicode-auth/) is appended verbatim to HEICODE_AUTH_BASE_URL. Request
|
||||
// method, query string, body, and the Authorization header are preserved.
|
||||
func HeicodeAuthProxy(c *gin.Context) {
|
||||
tail := strings.TrimPrefix(c.Param("proxyPath"), "/")
|
||||
if tail == "" {
|
||||
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "missing upstream path"})
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := defaultHeicodeAuthBaseURL()
|
||||
target := baseURL + "/" + tail
|
||||
if raw := c.Request.URL.RawQuery; raw != "" {
|
||||
target += "?" + raw
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if c.Request.Body != nil {
|
||||
body = c.Request.Body
|
||||
}
|
||||
req, err := http.NewRequestWithContext(c.Request.Context(), c.Request.Method, target, body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"success": false, "message": "upstream request build failed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Forward only headers that matter for the upstream call. We intentionally
|
||||
// drop Cookie, Host, Origin, Referer so APIM does not see browser context.
|
||||
if auth := strings.TrimSpace(c.GetHeader("Authorization")); auth != "" {
|
||||
req.Header.Set("Authorization", auth)
|
||||
}
|
||||
if ct := strings.TrimSpace(c.GetHeader("Content-Type")); ct != "" {
|
||||
req.Header.Set("Content-Type", ct)
|
||||
}
|
||||
if accept := strings.TrimSpace(c.GetHeader("Accept")); accept != "" {
|
||||
req.Header.Set("Accept", accept)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"success": false, "message": "upstream request failed"})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if ct := resp.Header.Get("Content-Type"); ct != "" {
|
||||
c.Writer.Header().Set("Content-Type", ct)
|
||||
}
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
_, _ = io.Copy(c.Writer, resp.Body)
|
||||
}
|
||||
@@ -54,6 +54,12 @@ func SetApiRouter(router *gin.Engine) {
|
||||
// Universal secure verification routes
|
||||
apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify)
|
||||
|
||||
// Same-origin proxy for Heicode external identity service (APIM).
|
||||
// Frontend calls /api/heicode-auth/<path>; controller forwards to
|
||||
// ${HEICODE_AUTH_BASE_URL}/<path>. Avoids browser CORS preflight failures
|
||||
// when APIM has not added code.xinghanlab.com to its allow-list.
|
||||
apiRouter.Any("/heicode-auth/*proxyPath", middleware.CriticalRateLimit(), controller.HeicodeAuthProxy)
|
||||
|
||||
userRoute := apiRouter.Group("/user")
|
||||
{
|
||||
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register)
|
||||
|
||||
+380
-425
@@ -390,9 +390,9 @@ export function AgnetDeploymentsPage() {
|
||||
return (
|
||||
<>
|
||||
<PageSurface
|
||||
title={t('Work / Runs')}
|
||||
title={t('Task overview')}
|
||||
subtitle={t(
|
||||
'Track every Agnet work run by status, risk, budget, scope and secret_ref coverage.'
|
||||
'Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.'
|
||||
)}
|
||||
toolbar={
|
||||
<>
|
||||
@@ -441,95 +441,60 @@ export function AgnetDeploymentsPage() {
|
||||
hint={t('Adjust filters or trigger a new orchestration plan.')}
|
||||
/>
|
||||
) : (
|
||||
<div className='space-y-4'>
|
||||
{selectedRun && <RunDetailPanel dep={selectedRun} />}
|
||||
<div className='grid gap-3 sm:grid-cols-2'>
|
||||
{filtered.map((dep) => {
|
||||
const risk = describeRiskLevel(dep)
|
||||
const phase = dep.phase || dep.status
|
||||
const objective =
|
||||
dep.orchestration_plan?.objective ||
|
||||
dep.orchestration_plan?.template_hint ||
|
||||
t('No objective')
|
||||
const selected = dep.deployment_id === selectedRun?.deployment_id
|
||||
return (
|
||||
<article
|
||||
key={dep.deployment_id}
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
aria-pressed={selected}
|
||||
onClick={() => setSelectedRunId(dep.deployment_id)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
setSelectedRunId(dep.deployment_id)
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'group flex cursor-pointer flex-col gap-3 rounded-2xl border bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 transition hover:border-primary/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
selected
|
||||
? 'border-primary/55'
|
||||
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))]'
|
||||
)}
|
||||
>
|
||||
<header className='flex items-start justify-between gap-2'>
|
||||
<div className='min-w-0'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Rocket className='h-3.5 w-3.5 text-primary' />
|
||||
<span className='font-mono text-xs'>
|
||||
{dep.deployment_id}
|
||||
</span>
|
||||
</div>
|
||||
<p className='mt-1.5 line-clamp-2 text-sm font-medium'>
|
||||
{objective}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge phase={phase} />
|
||||
</header>
|
||||
<div className='grid gap-3 sm:grid-cols-2'>
|
||||
{filtered.map((dep) => {
|
||||
const phase = dep.phase || dep.status
|
||||
const objective =
|
||||
dep.orchestration_plan?.objective ||
|
||||
dep.orchestration_plan?.template_hint ||
|
||||
t('No objective')
|
||||
const selected = dep.deployment_id === selectedRun?.deployment_id
|
||||
return (
|
||||
<article
|
||||
key={dep.deployment_id}
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
aria-pressed={selected}
|
||||
onClick={() => setSelectedRunId(dep.deployment_id)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
setSelectedRunId(dep.deployment_id)
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'group flex cursor-pointer flex-col gap-3 rounded-2xl border bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 transition hover:border-primary/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
selected
|
||||
? 'border-primary/55'
|
||||
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))]'
|
||||
)}
|
||||
>
|
||||
<header className='flex items-start justify-between gap-2'>
|
||||
<p className='line-clamp-2 text-sm font-medium'>
|
||||
{objective}
|
||||
</p>
|
||||
<StatusBadge phase={phase} />
|
||||
</header>
|
||||
|
||||
<div className='flex flex-wrap gap-1.5'>
|
||||
<MetaPill
|
||||
icon={Tag}
|
||||
label={t('risk')}
|
||||
value={risk.label}
|
||||
/>
|
||||
<MetaPill
|
||||
icon={Coins}
|
||||
label={t('budget')}
|
||||
value={describeBudget(dep)}
|
||||
/>
|
||||
<MetaPill
|
||||
icon={Building2}
|
||||
label={t('scope')}
|
||||
value={describeScope(dep)}
|
||||
/>
|
||||
<MetaPill
|
||||
icon={ShieldCheck}
|
||||
label='secret_ref'
|
||||
value={describeSecretRefs(dep)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<footer className='mt-auto flex items-center justify-between border-t border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pt-3'>
|
||||
<span className='text-[11px] text-muted-foreground'>
|
||||
{formatRelativeTime(dep.updated_at || dep.created_at)}
|
||||
</span>
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
className='gap-1 text-primary'
|
||||
>
|
||||
<Link to='/events'>
|
||||
{t('Inspect events')}
|
||||
<ArrowUpRight className='h-3.5 w-3.5' />
|
||||
</Link>
|
||||
</Button>
|
||||
</footer>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<footer className='mt-auto flex items-center justify-between border-t border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pt-3'>
|
||||
<span className='text-[11px] text-muted-foreground'>
|
||||
{formatRelativeTime(dep.updated_at || dep.created_at)}
|
||||
</span>
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
className='gap-1 text-primary'
|
||||
>
|
||||
<Link to='/events'>
|
||||
{t('View activity')}
|
||||
<ArrowUpRight className='h-3.5 w-3.5' />
|
||||
</Link>
|
||||
</Button>
|
||||
</footer>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</PageSurface>
|
||||
@@ -831,9 +796,15 @@ export function AgnetAuditPage() {
|
||||
// resource-binding slice for Work/Runs.
|
||||
// =============================================================================
|
||||
|
||||
// AgnetSKSourcesPage — “准备清单” wizard. Frames the page as a 4-step list
|
||||
// (代码 / 文档 / 云账号 / 推荐摘要) per docs/product-package/10 §"Manager 准备清单"
|
||||
// + /11 §5. Does not expose repo_url / ref / paths / usage / tenant_id as the
|
||||
// main flow — those move into a “手动补充”次级 sheet only opened when the user
|
||||
// clicks “连接代码仓库 → 高级补充”.
|
||||
export function AgnetSKSourcesPage() {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false)
|
||||
const [gitForm, setGitForm] = useState<GitSourcePayload>({
|
||||
name: '',
|
||||
provider: 'github',
|
||||
@@ -882,365 +853,349 @@ export function AgnetSKSourcesPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const deploymentsQuery = useQuery({
|
||||
queryKey: ['agnet', 'deployments'],
|
||||
queryFn: listAgnetDeploymentsQuiet,
|
||||
})
|
||||
const deployments = deploymentsQuery.data ?? []
|
||||
const [activeDeployment, setActiveDeployment] = useState<string | undefined>(
|
||||
undefined
|
||||
const projectSources = gitSources.filter(
|
||||
(s) => s.usage === 'project' || s.usage === 'combined'
|
||||
)
|
||||
const skSources = gitSources.filter(
|
||||
(s) => s.usage === 'sk' || s.usage === 'combined'
|
||||
)
|
||||
const effectiveDeployment = activeDeployment ?? deployments[0]?.deployment_id
|
||||
|
||||
const snapshotsQuery = useQuery({
|
||||
queryKey: ['agnet', 'snapshots', effectiveDeployment],
|
||||
queryFn: () => getAgnetSnapshots(effectiveDeployment as string),
|
||||
enabled: Boolean(effectiveDeployment),
|
||||
})
|
||||
const steps = [
|
||||
{
|
||||
key: 'code',
|
||||
title: t('Connect code repository'),
|
||||
summary:
|
||||
projectSources.length > 0
|
||||
? t('{{n}} repository connected', { n: projectSources.length })
|
||||
: t('Authorize GitHub / GitLab / Gitee / self-hosted Git'),
|
||||
done: projectSources.length > 0,
|
||||
},
|
||||
{
|
||||
key: 'docs',
|
||||
title: t('Connect SK or project docs'),
|
||||
summary:
|
||||
skSources.length > 0
|
||||
? t('{{n}} source connected', { n: skSources.length })
|
||||
: t('Pick an existing SK / docs repository or skip'),
|
||||
done: skSources.length > 0,
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
key: 'cloud',
|
||||
title: t('Connect cloud account'),
|
||||
summary: t(
|
||||
'Authorize Azure / AWS / GCP. Heicode auto-discovers resources.'
|
||||
),
|
||||
done: false,
|
||||
pendingHint: t('Cloud auto-discovery — coming soon'),
|
||||
},
|
||||
{
|
||||
key: 'review',
|
||||
title: t('Confirm recommendation summary'),
|
||||
summary: t(
|
||||
'Heicode generates the parameters automatically. You only confirm allowed scope and risk.'
|
||||
),
|
||||
done: false,
|
||||
cta: true,
|
||||
},
|
||||
]
|
||||
|
||||
const snapshots = snapshotsQuery.data ?? []
|
||||
const completed = steps.filter((s) => s.done).length
|
||||
const total = steps.length
|
||||
const prereqsDone =
|
||||
projectSources.length > 0 || skSources.length > 0
|
||||
|
||||
return (
|
||||
<PageSurface
|
||||
title={t('Resources')}
|
||||
subtitle={t('Resources subtitle')}
|
||||
title={t('Preparation checklist')}
|
||||
subtitle={t(
|
||||
'Connect code, docs and cloud resources for the current task, then confirm the recommendation before launching Agnet.'
|
||||
)}
|
||||
toolbar={
|
||||
<Select
|
||||
value={effectiveDeployment ?? ''}
|
||||
onValueChange={setActiveDeployment}
|
||||
disabled={deployments.length === 0}
|
||||
>
|
||||
<SelectTrigger className='h-9 w-60 rounded-xl text-xs'>
|
||||
<Rocket className='mr-1 h-3.5 w-3.5' />
|
||||
<SelectValue placeholder={t('Select deployment')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{deployments.map((dep) => (
|
||||
<SelectItem key={dep.deployment_id} value={dep.deployment_id}>
|
||||
<span className='font-mono text-xs'>{dep.deployment_id}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className='inline-flex items-center gap-1.5 rounded-full border border-[color-mix(in_oklch,var(--primary)_30%,var(--border))] bg-[color-mix(in_oklch,var(--primary)_10%,transparent)] px-3 py-1 text-[11px] font-semibold tracking-[0.12em] text-primary uppercase'>
|
||||
<CheckCircle2 className='h-3 w-3' />
|
||||
{t('{{done}}/{{total}} completed', { done: completed, total })}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className='grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(360px,0.8fr)]'>
|
||||
<div className='rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_50%,transparent)] p-4'>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<p className='text-sm font-medium text-foreground'>
|
||||
{t('Bind resource source')}
|
||||
<ol className='space-y-3'>
|
||||
{steps.map((step, idx) => (
|
||||
<li
|
||||
key={step.key}
|
||||
className='flex items-start gap-4 rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4'
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold ring-1 ring-inset',
|
||||
step.done
|
||||
? 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30'
|
||||
: 'bg-[color-mix(in_oklch,var(--primary)_12%,transparent)] text-primary ring-primary/30'
|
||||
)}
|
||||
>
|
||||
{step.done ? <CheckCircle2 className='h-3.5 w-3.5' /> : idx + 1}
|
||||
</span>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='text-sm font-semibold text-foreground'>
|
||||
{step.title}
|
||||
{step.optional && (
|
||||
<span className='ms-2 text-[10px] font-medium tracking-[0.12em] text-muted-foreground uppercase'>
|
||||
{t('optional')}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className='mt-1 text-xs text-muted-foreground'>
|
||||
{t('Bind resource source description')}
|
||||
{step.summary}
|
||||
</p>
|
||||
{step.pendingHint && (
|
||||
<p className='mt-1 text-[11px] font-medium tracking-[0.08em] text-amber-400/90 uppercase'>
|
||||
{step.pendingHint}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<GitBranch className='h-5 w-5 text-primary' />
|
||||
</div>
|
||||
{step.key === 'code' || step.key === 'docs' ? (
|
||||
<Button
|
||||
type='button'
|
||||
variant={step.done ? 'ghost' : 'default'}
|
||||
size='sm'
|
||||
className='shrink-0 rounded-xl'
|
||||
onClick={() => setAdvancedOpen(true)}
|
||||
>
|
||||
{step.done ? t('Manage') : t('Connect')}
|
||||
</Button>
|
||||
) : step.key === 'review' ? (
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
className='shrink-0 rounded-xl'
|
||||
disabled={!prereqsDone}
|
||||
>
|
||||
{t('Confirm and launch Agnet')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='shrink-0 rounded-xl'
|
||||
disabled
|
||||
>
|
||||
{t('Coming soon')}
|
||||
</Button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
<div className='mt-4 grid gap-3'>
|
||||
<div className='grid gap-2 sm:grid-cols-2'>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Source name')}
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.name}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, name: e.target.value }))
|
||||
}
|
||||
placeholder='project-main'
|
||||
className='mt-1 h-9 text-xs'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Provider')}
|
||||
</label>
|
||||
<Select
|
||||
value={gitForm.provider}
|
||||
onValueChange={(provider) =>
|
||||
setGitForm((v) => ({ ...v, provider }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className='mt-1 h-9 text-xs'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='github'>GitHub</SelectItem>
|
||||
<SelectItem value='gitlab'>GitLab</SelectItem>
|
||||
<SelectItem value='gitea'>Gitea</SelectItem>
|
||||
<SelectItem value='gitee'>Gitee</SelectItem>
|
||||
<SelectItem value='custom'>Custom Git</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
repo_url
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.repo_url}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, repo_url: e.target.value }))
|
||||
}
|
||||
placeholder='https://github.com/org/repo.git'
|
||||
className='mt-1 h-9 font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-2 sm:grid-cols-3'>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
ref
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.ref}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, ref: e.target.value }))
|
||||
}
|
||||
className='mt-1 h-9 font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Usage')}
|
||||
</label>
|
||||
<Select
|
||||
value={gitForm.usage}
|
||||
onValueChange={(usage) =>
|
||||
setGitForm((v) => ({
|
||||
...v,
|
||||
usage: usage as GitSourceUsage,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className='mt-1 h-9 text-xs'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='project'>
|
||||
{t('Project repository')}
|
||||
</SelectItem>
|
||||
<SelectItem value='sk'>{t('SK repository')}</SelectItem>
|
||||
<SelectItem value='combined'>
|
||||
{t('Combined repository')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Owner scope ref')}
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.tenant_id}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, tenant_id: e.target.value }))
|
||||
}
|
||||
placeholder='optional'
|
||||
className='mt-1 h-9 font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Allowed paths')}
|
||||
</label>
|
||||
<textarea
|
||||
value={pathsText}
|
||||
onChange={(e) => setPathsText(e.target.value)}
|
||||
rows={3}
|
||||
spellCheck={false}
|
||||
className='mt-1 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
className='w-fit gap-1.5'
|
||||
disabled={
|
||||
createGitMutation.isPending ||
|
||||
!gitForm.name.trim() ||
|
||||
!gitForm.repo_url.trim()
|
||||
}
|
||||
onClick={() => createGitMutation.mutate()}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
{t('Bind source')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_50%,transparent)] p-4'>
|
||||
<p className='text-sm font-medium text-foreground'>
|
||||
{t('Bound resource sources')}
|
||||
</p>
|
||||
{gitSourcesQuery.isLoading ? (
|
||||
<div className='mt-3 space-y-2'>
|
||||
<Skeleton className='h-16 rounded-xl' />
|
||||
<Skeleton className='h-16 rounded-xl' />
|
||||
</div>
|
||||
) : gitSources.length === 0 ? (
|
||||
<p className='mt-3 text-xs text-muted-foreground'>
|
||||
{t('No resource sources bound yet')}
|
||||
</p>
|
||||
) : (
|
||||
<ul className='mt-3 space-y-2'>
|
||||
{gitSources.map((src) => (
|
||||
<li
|
||||
key={src.id}
|
||||
className='rounded-xl border border-border bg-background/60 p-3'
|
||||
>
|
||||
<div className='flex items-start justify-between gap-3'>
|
||||
<div className='min-w-0'>
|
||||
<p className='truncate text-sm font-medium'>{src.name}</p>
|
||||
<p className='mt-1 truncate font-mono text-[11px] text-muted-foreground'>
|
||||
{src.repo_url}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
className='h-8 w-8 shrink-0'
|
||||
disabled={deleteGitMutation.isPending}
|
||||
onClick={() => deleteGitMutation.mutate(src.id)}
|
||||
>
|
||||
<Trash2 className='h-3.5 w-3.5 text-destructive' />
|
||||
</Button>
|
||||
</div>
|
||||
<div className='mt-2 flex flex-wrap gap-1.5'>
|
||||
<MetaPill icon={GitBranch} label='ref' value={src.ref} />
|
||||
<MetaPill icon={Tag} label='usage' value={src.usage} />
|
||||
<MetaPill
|
||||
icon={FileSearch}
|
||||
label='paths'
|
||||
value={src.paths.join(', ')}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className='rounded-2xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_40%,transparent)] p-4 text-xs leading-relaxed text-muted-foreground'>
|
||||
<p className='font-medium text-foreground'>{t('How this works')}</p>
|
||||
<p className='mt-2'>
|
||||
{t(
|
||||
'Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_50%,transparent)] p-4 text-sm leading-relaxed text-muted-foreground'>
|
||||
<p className='font-medium text-foreground'>{t('Resource binding')}</p>
|
||||
<p className='mt-2'>{t('Resources binding explainer')}</p>
|
||||
</div>
|
||||
|
||||
<div className='mt-4 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_50%,transparent)] p-4 text-sm leading-relaxed text-muted-foreground'>
|
||||
<p className='font-medium text-foreground'>
|
||||
{t('Resources workflow title')}
|
||||
</p>
|
||||
<ol className='mt-3 list-decimal space-y-2 ps-5 marker:text-muted-foreground'>
|
||||
<li>{t('Resources workflow step 1')}</li>
|
||||
<li>{t('Resources workflow step 2')}</li>
|
||||
<li>{t('Resources workflow step 3')}</li>
|
||||
<li>{t('Resources workflow step 4')}</li>
|
||||
<li>{t('Resources workflow step 5')}</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{!effectiveDeployment ? (
|
||||
<EmptySurface
|
||||
title={t('No deployment for Git sources')}
|
||||
hint={t('No deployment for Git sources hint')}
|
||||
/>
|
||||
) : snapshotsQuery.isLoading ? (
|
||||
<LoadingGrid rows={3} height='h-24' />
|
||||
) : snapshots.length === 0 ? (
|
||||
<EmptySurface
|
||||
title={t('No resolved snapshots yet')}
|
||||
hint={t('No resolved snapshots hint')}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p className='mb-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground'>
|
||||
{t('Resolved snapshot anchors')}
|
||||
</p>
|
||||
<ul className='space-y-3'>
|
||||
{snapshots.map((entry, idx) => {
|
||||
const e = entry as Record<string, unknown>
|
||||
const sourceType = String(e.source_type || 'unknown')
|
||||
const sourceRef = String(e.source_ref || e.ref || '—')
|
||||
const hash = String(e.hash || e.snapshot_hash || '—')
|
||||
const resolvedAt = String(e.resolved_at || '')
|
||||
return (
|
||||
<li
|
||||
key={idx}
|
||||
className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4'
|
||||
>
|
||||
<div className='grid gap-3 sm:grid-cols-3'>
|
||||
<div className='flex items-start gap-2'>
|
||||
<div className='inline-flex h-8 w-8 items-center justify-center rounded-lg bg-[color-mix(in_oklch,var(--primary)_18%,transparent)] text-primary'>
|
||||
<Bot className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<p className='text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
|
||||
{t('source')}
|
||||
</p>
|
||||
<p className='mt-0.5 truncate font-mono text-xs'>
|
||||
{sourceType}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-2'>
|
||||
<div className='inline-flex h-8 w-8 items-center justify-center rounded-lg bg-[color-mix(in_oklch,var(--primary)_18%,transparent)] text-primary'>
|
||||
<GitBranch className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<p className='text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
|
||||
{t('reference')}
|
||||
</p>
|
||||
<p className='mt-0.5 truncate font-mono text-xs'>
|
||||
{sourceRef}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-2'>
|
||||
<div className='inline-flex h-8 w-8 items-center justify-center rounded-lg bg-[color-mix(in_oklch,var(--primary)_18%,transparent)] text-primary'>
|
||||
<Hash className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<p className='text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
|
||||
{t('hash')}
|
||||
</p>
|
||||
<p
|
||||
className='mt-0.5 truncate font-mono text-xs'
|
||||
title={hash}
|
||||
>
|
||||
{hash.length > 18
|
||||
? `${hash.slice(0, 8)}…${hash.slice(-6)}`
|
||||
: hash}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{resolvedAt && (
|
||||
<p className='mt-3 border-t border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pt-2 text-[11px] text-muted-foreground'>
|
||||
<Calendar className='mr-1 inline h-3 w-3' />
|
||||
{t('resolved')} {formatRelativeTime(resolvedAt)}
|
||||
</p>
|
||||
{advancedOpen && (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm'>
|
||||
<div className='max-h-[85vh] w-[min(640px,92vw)] overflow-auto rounded-2xl border border-[color-mix(in_oklch,var(--primary)_24%,var(--border))] bg-card p-5 shadow-2xl'>
|
||||
<header className='flex items-start justify-between gap-3 border-b border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pb-3'>
|
||||
<div>
|
||||
<p className='text-[11px] font-semibold tracking-[0.16em] text-muted-foreground uppercase'>
|
||||
{t('Advanced — manual entry')}
|
||||
</p>
|
||||
<h3 className='mt-1 text-base font-semibold'>
|
||||
{t('Connect a code or SK repository')}
|
||||
</h3>
|
||||
<p className='mt-1 text-xs text-muted-foreground'>
|
||||
{t(
|
||||
'Only needed when auto-discovery cannot find the source. Heicode will store the binding and never expose plaintext credentials.'
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => setAdvancedOpen(false)}
|
||||
>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<div className='mt-4 grid gap-3'>
|
||||
<div className='grid gap-2 sm:grid-cols-2'>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Source name')}
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.name}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, name: e.target.value }))
|
||||
}
|
||||
placeholder='project-main'
|
||||
className='mt-1 h-9 text-xs'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Provider')}
|
||||
</label>
|
||||
<Select
|
||||
value={gitForm.provider}
|
||||
onValueChange={(provider) =>
|
||||
setGitForm((v) => ({ ...v, provider }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className='mt-1 h-9 text-xs'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='github'>GitHub</SelectItem>
|
||||
<SelectItem value='gitlab'>GitLab</SelectItem>
|
||||
<SelectItem value='gitea'>Gitea</SelectItem>
|
||||
<SelectItem value='gitee'>Gitee</SelectItem>
|
||||
<SelectItem value='custom'>Custom Git</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Repository URL')}
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.repo_url}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, repo_url: e.target.value }))
|
||||
}
|
||||
placeholder='https://github.com/org/repo.git'
|
||||
className='mt-1 h-9 font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-2 sm:grid-cols-2'>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Branch')}
|
||||
</label>
|
||||
<Input
|
||||
value={gitForm.ref}
|
||||
onChange={(e) =>
|
||||
setGitForm((v) => ({ ...v, ref: e.target.value }))
|
||||
}
|
||||
className='mt-1 h-9 font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Usage')}
|
||||
</label>
|
||||
<Select
|
||||
value={gitForm.usage}
|
||||
onValueChange={(usage) =>
|
||||
setGitForm((v) => ({
|
||||
...v,
|
||||
usage: usage as GitSourceUsage,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className='mt-1 h-9 text-xs'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='project'>
|
||||
{t('Project repository')}
|
||||
</SelectItem>
|
||||
<SelectItem value='sk'>{t('SK repository')}</SelectItem>
|
||||
<SelectItem value='combined'>
|
||||
{t('Combined repository')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='text-xs font-medium text-muted-foreground'>
|
||||
{t('Allowed paths')}
|
||||
</label>
|
||||
<textarea
|
||||
value={pathsText}
|
||||
onChange={(e) => setPathsText(e.target.value)}
|
||||
rows={2}
|
||||
spellCheck={false}
|
||||
className='mt-1 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
className='w-fit gap-1.5'
|
||||
disabled={
|
||||
createGitMutation.isPending ||
|
||||
!gitForm.name.trim() ||
|
||||
!gitForm.repo_url.trim()
|
||||
}
|
||||
onClick={() => createGitMutation.mutate()}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
{t('Bind source')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 border-t border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pt-4'>
|
||||
<p className='text-sm font-medium'>
|
||||
{t('Connected sources')}
|
||||
</p>
|
||||
{gitSourcesQuery.isLoading ? (
|
||||
<div className='mt-2 space-y-2'>
|
||||
<Skeleton className='h-12 rounded-lg' />
|
||||
<Skeleton className='h-12 rounded-lg' />
|
||||
</div>
|
||||
) : gitSources.length === 0 ? (
|
||||
<p className='mt-2 text-xs text-muted-foreground'>
|
||||
{t('No sources connected yet.')}
|
||||
</p>
|
||||
) : (
|
||||
<ul className='mt-2 space-y-2'>
|
||||
{gitSources.map((src) => (
|
||||
<li
|
||||
key={src.id}
|
||||
className='flex items-start justify-between gap-3 rounded-lg border border-border bg-background/60 p-3'
|
||||
>
|
||||
<div className='min-w-0'>
|
||||
<p className='truncate text-sm font-medium'>
|
||||
{src.name}
|
||||
</p>
|
||||
<p className='mt-0.5 truncate font-mono text-[11px] text-muted-foreground'>
|
||||
{src.repo_url}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
className='h-8 w-8 shrink-0'
|
||||
disabled={deleteGitMutation.isPending}
|
||||
onClick={() => deleteGitMutation.mutate(src.id)}
|
||||
>
|
||||
<Trash2 className='h-3.5 w-3.5 text-destructive' />
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageSurface>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Templates / Agents (kept for backward compatibility — invoked by side routes)
|
||||
// =============================================================================
|
||||
|
||||
+3
-1
@@ -13,9 +13,11 @@ import type {
|
||||
ApiResponse,
|
||||
} from './types'
|
||||
|
||||
// Default to same-origin proxy so the browser never hits APIM directly (CORS).
|
||||
// Backend route: /api/heicode-auth/* → ${HEICODE_AUTH_BASE_URL}/*
|
||||
const AUTH_BASE_URL = (
|
||||
(import.meta.env.VITE_HEICODE_AUTH_BASE_URL as string | undefined) ||
|
||||
'https://apimtaiji.azure-api.net/api/mcp'
|
||||
'/api/heicode-auth'
|
||||
).trim()
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'heicode_access_token'
|
||||
|
||||
+13
-39
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
Activity,
|
||||
Command,
|
||||
Cpu,
|
||||
Download,
|
||||
FileBarChart,
|
||||
GitBranch,
|
||||
LayoutDashboard,
|
||||
Rocket,
|
||||
@@ -19,21 +16,13 @@ import { type SidebarData } from '@/components/layout/types'
|
||||
/**
|
||||
* Heicode Manager 默认 workspace 侧边栏。
|
||||
*
|
||||
* 设计原则:
|
||||
* - 平台是管理后台:登录后默认仅展示 Code delivery cockpit + 个人入口;
|
||||
* - 不再向普通用户暴露 Playground / API Keys / Models 等开发者控制台条目;
|
||||
* - Usage logs 作为交付链路的一环,归并进 Code delivery 分组;
|
||||
* - Code delivery 菜单顺序:总览 → Git 来源(绑定代码/SK 与云上权限)→ 部署(子 Agent)→ 事件 → 审计 → 用量日志;
|
||||
* - Git sources:绑定完成后部署,再在此对照各次部署的快照锚点;SK 正文仍以 Git 为准,不提供 Markdown 编辑;
|
||||
* - 所有 admin/root 管理类入口(Channels/Models/Tenants/Redemption/...)
|
||||
* 收纳进 "系统设置" workspace(点击进入 /system-settings 后才出现)。
|
||||
* 文案与结构以 docs/product-package/10-frontend-detail-spec.md §"全局体验结构" 和
|
||||
* docs/product-package/11-product-prototype-wireframes.md §4 Manager 原型为准。
|
||||
*
|
||||
* Visibility rules (filter happens in `app-sidebar.tsx` by `group.id`):
|
||||
* - everyone (no id filter): cockpit / personal
|
||||
* - id === 'admin-entry' -> ROLE.ADMIN+ (单一入口:系统设置)
|
||||
*
|
||||
* 注意:进入 /system-settings 之后侧边栏由
|
||||
* `components/layout/config/system-settings.config.ts` 接管。
|
||||
* - 不使用"Git 来源 / Deployments / Events"等后台术语,统一替换为"准备清单 / 任务总览 / 审计"等用户语义。
|
||||
* - 钱包 + 可用模型 合并为"模型与余额"。
|
||||
* - 我的资料 改名"账号安全"。
|
||||
* - admin/root 管理类入口(Channels/Models/Tenants/...)仍统一归到"系统设置" workspace。
|
||||
*/
|
||||
export function useSidebarData(): SidebarData {
|
||||
const { t } = useTranslation()
|
||||
@@ -48,10 +37,10 @@ export function useSidebarData(): SidebarData {
|
||||
},
|
||||
],
|
||||
navGroups: [
|
||||
// ============ Code delivery cockpit ============
|
||||
// ============ Heicode 主流程(按 docs §10 全局体验结构)============
|
||||
{
|
||||
id: 'cockpit',
|
||||
title: t('Code delivery'),
|
||||
title: t('Heicode'),
|
||||
items: [
|
||||
{
|
||||
title: t('Overview'),
|
||||
@@ -59,55 +48,40 @@ export function useSidebarData(): SidebarData {
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: t('Git sources'),
|
||||
title: t('Preparation checklist'),
|
||||
url: '/sk-sources',
|
||||
icon: GitBranch,
|
||||
},
|
||||
{
|
||||
title: t('Deployments'),
|
||||
title: t('Task overview'),
|
||||
url: '/deployments',
|
||||
icon: Rocket,
|
||||
},
|
||||
{
|
||||
title: t('Events'),
|
||||
url: '/events',
|
||||
icon: Activity,
|
||||
},
|
||||
{
|
||||
title: t('Audit'),
|
||||
url: '/audit',
|
||||
icon: ShieldCheck,
|
||||
},
|
||||
{
|
||||
title: t('Usage logs'),
|
||||
url: '/usage-logs',
|
||||
icon: FileBarChart,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ============ Personal ============
|
||||
// ============ 辅助入口 ============
|
||||
{
|
||||
id: 'personal',
|
||||
title: t('Personal'),
|
||||
items: [
|
||||
{
|
||||
title: t('Wallet'),
|
||||
title: t('Models and balance'),
|
||||
url: '/wallet',
|
||||
icon: Wallet,
|
||||
},
|
||||
{
|
||||
title: t('Available models'),
|
||||
url: '/available-models',
|
||||
icon: Cpu,
|
||||
},
|
||||
{
|
||||
title: t('Heicode desktop client'),
|
||||
url: '/desktop-client',
|
||||
icon: Download,
|
||||
},
|
||||
{
|
||||
title: t('Profile'),
|
||||
title: t('Account security'),
|
||||
url: '/profile',
|
||||
icon: UserCog,
|
||||
},
|
||||
|
||||
+37
-3
@@ -1,4 +1,36 @@
|
||||
{
|
||||
"Account security": "Account security",
|
||||
"Advanced — manual entry": "Advanced — manual entry",
|
||||
"Authorize Azure / AWS / GCP. Heicode auto-discovers resources.": "Authorize Azure / AWS / GCP. Heicode auto-discovers resources.",
|
||||
"Authorize GitHub / GitLab / Gitee / self-hosted Git": "Authorize GitHub / GitLab / Gitee / self-hosted Git",
|
||||
"Branch": "Branch",
|
||||
"Close": "Close",
|
||||
"Cloud auto-discovery — coming soon": "Cloud auto-discovery — coming soon",
|
||||
"Coming soon": "Coming soon",
|
||||
"Confirm and launch Agnet": "Confirm and launch Agnet",
|
||||
"Confirm recommendation summary": "Confirm recommendation summary",
|
||||
"Connect": "Connect",
|
||||
"Connect a code or SK repository": "Connect a code or SK repository",
|
||||
"Connect cloud account": "Connect cloud account",
|
||||
"Connect code repository": "Connect code repository",
|
||||
"Connect SK or project docs": "Connect SK or project docs",
|
||||
"Connected sources": "Connected sources",
|
||||
"Heicode": "Heicode",
|
||||
"Heicode generates the parameters automatically. You only confirm allowed scope and risk.": "Heicode generates the parameters automatically. You only confirm allowed scope and risk.",
|
||||
"How this works": "How this works",
|
||||
"Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.": "Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.",
|
||||
"Manage": "Manage",
|
||||
"Models and balance": "Models and balance",
|
||||
"No sources connected yet.": "No sources connected yet.",
|
||||
"No tasks yet": "No tasks yet",
|
||||
"Only needed when auto-discovery cannot find the source. Heicode will store the binding and never expose plaintext credentials.": "Only needed when auto-discovery cannot find the source. Heicode will store the binding and never expose plaintext credentials.",
|
||||
"optional": "optional",
|
||||
"Pick an existing SK / docs repository or skip": "Pick an existing SK / docs repository or skip",
|
||||
"Preparation checklist": "Preparation checklist",
|
||||
"Repository URL": "Repository URL",
|
||||
"Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.": "Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.",
|
||||
"Task overview": "Task overview",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.": "Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.",
|
||||
"translation": {
|
||||
"360": "360",
|
||||
"1000": "1000",
|
||||
@@ -2710,7 +2742,6 @@
|
||||
"Provide Markdown, HTML, or an external URL for the user agreement": "Provide Markdown, HTML, or an external URL for the user agreement",
|
||||
"Provide per-category safety overrides as JSON. Use `default` for fallback values.": "Provide per-category safety overrides as JSON. Use `default` for fallback values.",
|
||||
"Provide per-model header overrides as JSON. Useful for enabling beta features such as expanded context windows.": "Provide per-model header overrides as JSON. Useful for enabling beta features such as expanded context windows.",
|
||||
"Provider": "Provider",
|
||||
"Provider created successfully": "Provider created successfully",
|
||||
"Provider deleted successfully": "Provider deleted successfully",
|
||||
"Provider Name": "Provider Name",
|
||||
@@ -3633,7 +3664,6 @@
|
||||
"URL": "URL",
|
||||
"URL is required": "URL is required",
|
||||
"URL to your logo image (optional)": "URL to your logo image (optional)",
|
||||
"Usage": "Usage",
|
||||
"Usage logs": "Usage logs",
|
||||
"Usage Logs": "Usage Logs",
|
||||
"Usage mode": "Usage mode",
|
||||
@@ -3918,5 +3948,9 @@
|
||||
"Resources workflow step 3": "Allocate scope, allowed paths/actions, runtime policy, and budget to the run manifest.",
|
||||
"Resources workflow step 4": "Start a Work/Run; Agnet resolves immutable anchors and enforces the effective grants.",
|
||||
"Resources workflow step 5": "Use snapshots, events, and audit together to replay which resource context actually ran."
|
||||
}
|
||||
},
|
||||
"View activity": "View activity",
|
||||
"{{done}}/{{total}} completed": "{{done}}/{{total}} completed",
|
||||
"{{n}} repository connected": "{{n}} repository connected",
|
||||
"{{n}} source connected": "{{n}} source connected"
|
||||
}
|
||||
|
||||
+39
-5
@@ -1,4 +1,36 @@
|
||||
{
|
||||
"Account security": "账号安全",
|
||||
"Advanced — manual entry": "高级 · 手动补充",
|
||||
"Authorize Azure / AWS / GCP. Heicode auto-discovers resources.": "授权 Azure / AWS / GCP,Heicode 自动发现资源",
|
||||
"Authorize GitHub / GitLab / Gitee / self-hosted Git": "授权 GitHub / GitLab / Gitee / 自建 Git",
|
||||
"Branch": "分支",
|
||||
"Close": "关闭",
|
||||
"Cloud auto-discovery — coming soon": "云资源自动发现 · 即将上线",
|
||||
"Coming soon": "即将上线",
|
||||
"Confirm and launch Agnet": "确认并启动 Agnet",
|
||||
"Confirm recommendation summary": "确认推荐摘要",
|
||||
"Connect": "连接",
|
||||
"Connect a code or SK repository": "连接代码 / SK 仓库",
|
||||
"Connect cloud account": "连接云账号",
|
||||
"Connect code repository": "连接代码仓库",
|
||||
"Connect SK or project docs": "连接 SK / 项目文档",
|
||||
"Connected sources": "已连接的来源",
|
||||
"Heicode": "Heicode",
|
||||
"Heicode generates the parameters automatically. You only confirm allowed scope and risk.": "Heicode 自动生成参数,你只需要确认允许范围和风险",
|
||||
"How this works": "这里发生了什么",
|
||||
"Long-lived credentials are stored in the secret vault. Agnet only requests short-lived, scoped credentials at run time. Production deploys and other high-risk actions are approved from the desktop client.": "长期凭证保存在密钥保管器。Agnet 执行时只申请短期、最小权限凭证。生产部署等高危操作需在客户端审批。",
|
||||
"Manage": "管理",
|
||||
"Models and balance": "模型与余额",
|
||||
"No sources connected yet.": "暂未连接任何来源",
|
||||
"No tasks yet": "暂无任务",
|
||||
"Only needed when auto-discovery cannot find the source. Heicode will store the binding and never expose plaintext credentials.": "仅在自动发现无法识别时使用。Heicode 只保存绑定关系,不展示明文凭证。",
|
||||
"optional": "可选",
|
||||
"Pick an existing SK / docs repository or skip": "选择已有 SK / 文档仓库,或跳过",
|
||||
"Preparation checklist": "准备清单",
|
||||
"Repository URL": "仓库地址",
|
||||
"Status, latest activity and last update for every Agnet task you launched. Details live in the desktop client.": "查看每个启动的 Agnet 任务的状态、最近动态和更新时间。详细对话与产物在客户端中查看。",
|
||||
"Task overview": "任务总览",
|
||||
"Tasks appear here after you confirm the recommendation in the desktop client and launch Agnet.": "在客户端确认推荐摘要并启动 Agnet 后,任务会出现在这里。",
|
||||
"translation": {
|
||||
"360": "360",
|
||||
"1000": "1000",
|
||||
@@ -1664,10 +1696,10 @@
|
||||
"GitHub": "GitHub",
|
||||
"No Git sources bound yet": "还没有绑定 Git 来源。",
|
||||
"Project repository": "项目仓库",
|
||||
"Provider": "服务商",
|
||||
"Provider": "提供商",
|
||||
"SK repository": "SK 技能仓库",
|
||||
"Source name": "来源名称",
|
||||
"Usage": "用途",
|
||||
"Usage": "用量",
|
||||
"Give the group a recognizable name and optional description.": "为该分组提供一个可识别的名称和可选的描述。",
|
||||
"Give this group a recognizable name.": "为此分组提供一个可识别的名称。",
|
||||
"Global configuration and administrative tools.": "全局配置和管理工具。",
|
||||
@@ -2710,7 +2742,6 @@
|
||||
"Provide Markdown, HTML, or an external URL for the user agreement": "提供 Markdown、HTML 或外部 URL 作为用户协议",
|
||||
"Provide per-category safety overrides as JSON. Use `default` for fallback values.": "以 JSON 格式提供按类别划分的安全覆盖。使用 `default` 作为回退值。",
|
||||
"Provide per-model header overrides as JSON. Useful for enabling beta features such as expanded context windows.": "以 JSON 格式提供按模型划分的标头覆盖。可用于启用测试功能,例如扩展上下文窗口。",
|
||||
"Provider": "提供商",
|
||||
"Provider created successfully": "提供商创建成功",
|
||||
"Provider deleted successfully": "提供商删除成功",
|
||||
"Provider Name": "提供商名称",
|
||||
@@ -3633,7 +3664,6 @@
|
||||
"URL": "URL",
|
||||
"URL is required": "URL 为必填项",
|
||||
"URL to your logo image (optional)": "您的徽标图片 URL(可选)",
|
||||
"Usage": "用量",
|
||||
"Usage logs": "使用日志",
|
||||
"Usage Logs": "使用日志",
|
||||
"Usage mode": "使用模式",
|
||||
@@ -3918,5 +3948,9 @@
|
||||
"Resources workflow step 3": "为运行 manifest 分配范围、允许路径/动作、运行策略和预算。",
|
||||
"Resources workflow step 4": "启动 Work/Run;Agnet 解析不可变锚点并执行有效授权。",
|
||||
"Resources workflow step 5": "结合快照、事件和审计回放实际运行的资源上下文。"
|
||||
}
|
||||
},
|
||||
"View activity": "查看动态",
|
||||
"{{done}}/{{total}} completed": "{{done}}/{{total}} 已完成",
|
||||
"{{n}} repository connected": "已连接 {{n}} 个仓库",
|
||||
"{{n}} source connected": "已连接 {{n}} 个来源"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user