fix(web): remove old "运行总览" task UI; Overview shows new template agents
The old /deployments (运行总览) + /tasks/$id pages rendered the new template agents with meaningless old sub-task fields (待确认/sub_agile/子任务流/智能体任务图/产物/ SK快照/合并时间线 — all empty). Removed the whole old-model UI cluster and pointed the Overview dashboard at the real new endpoint: - deleted features/agent-console, features/agent-hub, features/tasks and the routes /deployments and /tasks/$id. - cockpit (Overview) now lists deployed template agents from /api/heicode/agents (status counts + recent agents), not the old listAgentDeployments. - removed the "运行总览" sidebar item + dead /deployments links in footer / top-nav / sidebar-config. - routeTree regenerated; frontend build + tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vendored
+1
-1
@@ -20,7 +20,7 @@
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="theme-color" content="#7B6BE3" />
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.829c7e3fad.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/6374.bc21d8b214.js"></script><script defer src="/static/js/index.13c9f5d1a3.js"></script><link href="/static/css/index.592272f49e.css" rel="stylesheet"></head>
|
||||
<link rel="icon" href="/favicon.ico"><script defer src="/static/js/vendor-radix.829c7e3fad.js"></script><script defer src="/static/js/vendor-tanstack.632dbe8908.js"></script><script defer src="/static/js/lib-react.5c8909c28c.js"></script><script defer src="/static/js/6374.bc21d8b214.js"></script><script defer src="/static/js/index.046d6d6bc8.js"></script><link href="/static/css/index.48ece32abf.css" rel="stylesheet"></head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -77,7 +77,6 @@ export function Footer(props: FooterProps) {
|
||||
{
|
||||
title: t('footer.columns.docs.title'),
|
||||
links: [
|
||||
{ text: t('Deployments'), href: '/deployments' },
|
||||
{ text: t('API Keys'), href: '/keys' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,521 +0,0 @@
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
export type AgentSubMode = 'agile' | 'waterfall'
|
||||
|
||||
/** Sub-agent cloud/runtime binding (passed to Agent on deploy). */
|
||||
export type AgentRuntimeExecution = {
|
||||
profile_id?: string
|
||||
cloud_principal_refs?: string[]
|
||||
network_policy_ref?: string
|
||||
}
|
||||
|
||||
/** SK allow/deny policy attached to the agent in the deployment plan. */
|
||||
export type AgentSKAccessPolicy = {
|
||||
policy_ref?: string
|
||||
deny_skill_ids?: string[]
|
||||
inherit_deployment_defaults?: boolean
|
||||
}
|
||||
|
||||
/** Git-backed SK source (`type: git`). */
|
||||
export type AgentRepoRef = {
|
||||
connection_id?: string
|
||||
repo_url?: string
|
||||
ref: string
|
||||
paths: string[]
|
||||
}
|
||||
|
||||
/** Single SK source entry (git or upload). */
|
||||
export type AgentSKSource = {
|
||||
type?: string
|
||||
artifact_id?: string
|
||||
mime?: string
|
||||
repo_ref?: AgentRepoRef
|
||||
}
|
||||
|
||||
export type AgentAgentPlan = {
|
||||
role_template: string
|
||||
goal: string
|
||||
default_model_id?: string
|
||||
sk_sources?: AgentSKSource[]
|
||||
runtime_execution?: AgentRuntimeExecution
|
||||
sk_access_policy?: AgentSKAccessPolicy
|
||||
resource_grants?: Array<{
|
||||
grant_id?: string
|
||||
resource_id?: string
|
||||
resource_type?: string
|
||||
user_id?: string
|
||||
binding_scope?: string
|
||||
target_role?: string
|
||||
target_agent_ref?: string
|
||||
permission_scope?: string[]
|
||||
constraints?: Record<string, string>
|
||||
metadata?: Record<string, string>
|
||||
status?: 'pending' | 'active' | 'disabled' | 'revoked'
|
||||
secret_ref?: string
|
||||
audit?: Record<string, string>
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgentBudget = {
|
||||
max_tokens: number
|
||||
max_cost_usd: number
|
||||
max_duration_sec: number
|
||||
}
|
||||
|
||||
export type AgentUserContext = {
|
||||
user_id: string
|
||||
email?: string
|
||||
role?: string
|
||||
channel_id?: string
|
||||
subscription_tier?: string
|
||||
}
|
||||
|
||||
export type AgentBillingContext = {
|
||||
provider?: 'newapi'
|
||||
newapi_user_ref?: string
|
||||
newapi_group?: string
|
||||
quota_ref?: string
|
||||
}
|
||||
|
||||
export type AgentAgentRuntime = {
|
||||
platform?: 'agent'
|
||||
agents?: Array<{
|
||||
role: string
|
||||
model_ref: string
|
||||
instance_count: number
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgentConstraints = {
|
||||
/** Runtime model allow-list for Agent deployments; not a NewAPI billing map. */
|
||||
allowed_model_ids?: string[]
|
||||
}
|
||||
|
||||
export type AgentOrchestrationMetadata = {
|
||||
/**
|
||||
* Compatibility field for Agent routing scope; UI treats this as user scope,
|
||||
* not billing tenant.
|
||||
*/
|
||||
tenant_id: string
|
||||
/**
|
||||
* Compatibility field for Agent routing scope; UI treats this as resource
|
||||
* scope, not project control.
|
||||
*/
|
||||
project_id: string
|
||||
correlation_id: string
|
||||
}
|
||||
|
||||
export type AgentOrchestrationPlan = {
|
||||
intent_id: string
|
||||
template_hint: string
|
||||
objective: string
|
||||
sub_mode?: AgentSubMode
|
||||
risk_level: 'low' | 'medium' | 'high'
|
||||
budget: AgentBudget
|
||||
user_context: AgentUserContext
|
||||
billing_context?: AgentBillingContext
|
||||
agent_runtime?: AgentAgentRuntime
|
||||
agents: AgentAgentPlan[]
|
||||
constraints: AgentConstraints
|
||||
metadata: AgentOrchestrationMetadata
|
||||
}
|
||||
|
||||
export type AgentCreateDeploymentBody = {
|
||||
orchestration_plan: AgentOrchestrationPlan
|
||||
}
|
||||
|
||||
export type AgentCreateDeploymentResult = {
|
||||
deployment_id: string
|
||||
sub_mode?: AgentSubMode
|
||||
status: string
|
||||
agent_instances?: Array<{
|
||||
instance_id?: string
|
||||
role?: string
|
||||
phase?: string
|
||||
}>
|
||||
permission_manifest?: AgentPermissionManifest
|
||||
}
|
||||
|
||||
export type AgentPermissionManifest = {
|
||||
user_id?: string
|
||||
binding_scope?: string
|
||||
agent_role?: string
|
||||
target_agent_ref?: string
|
||||
resource_grants?: Array<{
|
||||
grant_id?: string
|
||||
resource_id?: string
|
||||
resource_type?: string
|
||||
resource_ref?: string
|
||||
allowed_actions?: string[]
|
||||
constraints?: Record<string, string>
|
||||
secret_ref?: string
|
||||
status?: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type AgentDeployment = {
|
||||
deployment_id: string
|
||||
// Client-facing mode (sub_agile | swarm) + Manager-judged display_status,
|
||||
// both populated by the backend list/detail responses.
|
||||
mode?: string
|
||||
display_status?: string
|
||||
sub_mode?: AgentSubMode
|
||||
status: string
|
||||
phase: string
|
||||
runtime_state?: string
|
||||
runtime_deployment_id?: string
|
||||
runtime_swarm_id?: string
|
||||
runtime_last_sync_at?: string
|
||||
failure_reason?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
permission_manifest?: AgentPermissionManifest
|
||||
orchestration_plan: {
|
||||
intent_id?: string
|
||||
template_hint?: string
|
||||
objective?: string
|
||||
sub_mode?: AgentSubMode
|
||||
risk_level?: string
|
||||
budget?: AgentBudget
|
||||
agents?: AgentAgentPlan[]
|
||||
constraints?: AgentConstraints
|
||||
metadata?: AgentOrchestrationMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export type AgentRuntimeDiagnostics = {
|
||||
deployment_id: string
|
||||
runtime_mode?: 'agent' | 'swarm' | string
|
||||
sub_mode?: AgentSubMode
|
||||
runtime_deployment_id?: string
|
||||
runtime_swarm_id?: string
|
||||
data_source?: string
|
||||
http_status?: number
|
||||
status?: string
|
||||
phase?: string
|
||||
progress?: unknown
|
||||
error_message?: string
|
||||
agents?: Array<Record<string, unknown>>
|
||||
artifacts?: Array<Record<string, unknown>>
|
||||
metrics?: Record<string, unknown>
|
||||
warnings?: string[]
|
||||
checked_at?: string
|
||||
}
|
||||
|
||||
export type AgentApprovalRequest = {
|
||||
approval_id: string
|
||||
user_id: number
|
||||
deployment_id?: string
|
||||
binding_scope?: string
|
||||
operation: string
|
||||
resource_id: string
|
||||
resource_type: string
|
||||
resource_scope?: string
|
||||
target_role: string
|
||||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||||
requires_credential: boolean
|
||||
credential_lease_id?: string
|
||||
status: 'pending' | 'approved' | 'rejected' | 'expired'
|
||||
requested_by?: string
|
||||
decided_by?: string
|
||||
request_reason?: string
|
||||
decision_reason?: string
|
||||
ttl_seconds: number
|
||||
expires_at: number
|
||||
decided_at?: number
|
||||
created_at?: number
|
||||
updated_at?: number
|
||||
credential_lease?: AgentCredentialLease
|
||||
}
|
||||
|
||||
export type AgentCredentialLease = {
|
||||
lease_id: string
|
||||
credential_ref: string
|
||||
approval_id: string
|
||||
user_id: number
|
||||
deployment_id?: string
|
||||
binding_scope?: string
|
||||
resource_id: string
|
||||
resource_type: string
|
||||
resource_scope?: string
|
||||
target_role: string
|
||||
status: 'active' | 'expired' | 'revoked'
|
||||
ttl_seconds: number
|
||||
expires_at: number
|
||||
revoked_at?: number
|
||||
created_at?: number
|
||||
updated_at?: number
|
||||
}
|
||||
|
||||
type ApiEnvelope<T> = { success: boolean; data?: T; message?: string }
|
||||
|
||||
export type GitSourceUsage = 'project' | 'sk' | 'combined'
|
||||
|
||||
export type GitSource = {
|
||||
id: number
|
||||
user_id: number
|
||||
tenant_id?: string
|
||||
name: string
|
||||
provider: string
|
||||
repo_url: string
|
||||
ref: string
|
||||
paths: string[]
|
||||
usage: GitSourceUsage | string
|
||||
status: string
|
||||
created_at?: number
|
||||
updated_at?: number
|
||||
}
|
||||
|
||||
export type GitSourcePayload = {
|
||||
tenant_id?: string
|
||||
name: string
|
||||
provider: string
|
||||
repo_url: string
|
||||
ref: string
|
||||
paths: string[]
|
||||
usage: GitSourceUsage
|
||||
}
|
||||
|
||||
// Platform-recommended role catalog shape. Mirrors backend
|
||||
// `AgentRoleTemplate` in controller/agent_role_template.go. The
|
||||
// six canonical roles come from docs/product-package §13.3.3 —
|
||||
// keys are stable identifiers, display strings can be translated.
|
||||
export type AgentRoleTemplate = {
|
||||
key: string
|
||||
display_name: string
|
||||
summary: string
|
||||
default_model: string
|
||||
default_permissions: string[]
|
||||
risk_level: 'low' | 'medium' | 'high'
|
||||
}
|
||||
|
||||
// Cached at module level — the canonical six-role catalog doesn't
|
||||
// change between page loads, so we avoid an extra request every
|
||||
// time the create-deployment sheet opens.
|
||||
let _roleTemplateCache: AgentRoleTemplate[] | null = null
|
||||
|
||||
export async function listAgentRoleTemplates(): Promise<AgentRoleTemplate[]> {
|
||||
if (_roleTemplateCache) return _roleTemplateCache
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentRoleTemplate[] }>>(
|
||||
'/api/agent/role-templates'
|
||||
)
|
||||
const items = res.data?.data?.items ?? []
|
||||
if (items.length > 0) {
|
||||
_roleTemplateCache = items
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
export async function listAgentDeployments(): Promise<AgentDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentDeployment[] }>>(
|
||||
'/api/agent/user/deployments'
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function listAgentDeploymentsQuiet(): Promise<AgentDeployment[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentDeployment[] }>>(
|
||||
'/api/agent/user/deployments',
|
||||
{
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
} as Record<string, unknown>
|
||||
)
|
||||
if (!res.data?.success) return []
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function createAgentDeployment(
|
||||
body: AgentCreateDeploymentBody
|
||||
): Promise<AgentCreateDeploymentResult> {
|
||||
const res = await api.post<ApiEnvelope<AgentCreateDeploymentResult>>(
|
||||
'/api/agent/user/deployments',
|
||||
body
|
||||
)
|
||||
const env = res.data
|
||||
if (!env?.success) {
|
||||
throw new Error(env?.message || 'Deployment request failed')
|
||||
}
|
||||
const data = env.data
|
||||
if (!data?.deployment_id) {
|
||||
throw new Error(env?.message || 'Invalid deployment response')
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getAgentDeploymentEvents(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agent/user/deployments/${deploymentId}/events`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgentDeploymentArtifacts(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agent/user/deployments/${deploymentId}/artifacts`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgentDeploymentSKSnapshots(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agent/user/deployments/${deploymentId}/sk-snapshots`)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function getAgentDeploymentTimeline(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{
|
||||
callbacks?: Array<Record<string, unknown>>
|
||||
artifacts?: Array<Record<string, unknown>>
|
||||
sk_snapshots?: Array<Record<string, unknown>>
|
||||
timeline?: Array<Record<string, unknown>>
|
||||
}>
|
||||
>(`/api/agent/user/deployments/${deploymentId}/timeline`)
|
||||
return (
|
||||
res.data?.data ?? {
|
||||
callbacks: [],
|
||||
artifacts: [],
|
||||
sk_snapshots: [],
|
||||
timeline: [],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function getAgentRuntimeDiagnostics(
|
||||
deploymentId: string
|
||||
): Promise<AgentRuntimeDiagnostics | null> {
|
||||
const res = await api.get<ApiEnvelope<AgentRuntimeDiagnostics>>(
|
||||
`/api/agent/user/deployments/${deploymentId}/runtime-diagnostics`,
|
||||
{
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
} as Record<string, unknown>
|
||||
)
|
||||
if (!res.data?.success) return null
|
||||
return res.data?.data ?? null
|
||||
}
|
||||
|
||||
export async function simulateAgentDeploymentEvents(
|
||||
deploymentId: string,
|
||||
events?: string[]
|
||||
): Promise<{ deployment_id: string; simulated: boolean; total: number }> {
|
||||
const res = await api.post<
|
||||
ApiEnvelope<{ deployment_id: string; simulated: boolean; total: number }>
|
||||
>(`/api/agent/user/deployments/${deploymentId}/simulate-events`, {
|
||||
events: events ?? [],
|
||||
})
|
||||
const env = res.data
|
||||
if (!env?.success || !env.data) {
|
||||
throw new Error(env?.message || 'simulateAgentDeploymentEvents failed')
|
||||
}
|
||||
return env.data
|
||||
}
|
||||
|
||||
export async function getAgentAuditLogs() {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>('/api/agent/audit-logs')
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function listAgentApprovals(params?: {
|
||||
status?: string
|
||||
deployment_id?: string
|
||||
}): Promise<AgentApprovalRequest[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentApprovalRequest[] }>>(
|
||||
'/api/agent/approvals',
|
||||
{ params }
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function approveAgentApproval(
|
||||
approvalId: string,
|
||||
reason?: string
|
||||
): Promise<AgentApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgentApprovalRequest>>(
|
||||
`/api/agent/approvals/${approvalId}/approve`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
throw new Error(res.data?.message || 'Approve request failed')
|
||||
}
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function rejectAgentApproval(
|
||||
approvalId: string,
|
||||
reason?: string
|
||||
): Promise<AgentApprovalRequest> {
|
||||
const res = await api.post<ApiEnvelope<AgentApprovalRequest>>(
|
||||
`/api/agent/approvals/${approvalId}/reject`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
throw new Error(res.data?.message || 'Reject request failed')
|
||||
}
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function listAgentCredentialLeases(params?: {
|
||||
status?: string
|
||||
deployment_id?: string
|
||||
}): Promise<AgentCredentialLease[]> {
|
||||
const res = await api.get<ApiEnvelope<{ items?: AgentCredentialLease[] }>>(
|
||||
'/api/agent/credential-leases',
|
||||
{ params }
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function revokeAgentCredentialLease(
|
||||
leaseId: string,
|
||||
reason?: string
|
||||
): Promise<AgentCredentialLease> {
|
||||
const res = await api.post<ApiEnvelope<AgentCredentialLease>>(
|
||||
`/api/agent/credential-leases/${leaseId}/revoke`,
|
||||
{ reason }
|
||||
)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
throw new Error(res.data?.message || 'Revoke lease failed')
|
||||
}
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function getAgentSnapshots(deploymentId: string) {
|
||||
const res = await api.get<
|
||||
ApiEnvelope<{ items?: Array<Record<string, unknown>> }>
|
||||
>(`/api/agent/deployments/${deploymentId}/sk-snapshots`, {
|
||||
skipBusinessError: true,
|
||||
skipErrorHandler: true,
|
||||
} as Record<string, unknown>)
|
||||
if (!res.data?.success) return []
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function listGitSources(): Promise<GitSource[]> {
|
||||
const res =
|
||||
await api.get<ApiEnvelope<{ items?: GitSource[] }>>('/api/git-sources/')
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
export async function createGitSource(
|
||||
body: GitSourcePayload
|
||||
): Promise<GitSource> {
|
||||
const res = await api.post<ApiEnvelope<GitSource>>('/api/git-sources/', body)
|
||||
if (!res.data?.success || !res.data.data) {
|
||||
throw new Error(res.data?.message || 'Create Git source failed')
|
||||
}
|
||||
return res.data.data
|
||||
}
|
||||
|
||||
export async function deleteGitSource(id: number): Promise<void> {
|
||||
const res = await api.delete<ApiEnvelope<{ deleted?: boolean }>>(
|
||||
`/api/git-sources/${id}`
|
||||
)
|
||||
if (!res.data?.success) {
|
||||
throw new Error(res.data?.message || 'Delete Git source failed')
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,34 +0,0 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
|
||||
type AgentHubProps = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const quickLinks = [{ title: 'Deployments', to: '/deployments' as const }]
|
||||
|
||||
export function AgentHub(props: AgentHubProps) {
|
||||
return (
|
||||
<div className='mx-auto w-full max-w-5xl p-6'>
|
||||
<div className='mb-5'>
|
||||
<h1 className='text-2xl font-semibold'>{props.title}</h1>
|
||||
<p className='text-muted-foreground mt-2 text-sm'>{props.description}</p>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-4 md:grid-cols-3'>
|
||||
{quickLinks.map((item) => (
|
||||
<Link
|
||||
key={item.title}
|
||||
to={item.to}
|
||||
className='bg-card hover:bg-accent/40 rounded-lg border p-4 transition-colors'
|
||||
>
|
||||
<div className='font-medium'>{item.title}</div>
|
||||
<div className='text-muted-foreground mt-1 text-xs'>
|
||||
Open {item.title.toLowerCase()} workspace
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -15,13 +15,26 @@ import {
|
||||
import { api } from '@/lib/api'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
listAgentDeployments,
|
||||
type AgentDeployment,
|
||||
} from '@/features/agent-console/api'
|
||||
import { WalletStatsCard } from '@/features/wallet/components/wallet-stats-card'
|
||||
import type { UserWalletData } from '@/features/wallet/types'
|
||||
|
||||
// Deployed template agents (new model).
|
||||
type AgentItem = {
|
||||
agent_id: string
|
||||
template_id: string
|
||||
subdomain: string
|
||||
status: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
async function listMyAgents(): Promise<AgentItem[]> {
|
||||
const res = await api.get<{ data?: { items?: AgentItem[] } }>(
|
||||
'/api/heicode/agents'
|
||||
)
|
||||
return res.data?.data?.items ?? []
|
||||
}
|
||||
|
||||
// Current user's OWN balance/usage (NOT the admin-global aggregate) — same
|
||||
// source the 模型与余额 page uses.
|
||||
async function getSelfUsage(): Promise<UserWalletData> {
|
||||
@@ -149,9 +162,10 @@ export function CockpitView() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const deploymentsQuery = useQuery({
|
||||
queryKey: ['cockpit', 'deployments'],
|
||||
queryFn: listAgentDeployments,
|
||||
queryKey: ['cockpit', 'agents'],
|
||||
queryFn: listMyAgents,
|
||||
refetchInterval: 30_000,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const selfQuery = useQuery({
|
||||
@@ -162,7 +176,7 @@ export function CockpitView() {
|
||||
})
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const list: AgentDeployment[] = deploymentsQuery.data ?? []
|
||||
const list: AgentItem[] = deploymentsQuery.data ?? []
|
||||
const counters: Record<StatusKey, number> = {
|
||||
running: 0,
|
||||
success: 0,
|
||||
@@ -170,7 +184,7 @@ export function CockpitView() {
|
||||
pending: 0,
|
||||
}
|
||||
list.forEach((d) => {
|
||||
counters[classifyStatus(d.display_status || d.status || '')]++
|
||||
counters[classifyStatus(d.status || '')]++
|
||||
})
|
||||
const total = list.length || 1
|
||||
const successRate = Math.round(
|
||||
@@ -254,7 +268,7 @@ export function CockpitView() {
|
||||
</h3>
|
||||
</div>
|
||||
<Button asChild size='sm' variant='ghost' className='gap-1'>
|
||||
<Link to='/deployments'>
|
||||
<Link to='/deploy-agent'>
|
||||
{t('Open Deployments')}
|
||||
<ArrowUpRight className='h-3.5 w-3.5' />
|
||||
</Link>
|
||||
@@ -276,47 +290,38 @@ export function CockpitView() {
|
||||
</div>
|
||||
) : (
|
||||
<ul className='space-y-2'>
|
||||
{recentDeployments.map((dep) => {
|
||||
const tenant = dep.orchestration_plan?.metadata?.tenant_id || '—'
|
||||
const objective =
|
||||
dep.orchestration_plan?.objective ||
|
||||
dep.orchestration_plan?.template_hint ||
|
||||
t('No objective')
|
||||
return (
|
||||
<li
|
||||
key={dep.deployment_id}
|
||||
className='group flex items-center justify-between gap-4 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-3 transition hover:border-primary/45'
|
||||
>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Rocket className='h-3.5 w-3.5 text-primary' />
|
||||
<span className='truncate font-mono text-xs'>
|
||||
{dep.deployment_id}
|
||||
</span>
|
||||
<PhasePill phase={dep.phase || dep.status} />
|
||||
</div>
|
||||
<p className='mt-1 truncate text-sm text-foreground/90'>
|
||||
{objective}
|
||||
</p>
|
||||
<p className='mt-0.5 truncate text-[11px] text-muted-foreground'>
|
||||
<Building2 className='mr-1 inline h-3 w-3' />
|
||||
{tenant} · {formatRelative(dep.updated_at || dep.created_at)} ago
|
||||
</p>
|
||||
{recentDeployments.map((dep) => (
|
||||
<li
|
||||
key={dep.agent_id}
|
||||
className='group flex items-center justify-between gap-4 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-3 transition hover:border-primary/45'
|
||||
>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Rocket className='h-3.5 w-3.5 text-primary' />
|
||||
<span className='truncate text-sm font-medium'>
|
||||
{dep.template_id}
|
||||
</span>
|
||||
<PhasePill phase={dep.status} />
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='shrink-0 opacity-70 group-hover:opacity-100'
|
||||
>
|
||||
<Link to='/deployments'>
|
||||
<ScrollText className='mr-1 h-3.5 w-3.5' />
|
||||
{t('Inspect')}
|
||||
</Link>
|
||||
</Button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
<p className='mt-0.5 truncate text-[11px] text-muted-foreground'>
|
||||
<Building2 className='mr-1 inline h-3 w-3' />
|
||||
{dep.subdomain || '—'} ·{' '}
|
||||
{formatRelative(dep.updated_at || dep.created_at)} ago
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='shrink-0 opacity-70 group-hover:opacity-100'
|
||||
>
|
||||
<Link to='/deploy-agent'>
|
||||
<ScrollText className='mr-1 h-3.5 w-3.5' />
|
||||
{t('Inspect')}
|
||||
</Link>
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -1,519 +0,0 @@
|
||||
/**
|
||||
* Task Card route — implements docs/product-package/10 §"任务卡" + 11 §3
|
||||
* 任务卡 wireframe, backed by mcp-server §6 HeicodeTask object:
|
||||
*
|
||||
* - status = "configuring" → render the open follow-up questions
|
||||
* (each option becomes a clickable chip; clicking POSTs answer to
|
||||
* /api/user/tasks/$id/answer and the backend state machine advances).
|
||||
* - status = "running" / etc → render task.card with the four blocks
|
||||
* the spec mandates: 目标 / 第一版范围 / 自动生成 / 待确认上下文.
|
||||
*
|
||||
* Forbidden per §10 高级展开: no JSON editor, no permission manifest, no
|
||||
* resource_grant editor. Only the user-facing summary fields.
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { Link, getRouteApi } from '@tanstack/react-router'
|
||||
import {
|
||||
ArrowLeft,
|
||||
CheckCircle2,
|
||||
CircleDashed,
|
||||
GitBranch,
|
||||
ListChecks,
|
||||
MessageSquare,
|
||||
PencilLine,
|
||||
PlayCircle,
|
||||
Rocket,
|
||||
ScrollText,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Wallet,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
answerHeicodeTask,
|
||||
createDeploymentDraftFromHeicodeTask,
|
||||
getHeicodeTask,
|
||||
type HeicodeFollowup,
|
||||
type HeicodeManagerAction,
|
||||
type HeicodeTask,
|
||||
type HeicodeTaskStatus,
|
||||
} from '@/lib/heicode-mcp'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import {
|
||||
createAgentDeployment,
|
||||
type AgentOrchestrationPlan,
|
||||
} from '@/features/agent-console/api'
|
||||
|
||||
const route = getRouteApi('/_authenticated/tasks/$id')
|
||||
|
||||
type StatusKey = 'running' | 'success' | 'failed' | 'pending'
|
||||
|
||||
const STATUS_MAP: Record<HeicodeTaskStatus | string, StatusKey> = {
|
||||
draft: 'pending',
|
||||
configuring: 'pending',
|
||||
awaiting_approval: 'pending',
|
||||
running: 'running',
|
||||
paused: 'pending',
|
||||
completed: 'success',
|
||||
failed: 'failed',
|
||||
}
|
||||
|
||||
function classifyStatus(s: string): StatusKey {
|
||||
return STATUS_MAP[s] ?? 'pending'
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: HeicodeTaskStatus | string }) {
|
||||
const k = classifyStatus(status)
|
||||
const map: Record<StatusKey, { cls: string; Icon: typeof PlayCircle }> = {
|
||||
running: {
|
||||
cls: 'bg-[color-mix(in_oklch,var(--primary)_22%,transparent)] text-primary ring-primary/40',
|
||||
Icon: PlayCircle,
|
||||
},
|
||||
success: {
|
||||
cls: 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30',
|
||||
Icon: CheckCircle2,
|
||||
},
|
||||
failed: {
|
||||
cls: 'bg-rose-500/15 text-rose-400 ring-rose-500/30',
|
||||
Icon: XCircle,
|
||||
},
|
||||
pending: {
|
||||
cls: 'bg-amber-500/15 text-amber-400 ring-amber-500/30',
|
||||
Icon: CircleDashed,
|
||||
},
|
||||
}
|
||||
const p = map[k]
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold tracking-[0.12em] uppercase ring-1 ring-inset',
|
||||
p.cls
|
||||
)}
|
||||
>
|
||||
<p.Icon className='h-3 w-3' />
|
||||
{status}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function readScopeArray(card: HeicodeTask['card'], key: string): string[] {
|
||||
if (!card) return []
|
||||
const v = (card as Record<string, unknown>)[key]
|
||||
if (Array.isArray(v))
|
||||
return v.filter((x): x is string => typeof x === 'string')
|
||||
if (typeof v === 'string' && v.trim()) return [v]
|
||||
return []
|
||||
}
|
||||
|
||||
function readManagerActions(card: HeicodeTask['card']): HeicodeManagerAction[] {
|
||||
if (!card) return []
|
||||
const v = (card as Record<string, unknown>).manager_actions
|
||||
if (!Array.isArray(v)) return []
|
||||
return v
|
||||
.filter(
|
||||
(x): x is HeicodeManagerAction =>
|
||||
typeof x === 'object' &&
|
||||
x != null &&
|
||||
typeof (x as Record<string, unknown>).label === 'string' &&
|
||||
typeof (x as Record<string, unknown>).deeplink === 'string'
|
||||
)
|
||||
.slice(0, 6)
|
||||
}
|
||||
|
||||
/** Resolve helper icon for a manager action by deeplink keyword. */
|
||||
function iconForAction(deeplink: string): typeof GitBranch {
|
||||
const k = deeplink.toLowerCase()
|
||||
if (
|
||||
k.includes('resource') ||
|
||||
k.includes('preparation') ||
|
||||
k.includes('sk-source')
|
||||
)
|
||||
return GitBranch
|
||||
if (k.includes('audit')) return ShieldCheck
|
||||
if (k.includes('wallet') || k.includes('budget')) return Wallet
|
||||
if (k.includes('event') || k.includes('activity')) return ScrollText
|
||||
return Rocket
|
||||
}
|
||||
|
||||
/** Manager-side route normalization. Legacy deep-links from older mcp-server
|
||||
* payloads (resources / team / audit) now collapse to the live task overview;
|
||||
* wallet keeps its own route. */
|
||||
function normalizeDeeplink(deeplink: string): string {
|
||||
return deeplink
|
||||
.replace(/^\/manager\/resources/i, '/deployments')
|
||||
.replace(/^\/manager\/team/i, '/deployments')
|
||||
.replace(/^\/manager\/audit/i, '/deployments')
|
||||
.replace(/^\/manager\/wallet/i, '/wallet')
|
||||
.replace(/^\/manager\//i, '/')
|
||||
}
|
||||
|
||||
/** Collect open follow-ups from the most recent Heicode thread entry. */
|
||||
function collectOpenFollowups(task: HeicodeTask): HeicodeFollowup[] {
|
||||
for (let i = task.thread.length - 1; i >= 0; i--) {
|
||||
const entry = task.thread[i]!
|
||||
if (
|
||||
entry.kind === 'heicode' &&
|
||||
entry.followups &&
|
||||
entry.followups.length > 0
|
||||
) {
|
||||
return entry.followups
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function TaskCardView() {
|
||||
const { t } = useTranslation()
|
||||
const { id } = route.useParams()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { data: task, isLoading } = useQuery({
|
||||
queryKey: ['heicode', 'task', id],
|
||||
queryFn: () => getHeicodeTask(id),
|
||||
refetchInterval: (q) => {
|
||||
const status = (q.state.data as HeicodeTask | undefined)?.status
|
||||
if (status === 'completed' || status === 'failed') return false
|
||||
if (status === 'running' || status === 'awaiting_approval') return 3_000
|
||||
return 15_000
|
||||
},
|
||||
})
|
||||
|
||||
const answerMutation = useMutation({
|
||||
mutationFn: ({ qid, oid }: { qid: string; oid: string }) =>
|
||||
answerHeicodeTask(id, qid, oid),
|
||||
onSuccess: (updated) => {
|
||||
queryClient.setQueryData(['heicode', 'task', id], updated)
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['heicode', 'tasks', 'recent'],
|
||||
})
|
||||
if (updated.status === 'running') {
|
||||
toast.success(
|
||||
t(
|
||||
'All follow-ups answered. Heicode generated the recommendation summary.'
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : t('Failed to answer follow-up')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const deploymentMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!task) throw new Error('Task not found')
|
||||
const draft = await createDeploymentDraftFromHeicodeTask(task, {
|
||||
sub_mode: 'agile',
|
||||
binding_scope: `task-${task.id}`,
|
||||
role_templates: ['backend'],
|
||||
default_model_id: 'claude-sonnet-4-6',
|
||||
})
|
||||
return createAgentDeployment({
|
||||
orchestration_plan:
|
||||
draft.orchestration_plan as unknown as AgentOrchestrationPlan,
|
||||
})
|
||||
},
|
||||
onSuccess: (deployment) => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['agent', 'deployments'] })
|
||||
toast.success(
|
||||
t('Manager deployment created', {
|
||||
deployment_id: deployment.deployment_id,
|
||||
}) as string
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : t('Deployment request failed')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='mx-auto max-w-3xl space-y-4 p-6'>
|
||||
<Skeleton className='h-7 w-40' />
|
||||
<Skeleton className='h-48 w-full rounded-2xl' />
|
||||
<Skeleton className='h-32 w-full rounded-2xl' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
return (
|
||||
<div className='mx-auto max-w-3xl space-y-4 p-6'>
|
||||
<Button asChild variant='ghost' size='sm' className='gap-1'>
|
||||
<Link to='/dashboard/$section' params={{ section: 'overview' }}>
|
||||
<ArrowLeft className='h-4 w-4' />
|
||||
{t('Back to home')}
|
||||
</Link>
|
||||
</Button>
|
||||
<div className='rounded-2xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] p-10 text-center'>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t(
|
||||
'Task not found. It may have been removed or was never created.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const openFollowups = collectOpenFollowups(task)
|
||||
// Card shape per live mcp-server smoke test (§6.4 returns):
|
||||
// { goal: string, scope: string[], generated_artifacts: string[],
|
||||
// manager_actions: Array<{label, deeplink}> }
|
||||
const objective =
|
||||
(task.card &&
|
||||
((task.card as Record<string, unknown>).goal as string | undefined)) ||
|
||||
task.name ||
|
||||
task.intent ||
|
||||
t('No objective')
|
||||
const firstVersionScope =
|
||||
readScopeArray(task.card, 'scope').length > 0
|
||||
? readScopeArray(task.card, 'scope')
|
||||
: [task.intent || objective]
|
||||
const autoGenerated =
|
||||
readScopeArray(task.card, 'generated_artifacts').length > 0
|
||||
? readScopeArray(task.card, 'generated_artifacts')
|
||||
: [
|
||||
t('Product brief'),
|
||||
t('Prototype description'),
|
||||
t('Development tasks'),
|
||||
t('Check list'),
|
||||
t('Deployment steps'),
|
||||
]
|
||||
const managerActions = readManagerActions(task.card)
|
||||
const pendingContextLine = t(
|
||||
'Code, docs, cloud resources and high-risk approval rules. Connect them in the preparation checklist.'
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='mx-auto max-w-4xl space-y-5 p-6'>
|
||||
<header className='flex items-center justify-between gap-3'>
|
||||
<Button asChild variant='ghost' size='sm' className='gap-1'>
|
||||
<Link to='/dashboard/$section' params={{ section: 'overview' }}>
|
||||
<ArrowLeft className='h-4 w-4' />
|
||||
{t('Back to home')}
|
||||
</Link>
|
||||
</Button>
|
||||
<span className='text-muted-foreground font-mono text-[11px]'>
|
||||
{task.id}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
{/* ── Follow-up questions (only while status=configuring) ─────────── */}
|
||||
{task.status === 'configuring' && openFollowups.length > 0 && (
|
||||
<section
|
||||
className='overflow-hidden rounded-3xl border p-5 sm:p-6'
|
||||
style={{
|
||||
borderColor: 'rgba(123,107,227,0.28)',
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, rgba(123,107,227,0.07) 0%, rgba(123,107,227,0.00) 60%)',
|
||||
}}
|
||||
>
|
||||
<div className='flex items-start gap-3'>
|
||||
<span
|
||||
className='inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-2xl text-white'
|
||||
style={{ backgroundImage: 'var(--gradient-brand)' }}
|
||||
>
|
||||
<MessageSquare className='h-4 w-4' />
|
||||
</span>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
|
||||
{t('Heicode is asking')}
|
||||
</p>
|
||||
<p className='text-foreground mt-1 text-sm'>
|
||||
{task.status_caption ||
|
||||
t(
|
||||
'Answer a few questions so Heicode can draft the right plan.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol className='mt-5 space-y-4'>
|
||||
{openFollowups.map((q) => (
|
||||
<li
|
||||
key={q.id}
|
||||
className='bg-background/40 rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] p-4'
|
||||
>
|
||||
<p className='text-sm font-medium'>{q.question}</p>
|
||||
<div className='mt-3 flex flex-wrap gap-2'>
|
||||
{q.options.map((opt) => (
|
||||
<Button
|
||||
key={opt.id}
|
||||
type='button'
|
||||
size='sm'
|
||||
variant='outline'
|
||||
disabled={answerMutation.isPending}
|
||||
onClick={() =>
|
||||
answerMutation.mutate({ qid: q.id, oid: opt.id })
|
||||
}
|
||||
className={cn(
|
||||
'rounded-xl text-xs',
|
||||
opt.risk === 'high-risk' &&
|
||||
'border-rose-500/40 text-rose-300 hover:border-rose-500/70'
|
||||
)}
|
||||
>
|
||||
{opt.label}
|
||||
{opt.risk === 'high-risk' && (
|
||||
<span className='ms-1 rounded-full bg-rose-500/15 px-1.5 text-[9px] tracking-wider text-rose-300 uppercase'>
|
||||
{t('high-risk')}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* ── Task card (objective / scope / auto / pending) ───────────── */}
|
||||
<section
|
||||
className='overflow-hidden rounded-3xl border p-6 sm:p-8'
|
||||
style={{
|
||||
borderColor: 'rgba(123,107,227,0.28)',
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at 0% 0%, rgba(184,136,229,0.10), transparent 60%), linear-gradient(180deg, rgba(123,107,227,0.04) 0%, rgba(123,107,227,0.00) 60%)',
|
||||
}}
|
||||
>
|
||||
<div className='flex items-start justify-between gap-4'>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
|
||||
{t('Task card')}
|
||||
</p>
|
||||
<h1 className='mt-2 text-2xl font-semibold tracking-tight sm:text-3xl'>
|
||||
{objective}
|
||||
</h1>
|
||||
{task.intent && task.intent !== objective && (
|
||||
<p className='text-muted-foreground mt-2 text-sm'>
|
||||
{task.intent}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<StatusBadge status={task.status} />
|
||||
</div>
|
||||
|
||||
<div className='mt-6 grid gap-5 md:grid-cols-2'>
|
||||
<div className='bg-background/40 rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] p-4'>
|
||||
<p className='flex items-center gap-1.5 text-sm font-semibold'>
|
||||
<ListChecks className='text-primary h-4 w-4' />
|
||||
{t('First-version scope')}
|
||||
</p>
|
||||
<ul className='text-muted-foreground mt-2 space-y-1.5 text-sm'>
|
||||
{firstVersionScope.map((line, i) => (
|
||||
<li key={i} className='flex items-start gap-2'>
|
||||
<span className='bg-primary mt-1.5 inline-block h-1 w-1 shrink-0 rounded-full' />
|
||||
<span>{line}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className='bg-background/40 rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] p-4'>
|
||||
<p className='flex items-center gap-1.5 text-sm font-semibold'>
|
||||
<Sparkles className='text-primary h-4 w-4' />
|
||||
{t('Heicode auto-generates')}
|
||||
</p>
|
||||
<p className='text-muted-foreground mt-2 text-sm'>
|
||||
{autoGenerated.join(' / ')}
|
||||
</p>
|
||||
<p className='text-muted-foreground mt-2 text-[11px]'>
|
||||
{t(
|
||||
'These artifacts appear inside the desktop client as the task progresses.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-background/30 mt-5 rounded-2xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] p-4'>
|
||||
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.14em] uppercase'>
|
||||
{t('Needs Manager assistance')}
|
||||
</p>
|
||||
<div className='mt-3 flex flex-wrap gap-2'>
|
||||
{managerActions.length > 0 ? (
|
||||
managerActions.map((act) => {
|
||||
const Icon = iconForAction(act.deeplink)
|
||||
const to = normalizeDeeplink(act.deeplink).split('?')[0]
|
||||
return (
|
||||
<Button
|
||||
key={`${act.label}-${act.deeplink}`}
|
||||
asChild
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='rounded-xl'
|
||||
>
|
||||
<a href={to}>
|
||||
<Icon className='mr-1 h-3.5 w-3.5' />
|
||||
{act.label}
|
||||
</a>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='rounded-xl'
|
||||
>
|
||||
<Link to='/wallet'>
|
||||
<Wallet className='mr-1 h-3.5 w-3.5' />
|
||||
{t('Budget & usage')}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className='mt-6 flex flex-wrap items-center justify-between gap-3 border-t border-dashed border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] pt-4'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='text-muted-foreground gap-1'
|
||||
disabled
|
||||
title={t('Editing the objective happens in the desktop client.')}
|
||||
>
|
||||
<PencilLine className='h-3.5 w-3.5' />
|
||||
{t('Edit objective in desktop client')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='gap-1 rounded-xl'
|
||||
disabled={
|
||||
deploymentMutation.isPending ||
|
||||
task.status === 'failed' ||
|
||||
task.status === 'draft' ||
|
||||
task.status === 'configuring'
|
||||
}
|
||||
onClick={() => deploymentMutation.mutate()}
|
||||
>
|
||||
<Rocket className='h-3.5 w-3.5' />
|
||||
{deploymentMutation.isPending
|
||||
? t('Creating deployment')
|
||||
: t('Create Manager deployment')}
|
||||
</Button>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<section 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'>
|
||||
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
|
||||
{t('Pending context')}
|
||||
</p>
|
||||
<p className='text-muted-foreground mt-2 text-sm'>
|
||||
{pendingContextLine}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -40,7 +40,6 @@ const DEFAULT_SIDEBAR_MODULES: SidebarModulesAdminConfig = {
|
||||
const URL_TO_CONFIG_MAP: Record<string, { section: string; module: string }> = {
|
||||
'/dashboard': { section: 'cockpit', module: 'overview' },
|
||||
'/dashboard/overview': { section: 'cockpit', module: 'overview' },
|
||||
'/deployments': { section: 'cockpit', module: 'deployments' },
|
||||
'/users': { section: 'admin', module: 'tenants' },
|
||||
'/system-settings/general': { section: 'admin', module: 'settings' },
|
||||
'/system-settings': { section: 'admin', module: 'settings' },
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Download,
|
||||
GitBranch,
|
||||
LayoutDashboard,
|
||||
Rocket,
|
||||
Settings,
|
||||
Smartphone,
|
||||
UserCog,
|
||||
@@ -58,11 +57,6 @@ export function useSidebarData(): SidebarData {
|
||||
url: '/deploy-agent',
|
||||
icon: Bot,
|
||||
},
|
||||
{
|
||||
title: t('Agent runs'),
|
||||
url: '/deployments',
|
||||
icon: Rocket,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -63,8 +63,5 @@ export function useTopNavLinks(): TopNavLink[] {
|
||||
if (modules?.overview !== false) {
|
||||
links.push({ title: t('Overview'), href: '/dashboard' })
|
||||
}
|
||||
if (modules?.deployments !== false) {
|
||||
links.push({ title: t('Deployments'), href: '/deployments' })
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
-43
@@ -45,13 +45,11 @@ import { Route as AuthenticatedModelsIndexRouteImport } from './routes/_authenti
|
||||
import { Route as AuthenticatedKeysIndexRouteImport } from './routes/_authenticated/keys/index'
|
||||
import { Route as AuthenticatedDevicesIndexRouteImport } from './routes/_authenticated/devices/index'
|
||||
import { Route as AuthenticatedDesktopClientIndexRouteImport } from './routes/_authenticated/desktop-client/index'
|
||||
import { Route as AuthenticatedDeploymentsIndexRouteImport } from './routes/_authenticated/deployments/index'
|
||||
import { Route as AuthenticatedDeployAgentIndexRouteImport } from './routes/_authenticated/deploy-agent/index'
|
||||
import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index'
|
||||
import { Route as AuthenticatedChannelsIndexRouteImport } from './routes/_authenticated/channels/index'
|
||||
import { Route as AuthenticatedAvailableModelsIndexRouteImport } from './routes/_authenticated/available-models/index'
|
||||
import { Route as AuthenticatedUsageLogsSectionRouteImport } from './routes/_authenticated/usage-logs/$section'
|
||||
import { Route as AuthenticatedTasksIdRouteImport } from './routes/_authenticated/tasks/$id'
|
||||
import { Route as AuthenticatedModelsSectionRouteImport } from './routes/_authenticated/models/$section'
|
||||
import { Route as AuthenticatedErrorsErrorRouteImport } from './routes/_authenticated/errors/$error'
|
||||
import { Route as AuthenticatedDashboardSectionRouteImport } from './routes/_authenticated/dashboard/$section'
|
||||
@@ -262,12 +260,6 @@ const AuthenticatedDesktopClientIndexRoute =
|
||||
path: '/desktop-client/',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedDeploymentsIndexRoute =
|
||||
AuthenticatedDeploymentsIndexRouteImport.update({
|
||||
id: '/deployments/',
|
||||
path: '/deployments/',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedDeployAgentIndexRoute =
|
||||
AuthenticatedDeployAgentIndexRouteImport.update({
|
||||
id: '/deploy-agent/',
|
||||
@@ -298,11 +290,6 @@ const AuthenticatedUsageLogsSectionRoute =
|
||||
path: '/usage-logs/$section',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedTasksIdRoute = AuthenticatedTasksIdRouteImport.update({
|
||||
id: '/tasks/$id',
|
||||
path: '/tasks/$id',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedModelsSectionRoute =
|
||||
AuthenticatedModelsSectionRouteImport.update({
|
||||
id: '/models/$section',
|
||||
@@ -442,13 +429,11 @@ export interface FileRoutesByFullPath {
|
||||
'/dashboard/$section': typeof AuthenticatedDashboardSectionRoute
|
||||
'/errors/$error': typeof AuthenticatedErrorsErrorRoute
|
||||
'/models/$section': typeof AuthenticatedModelsSectionRoute
|
||||
'/tasks/$id': typeof AuthenticatedTasksIdRoute
|
||||
'/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute
|
||||
'/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
||||
'/channels/': typeof AuthenticatedChannelsIndexRoute
|
||||
'/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
||||
'/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute
|
||||
'/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
||||
'/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
||||
'/devices/': typeof AuthenticatedDevicesIndexRoute
|
||||
'/keys/': typeof AuthenticatedKeysIndexRoute
|
||||
@@ -503,13 +488,11 @@ export interface FileRoutesByTo {
|
||||
'/dashboard/$section': typeof AuthenticatedDashboardSectionRoute
|
||||
'/errors/$error': typeof AuthenticatedErrorsErrorRoute
|
||||
'/models/$section': typeof AuthenticatedModelsSectionRoute
|
||||
'/tasks/$id': typeof AuthenticatedTasksIdRoute
|
||||
'/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute
|
||||
'/available-models': typeof AuthenticatedAvailableModelsIndexRoute
|
||||
'/channels': typeof AuthenticatedChannelsIndexRoute
|
||||
'/dashboard': typeof AuthenticatedDashboardIndexRoute
|
||||
'/deploy-agent': typeof AuthenticatedDeployAgentIndexRoute
|
||||
'/deployments': typeof AuthenticatedDeploymentsIndexRoute
|
||||
'/desktop-client': typeof AuthenticatedDesktopClientIndexRoute
|
||||
'/devices': typeof AuthenticatedDevicesIndexRoute
|
||||
'/keys': typeof AuthenticatedKeysIndexRoute
|
||||
@@ -568,13 +551,11 @@ export interface FileRoutesById {
|
||||
'/_authenticated/dashboard/$section': typeof AuthenticatedDashboardSectionRoute
|
||||
'/_authenticated/errors/$error': typeof AuthenticatedErrorsErrorRoute
|
||||
'/_authenticated/models/$section': typeof AuthenticatedModelsSectionRoute
|
||||
'/_authenticated/tasks/$id': typeof AuthenticatedTasksIdRoute
|
||||
'/_authenticated/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute
|
||||
'/_authenticated/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
||||
'/_authenticated/channels/': typeof AuthenticatedChannelsIndexRoute
|
||||
'/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
||||
'/_authenticated/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute
|
||||
'/_authenticated/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
||||
'/_authenticated/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
||||
'/_authenticated/devices/': typeof AuthenticatedDevicesIndexRoute
|
||||
'/_authenticated/keys/': typeof AuthenticatedKeysIndexRoute
|
||||
@@ -632,13 +613,11 @@ export interface FileRouteTypes {
|
||||
| '/dashboard/$section'
|
||||
| '/errors/$error'
|
||||
| '/models/$section'
|
||||
| '/tasks/$id'
|
||||
| '/usage-logs/$section'
|
||||
| '/available-models/'
|
||||
| '/channels/'
|
||||
| '/dashboard/'
|
||||
| '/deploy-agent/'
|
||||
| '/deployments/'
|
||||
| '/desktop-client/'
|
||||
| '/devices/'
|
||||
| '/keys/'
|
||||
@@ -693,13 +672,11 @@ export interface FileRouteTypes {
|
||||
| '/dashboard/$section'
|
||||
| '/errors/$error'
|
||||
| '/models/$section'
|
||||
| '/tasks/$id'
|
||||
| '/usage-logs/$section'
|
||||
| '/available-models'
|
||||
| '/channels'
|
||||
| '/dashboard'
|
||||
| '/deploy-agent'
|
||||
| '/deployments'
|
||||
| '/desktop-client'
|
||||
| '/devices'
|
||||
| '/keys'
|
||||
@@ -757,13 +734,11 @@ export interface FileRouteTypes {
|
||||
| '/_authenticated/dashboard/$section'
|
||||
| '/_authenticated/errors/$error'
|
||||
| '/_authenticated/models/$section'
|
||||
| '/_authenticated/tasks/$id'
|
||||
| '/_authenticated/usage-logs/$section'
|
||||
| '/_authenticated/available-models/'
|
||||
| '/_authenticated/channels/'
|
||||
| '/_authenticated/dashboard/'
|
||||
| '/_authenticated/deploy-agent/'
|
||||
| '/_authenticated/deployments/'
|
||||
| '/_authenticated/desktop-client/'
|
||||
| '/_authenticated/devices/'
|
||||
| '/_authenticated/keys/'
|
||||
@@ -1066,13 +1041,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedDesktopClientIndexRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/deployments/': {
|
||||
id: '/_authenticated/deployments/'
|
||||
path: '/deployments'
|
||||
fullPath: '/deployments/'
|
||||
preLoaderRoute: typeof AuthenticatedDeploymentsIndexRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/deploy-agent/': {
|
||||
id: '/_authenticated/deploy-agent/'
|
||||
path: '/deploy-agent'
|
||||
@@ -1108,13 +1076,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedUsageLogsSectionRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/tasks/$id': {
|
||||
id: '/_authenticated/tasks/$id'
|
||||
path: '/tasks/$id'
|
||||
fullPath: '/tasks/$id'
|
||||
preLoaderRoute: typeof AuthenticatedTasksIdRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/models/$section': {
|
||||
id: '/_authenticated/models/$section'
|
||||
path: '/models/$section'
|
||||
@@ -1339,13 +1300,11 @@ interface AuthenticatedRouteRouteChildren {
|
||||
AuthenticatedDashboardSectionRoute: typeof AuthenticatedDashboardSectionRoute
|
||||
AuthenticatedErrorsErrorRoute: typeof AuthenticatedErrorsErrorRoute
|
||||
AuthenticatedModelsSectionRoute: typeof AuthenticatedModelsSectionRoute
|
||||
AuthenticatedTasksIdRoute: typeof AuthenticatedTasksIdRoute
|
||||
AuthenticatedUsageLogsSectionRoute: typeof AuthenticatedUsageLogsSectionRoute
|
||||
AuthenticatedAvailableModelsIndexRoute: typeof AuthenticatedAvailableModelsIndexRoute
|
||||
AuthenticatedChannelsIndexRoute: typeof AuthenticatedChannelsIndexRoute
|
||||
AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute
|
||||
AuthenticatedDeployAgentIndexRoute: typeof AuthenticatedDeployAgentIndexRoute
|
||||
AuthenticatedDeploymentsIndexRoute: typeof AuthenticatedDeploymentsIndexRoute
|
||||
AuthenticatedDesktopClientIndexRoute: typeof AuthenticatedDesktopClientIndexRoute
|
||||
AuthenticatedDevicesIndexRoute: typeof AuthenticatedDevicesIndexRoute
|
||||
AuthenticatedKeysIndexRoute: typeof AuthenticatedKeysIndexRoute
|
||||
@@ -1368,14 +1327,12 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
||||
AuthenticatedDashboardSectionRoute: AuthenticatedDashboardSectionRoute,
|
||||
AuthenticatedErrorsErrorRoute: AuthenticatedErrorsErrorRoute,
|
||||
AuthenticatedModelsSectionRoute: AuthenticatedModelsSectionRoute,
|
||||
AuthenticatedTasksIdRoute: AuthenticatedTasksIdRoute,
|
||||
AuthenticatedUsageLogsSectionRoute: AuthenticatedUsageLogsSectionRoute,
|
||||
AuthenticatedAvailableModelsIndexRoute:
|
||||
AuthenticatedAvailableModelsIndexRoute,
|
||||
AuthenticatedChannelsIndexRoute: AuthenticatedChannelsIndexRoute,
|
||||
AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute,
|
||||
AuthenticatedDeployAgentIndexRoute: AuthenticatedDeployAgentIndexRoute,
|
||||
AuthenticatedDeploymentsIndexRoute: AuthenticatedDeploymentsIndexRoute,
|
||||
AuthenticatedDesktopClientIndexRoute: AuthenticatedDesktopClientIndexRoute,
|
||||
AuthenticatedDevicesIndexRoute: AuthenticatedDevicesIndexRoute,
|
||||
AuthenticatedKeysIndexRoute: AuthenticatedKeysIndexRoute,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AgentDeploymentsPage } from '@/features/agent-console/pages'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/deployments/')({
|
||||
component: AgentDeploymentsPage,
|
||||
})
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { TaskCardView } from '@/features/tasks/task-card-view'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/tasks/$id')({
|
||||
component: TaskCardView,
|
||||
})
|
||||
Reference in New Issue
Block a user