- 引入 DirectAnnouncementCallback 类型用于处理直连模式下的公告 - 在 DelegationManager 中添加 _direct_announcement_callback 属性和设置方法 - 实现 _notify_direct_announcement 方法用于在非总线模式下将公告回写到本地会话 - 在委托取消、完成和分组完成时添加对直连公告的通知逻辑 feat(web): 增加 WebSocket 广播器支持实时会话更新通知 - 创建 WebSocketBroadcaster 类用于跟踪认证的 WebSocket 连接并广播 JSON 事件 - 在应用启动时初始化 websocket_broadcaster 实例 - 实现连接注册、注销和消息广播功能 - 添加过期连接清理机制 feat(agent): 新增系统公告处理方法支持本地处理 - 在 AgentLoop 中添加 process_system_announcement 方法用于在无常驻 run() 场景下处理系统公告 - 创建 InboundMessage 并通过 _process_message 进行处理 feat(cron): 改进定时任务的会话路由解析和实时更新 - 添加 _resolve_cron_session_key 和 _infer_cron_route_from_session_key 辅助函数 - 在 cron 任务执行完成后通过 WebSocket 广播会话更新事件 - 在添加定时任务时自动推断目标会话的渠道和聊天 ID refactor: 项目名称从 Boardware Genius 统一改为 Boardware Agent Sandbox - 更新前端页面标题和描述文本中的产品名称 - 添加新的品牌 Logo 图片资源 - 在前端布局中使用新的 Logo 显示 - 更新授权门户中的品牌信息和 Logo 显示 feat(frontend): 添加会话更新事件监听实现消息自动刷新 - 定义 SessionUpdatedEvent 类型接口 - 在 ChatPage 中添加会话更新事件的处理逻辑 - 当收到会话更新事件时自动重新加载会话列表和当前会话消息 feat(api): 扩展定时任务 API 支持会话键参数 - 在 addCronJob API 参数中添加 session_key 字段 - 更新前端 Cron 页面的表单处理以传递当前会话键
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
'use client';
|
||
|
||
import Image from 'next/image';
|
||
import Link from 'next/link';
|
||
import { useSearchParams } from 'next/navigation';
|
||
import { useState } from 'react';
|
||
|
||
import { buildFrontendHandoffUrl, login, withNext } from '@/lib/auth-client';
|
||
|
||
export default function LoginPage() {
|
||
const searchParams = useSearchParams();
|
||
const nextPath = searchParams?.get('next') || '/';
|
||
|
||
const [username, setUsername] = useState('');
|
||
const [password, setPassword] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState('');
|
||
|
||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||
event.preventDefault();
|
||
setLoading(true);
|
||
setError('');
|
||
|
||
try {
|
||
const response = await login(username, password);
|
||
window.location.replace(buildFrontendHandoffUrl(response, nextPath));
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '登录失败,请稍后重试');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<main className="portal-page">
|
||
<section className="portal-shell">
|
||
<div className="portal-brand">
|
||
<div className="portal-logo-lockup">
|
||
<Image
|
||
src="/boardware-logo.svg"
|
||
alt="Boardware logo"
|
||
width={128}
|
||
height={128}
|
||
className="portal-logo-image"
|
||
/>
|
||
</div>
|
||
<div className="portal-kicker">Auth Portal</div>
|
||
<h1 className="portal-title">Boardware Agent Sandbox</h1>
|
||
<p className="portal-copy">
|
||
这个入口只负责鉴权。成功后会把你直接送到为你分配的专属实例 URL,后续前后端请求都留在那套容器里。
|
||
</p>
|
||
<div className="portal-notes">
|
||
<div className="portal-note">
|
||
<strong>容器边界</strong>
|
||
登录注册先经过独立 auth portal,再跳到专属实例。一用户一套前后端容器不变。
|
||
</div>
|
||
<div className="portal-note">
|
||
<strong>目标页面</strong>
|
||
当前登录完成后将回到:<code>{nextPath}</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="portal-panel">
|
||
<div className="auth-card">
|
||
<h1>登录</h1>
|
||
<p>输入已有账号,认证完成后直接进入目标容器前端。</p>
|
||
|
||
<form className="auth-form" onSubmit={handleSubmit}>
|
||
<div className="field">
|
||
<label htmlFor="username">用户名</label>
|
||
<input
|
||
id="username"
|
||
value={username}
|
||
onChange={(event) => setUsername(event.target.value)}
|
||
autoComplete="username"
|
||
placeholder="例如:bwgdi"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label htmlFor="password">密码</label>
|
||
<input
|
||
id="password"
|
||
type="password"
|
||
value={password}
|
||
onChange={(event) => setPassword(event.target.value)}
|
||
autoComplete="current-password"
|
||
placeholder="输入密码"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="error-text">{error}</div>
|
||
|
||
<button className="primary-button" type="submit" disabled={loading}>
|
||
{loading ? '登录中...' : '登录并进入容器'}
|
||
</button>
|
||
</form>
|
||
|
||
<div className="auth-footer">
|
||
还没有账号? <Link href={withNext('/register', nextPath)}>去注册</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
);
|
||
}
|