feat: 添加swarms团队编排功能并优化agent委派系统
- 引入AgentTeamOrchestrator支持多agent协同任务执行 - 增加第三方swarms库依赖并配置git协议替换以改善包管理 - 扩展DelegationManager支持团队任务调度和进度跟踪 - 实现中文bigram分词算法提升中文任务检索准确性 - 调整A2AClient和DelegationManager超时时间从30秒增至600秒 - 优化AgentRunResult状态判断逻辑增加有意义摘要检测 - 修改Dockerfile配置npm仓库镜像地址和git协议映射 - 更新CLI命令行接口支持网关端口配置传递 - 调整提供者超时配置机制增强请求稳定性 - 移除过时的support_group字段简化agent描述符结构 - 增强错误处理和进度事件报告机制改进用户体验
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import type { TokenResponse } from '@/types/auth';
|
||||
import { normalizePortalLocale, pickPortalText } from '@/lib/i18n/core';
|
||||
import { HttpError, callDeployControl, callInstanceApi, normalizeTokenResponse } from '@/lib/runtime-control';
|
||||
|
||||
function errorStatus(error: unknown): number {
|
||||
@ -18,6 +19,11 @@ function errorDetail(error: unknown): string {
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const locale = normalizePortalLocale(
|
||||
request.cookies.get('nanobot_locale')?.value ||
|
||||
request.headers.get('accept-language')
|
||||
);
|
||||
|
||||
try {
|
||||
const body = (await request.json()) as {
|
||||
username?: string;
|
||||
@ -27,7 +33,9 @@ export async function POST(request: NextRequest) {
|
||||
const password = body.password || '';
|
||||
|
||||
if (!username || !password) {
|
||||
return NextResponse.json({ detail: 'username and password are required' }, { status: 400 });
|
||||
return NextResponse.json({
|
||||
detail: pickPortalText(locale, '用户名和密码不能为空', 'Username and password are required'),
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const routing = await callDeployControl<{
|
||||
@ -44,7 +52,9 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json(normalizeTokenResponse(response, routing));
|
||||
} catch (error) {
|
||||
const status = errorStatus(error);
|
||||
const detail = status === 404 || status === 401 ? '用户名或密码错误' : errorDetail(error);
|
||||
const detail = status === 404 || status === 401
|
||||
? pickPortalText(locale, '用户名或密码错误', 'Incorrect username or password')
|
||||
: errorDetail(error);
|
||||
return NextResponse.json({ detail }, { status: status === 404 ? 401 : status });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import type { TokenResponse } from '@/types/auth';
|
||||
import { normalizePortalLocale, pickPortalText } from '@/lib/i18n/core';
|
||||
import { HttpError, REGISTER_REQUEST_TIMEOUT_MS, callAuthzService } from '@/lib/runtime-control';
|
||||
|
||||
function errorStatus(error: unknown): number {
|
||||
@ -18,6 +19,11 @@ function errorDetail(error: unknown): string {
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const locale = normalizePortalLocale(
|
||||
request.cookies.get('nanobot_locale')?.value ||
|
||||
request.headers.get('accept-language')
|
||||
);
|
||||
|
||||
try {
|
||||
const body = (await request.json()) as {
|
||||
username?: string;
|
||||
@ -29,7 +35,9 @@ export async function POST(request: NextRequest) {
|
||||
const password = body.password || '';
|
||||
|
||||
if (!username || !password) {
|
||||
return NextResponse.json({ detail: 'username and password are required' }, { status: 400 });
|
||||
return NextResponse.json({
|
||||
detail: pickPortalText(locale, '用户名和密码不能为空', 'Username and password are required'),
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const response = await callAuthzService<TokenResponse>('/portal/register', {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import './globals.css';
|
||||
import type { Metadata } from 'next';
|
||||
import { PortalI18nProvider } from '@/lib/i18n/provider';
|
||||
import { getServerPortalLocale } from '@/lib/i18n/server';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Boardware Agent Sandbox Auth Portal',
|
||||
description: 'Dedicated login and registration portal for Boardware Genius containers.',
|
||||
description: 'Boardware Agent Sandbox Auth Portal',
|
||||
icons: {
|
||||
icon: '/boardware-logo.jpg',
|
||||
},
|
||||
@ -14,9 +16,13 @@ export default function RootLayout({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const locale = getServerPortalLocale();
|
||||
|
||||
return (
|
||||
<html lang="zh-CN">
|
||||
<body>{children}</body>
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<PortalI18nProvider initialLocale={locale}>{children}</PortalI18nProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,9 +5,13 @@ import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
|
||||
import { buildFrontendHandoffUrl, login, withNext } from '@/lib/auth-client';
|
||||
import { pickPortalText } from '@/lib/i18n/core';
|
||||
import { usePortalI18n } from '@/lib/i18n/provider';
|
||||
|
||||
export default function LoginPage() {
|
||||
const { locale } = usePortalI18n();
|
||||
const searchParams = useSearchParams();
|
||||
const nextPath = searchParams?.get('next') || '/';
|
||||
|
||||
@ -25,7 +29,7 @@ export default function LoginPage() {
|
||||
const response = await login(username, password);
|
||||
window.location.replace(buildFrontendHandoffUrl(response, nextPath));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '登录失败,请稍后重试');
|
||||
setError(err instanceof Error ? err.message : pickPortalText(locale, '登录失败,请稍后重试', 'Sign-in failed. Please try again.'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -33,6 +37,9 @@ export default function LoginPage() {
|
||||
|
||||
return (
|
||||
<main className="portal-page">
|
||||
<div className="absolute right-5 top-5 z-10">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<section className="portal-shell">
|
||||
<div className="portal-brand">
|
||||
<div className="portal-logo-lockup">
|
||||
@ -47,47 +54,55 @@ export default function LoginPage() {
|
||||
<div className="portal-kicker">Auth Portal</div>
|
||||
<h1 className="portal-title">Boardware Agent Sandbox</h1>
|
||||
<p className="portal-copy">
|
||||
这个入口只负责鉴权。成功后会把你直接送到为你分配的专属实例 URL,后续前后端请求都留在那套容器里。
|
||||
{pickPortalText(
|
||||
locale,
|
||||
'这个入口只负责鉴权。成功后会把你直接送到为你分配的专属实例 URL,后续前后端请求都留在那套容器里。',
|
||||
'This portal only handles authentication. After sign-in, you are redirected to your dedicated runtime URL and all later requests stay inside that container.'
|
||||
)}
|
||||
</p>
|
||||
<div className="portal-notes">
|
||||
<div className="portal-note">
|
||||
<strong>容器边界</strong>
|
||||
登录注册先经过独立 auth portal,再跳到专属实例。一用户一套前后端容器不变。
|
||||
<strong>{pickPortalText(locale, '容器边界', 'Container boundary')}</strong>
|
||||
{pickPortalText(
|
||||
locale,
|
||||
'登录注册先经过独立 auth portal,再跳到专属实例。一用户一套前后端容器不变。',
|
||||
'Authentication happens in this standalone portal first, then the browser jumps into the dedicated runtime. Each user keeps an isolated frontend/backend container pair.'
|
||||
)}
|
||||
</div>
|
||||
<div className="portal-note">
|
||||
<strong>目标页面</strong>
|
||||
当前登录完成后将回到:<code>{nextPath}</code>
|
||||
<strong>{pickPortalText(locale, '目标页面', 'Target page')}</strong>
|
||||
{pickPortalText(locale, '当前登录完成后将回到:', 'After sign-in you will return to:')} <code>{nextPath}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="portal-panel">
|
||||
<div className="auth-card">
|
||||
<h1>登录</h1>
|
||||
<p>输入已有账号,认证完成后直接进入目标容器前端。</p>
|
||||
<h1>{pickPortalText(locale, '登录', 'Sign In')}</h1>
|
||||
<p>{pickPortalText(locale, '输入已有账号,认证完成后直接进入目标容器前端。', 'Use an existing account and continue straight into the target runtime UI.')}</p>
|
||||
|
||||
<form className="auth-form" onSubmit={handleSubmit}>
|
||||
<div className="field">
|
||||
<label htmlFor="username">用户名</label>
|
||||
<label htmlFor="username">{pickPortalText(locale, '用户名', 'Username')}</label>
|
||||
<input
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
autoComplete="username"
|
||||
placeholder="例如:bwgdi"
|
||||
placeholder={pickPortalText(locale, '例如:bwgdi', 'Example: bwgdi')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="password">密码</label>
|
||||
<label htmlFor="password">{pickPortalText(locale, '密码', 'Password')}</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
autoComplete="current-password"
|
||||
placeholder="输入密码"
|
||||
placeholder={pickPortalText(locale, '输入密码', 'Enter password')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -95,12 +110,14 @@ export default function LoginPage() {
|
||||
<div className="error-text">{error}</div>
|
||||
|
||||
<button className="primary-button" type="submit" disabled={loading}>
|
||||
{loading ? '登录中...' : '登录并进入容器'}
|
||||
{loading
|
||||
? pickPortalText(locale, '登录中...', 'Signing in...')
|
||||
: pickPortalText(locale, '登录并进入容器', 'Sign in and continue')}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="auth-footer">
|
||||
还没有账号? <Link href={withNext('/register', nextPath)}>去注册</Link>
|
||||
{pickPortalText(locale, '还没有账号?', "Don't have an account yet?")} <Link href={withNext('/register', nextPath)}>{pickPortalText(locale, '去注册', 'Create one')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,9 +5,13 @@ import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
|
||||
import { buildFrontendHandoffUrl, register, withNext } from '@/lib/auth-client';
|
||||
import { pickPortalText } from '@/lib/i18n/core';
|
||||
import { usePortalI18n } from '@/lib/i18n/provider';
|
||||
|
||||
export default function RegisterPage() {
|
||||
const { locale } = usePortalI18n();
|
||||
const searchParams = useSearchParams();
|
||||
const nextPath = searchParams?.get('next') || '/mcp';
|
||||
|
||||
@ -25,12 +29,12 @@ export default function RegisterPage() {
|
||||
|
||||
try {
|
||||
if (password !== confirmPassword) {
|
||||
throw new Error('两次输入的密码不一致');
|
||||
throw new Error(pickPortalText(locale, '两次输入的密码不一致', 'Passwords do not match'));
|
||||
}
|
||||
const response = await register(username, email, password);
|
||||
window.location.replace(buildFrontendHandoffUrl(response, nextPath));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '注册失败,请稍后重试');
|
||||
setError(err instanceof Error ? err.message : pickPortalText(locale, '注册失败,请稍后重试', 'Sign-up failed. Please try again.'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -38,6 +42,9 @@ export default function RegisterPage() {
|
||||
|
||||
return (
|
||||
<main className="portal-page">
|
||||
<div className="absolute right-5 top-5 z-10">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<section className="portal-shell">
|
||||
<div className="portal-brand">
|
||||
<div className="portal-logo-lockup">
|
||||
@ -52,72 +59,80 @@ export default function RegisterPage() {
|
||||
<div className="portal-kicker">Auth Portal</div>
|
||||
<h1 className="portal-title">Create Runtime</h1>
|
||||
<p className="portal-copy">
|
||||
注册不仅建立登录账号,还会触发专属实例创建和 backend 身份分配。认证完成后会直接进入你的专属 URL。
|
||||
{pickPortalText(
|
||||
locale,
|
||||
'注册不仅建立登录账号,还会触发专属实例创建和 backend 身份分配。认证完成后会直接进入你的专属 URL。',
|
||||
'Sign-up not only creates a login account, it also provisions your dedicated runtime and backend identity. After authentication, you go straight into your own URL.'
|
||||
)}
|
||||
</p>
|
||||
<div className="portal-notes">
|
||||
<div className="portal-note">
|
||||
<strong>注册结果</strong>
|
||||
AuthZ 会编排 deploy-control 创建实例,并完成 backend 身份初始化,auth portal 最后把你转交到该实例前端。
|
||||
<strong>{pickPortalText(locale, '注册结果', 'Provisioning result')}</strong>
|
||||
{pickPortalText(
|
||||
locale,
|
||||
'AuthZ 会编排 deploy-control 创建实例,并完成 backend 身份初始化,auth portal 最后把你转交到该实例前端。',
|
||||
'AuthZ coordinates deploy-control to create the runtime, initialize backend identity, and then the portal hands the browser over to that frontend.'
|
||||
)}
|
||||
</div>
|
||||
<div className="portal-note">
|
||||
<strong>目标页面</strong>
|
||||
当前注册完成后将回到:<code>{nextPath}</code>
|
||||
<strong>{pickPortalText(locale, '目标页面', 'Target page')}</strong>
|
||||
{pickPortalText(locale, '当前注册完成后将回到:', 'After sign-up you will return to:')} <code>{nextPath}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="portal-panel">
|
||||
<div className="auth-card">
|
||||
<h1>注册</h1>
|
||||
<p>为当前容器创建登录账号,并完成 backend 身份初始化。</p>
|
||||
<h1>{pickPortalText(locale, '注册', 'Sign Up')}</h1>
|
||||
<p>{pickPortalText(locale, '为当前容器创建登录账号,并完成 backend 身份初始化。', 'Create a login account for this runtime and initialize backend identity.')}</p>
|
||||
|
||||
<form className="auth-form" onSubmit={handleSubmit}>
|
||||
<div className="field">
|
||||
<label htmlFor="username">用户名</label>
|
||||
<label htmlFor="username">{pickPortalText(locale, '用户名', 'Username')}</label>
|
||||
<input
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
autoComplete="username"
|
||||
placeholder="例如:bwgdi"
|
||||
placeholder={pickPortalText(locale, '例如:bwgdi', 'Example: bwgdi')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="email">邮箱</label>
|
||||
<label htmlFor="email">{pickPortalText(locale, '邮箱', 'Email')}</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
autoComplete="email"
|
||||
placeholder="例如:steven@example.com"
|
||||
placeholder={pickPortalText(locale, '例如:steven@example.com', 'Example: steven@example.com')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="password">密码</label>
|
||||
<label htmlFor="password">{pickPortalText(locale, '密码', 'Password')}</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
autoComplete="new-password"
|
||||
placeholder="设置密码"
|
||||
placeholder={pickPortalText(locale, '设置密码', 'Set a password')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="confirmPassword">确认密码</label>
|
||||
<label htmlFor="confirmPassword">{pickPortalText(locale, '确认密码', 'Confirm password')}</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(event) => setConfirmPassword(event.target.value)}
|
||||
autoComplete="new-password"
|
||||
placeholder="再次输入密码"
|
||||
placeholder={pickPortalText(locale, '再次输入密码', 'Enter the password again')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -125,16 +140,18 @@ export default function RegisterPage() {
|
||||
<div className="error-text">{error}</div>
|
||||
|
||||
<button className="primary-button" type="submit" disabled={loading}>
|
||||
{loading ? '注册中...' : '注册并进入容器'}
|
||||
{loading
|
||||
? pickPortalText(locale, '注册中...', 'Creating account...')
|
||||
: pickPortalText(locale, '注册并进入容器', 'Create account and continue')}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="auth-footer">
|
||||
已有账号? <Link href={withNext('/login', nextPath)}>去登录</Link>
|
||||
{pickPortalText(locale, '已有账号?', 'Already have an account?')} <Link href={withNext('/login', nextPath)}>{pickPortalText(locale, '去登录', 'Sign in')}</Link>
|
||||
</div>
|
||||
|
||||
<div className="status-panel">
|
||||
Portal 会先调用部署机接口创建实例,再把浏览器跳到实例自己的 URL。
|
||||
{pickPortalText(locale, 'Portal 会先调用部署机接口创建实例,再把浏览器跳到实例自己的 URL。', 'The portal first calls the deployment controller to create the runtime, then redirects the browser into the instance URL.')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user