feat: add Keycloak authentication
This commit is contained in:
@@ -1141,7 +1141,9 @@ function RunningEvidencePreview({ items }: { items: string[] }) {
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="line-clamp-1">{item}</span>
|
||||
<span className="mt-0.5 rounded-full bg-blue-50 px-1.5 py-0.5 text-xs font-semibold leading-4 text-blue-700">采集中</span>
|
||||
<StatusBadge tone="info" activity="loading" className="mt-0.5">
|
||||
采集中
|
||||
</StatusBadge>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -283,7 +283,8 @@ export function UserMenu({
|
||||
onRefreshTiles,
|
||||
onShowDataStatus,
|
||||
onShowShortcuts,
|
||||
onExportConfig
|
||||
onExportConfig,
|
||||
onLogout
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@@ -292,6 +293,7 @@ export function UserMenu({
|
||||
onShowDataStatus: () => void;
|
||||
onShowShortcuts: () => void;
|
||||
onExportConfig: () => void;
|
||||
onLogout?: () => Promise<void>;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={onOpenChange}>
|
||||
@@ -320,7 +322,15 @@ export function UserMenu({
|
||||
<MenuAction icon={Download} label="导出审计配置" description="保存当前地图和工具状态" onSelect={onExportConfig} />
|
||||
<MenuAction icon={Keyboard} label="操作参考" description="查看绘制与测量操作提示" onSelect={onShowShortcuts} />
|
||||
<MenuSeparator />
|
||||
<DropdownMenuItem disabled className={cn("px-2 py-2 text-slate-400", MAP_COMPACT_RADIUS_CLASS_NAME)}>
|
||||
<DropdownMenuItem
|
||||
disabled={!onLogout}
|
||||
onSelect={onLogout ? () => void onLogout() : undefined}
|
||||
className={cn(
|
||||
"px-2 py-2",
|
||||
!onLogout && "text-slate-400",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME
|
||||
)}
|
||||
>
|
||||
<LogOut size={15} aria-hidden="true" />
|
||||
退出登录
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -44,6 +44,7 @@ export type WorkbenchTopBarProps = {
|
||||
onRefreshTiles: () => void;
|
||||
onShowShortcuts: () => void;
|
||||
onExportConfig: () => void;
|
||||
onLogout?: () => Promise<void>;
|
||||
};
|
||||
|
||||
type HeaderMenuId = "alerts" | "compact-alerts" | "scenario" | "user";
|
||||
@@ -72,7 +73,8 @@ export function WorkbenchTopBar({
|
||||
onShowDataStatus,
|
||||
onRefreshTiles,
|
||||
onShowShortcuts,
|
||||
onExportConfig
|
||||
onExportConfig,
|
||||
onLogout
|
||||
}: WorkbenchTopBarProps) {
|
||||
const [openMenu, setOpenMenu] = useState<HeaderMenuId | null>(null);
|
||||
const activeScenario =
|
||||
@@ -176,6 +178,7 @@ export function WorkbenchTopBar({
|
||||
onShowDataStatus={onShowDataStatus}
|
||||
onShowShortcuts={onShowShortcuts}
|
||||
onExportConfig={onExportConfig}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DefaultChatTransport } from "ai";
|
||||
import useSWR from "swr";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import type { PersonaState } from "@/shared/ai-elements/persona";
|
||||
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
|
||||
import { env } from "@/shared/config/env";
|
||||
import { showMapNotice } from "@/features/map/core";
|
||||
import {
|
||||
@@ -50,13 +51,18 @@ const AGENT_PANEL_COLLAPSE_MS = 180;
|
||||
type UseWorkbenchAgentOptions = {
|
||||
onUiEnvelope: (payload: UIEnvelopePayload, sessionId: string) => Promise<void> | void;
|
||||
onFrontendAction: (request: FrontendActionRequest, signal: AbortSignal) => Promise<unknown>;
|
||||
getAccessToken?: AccessTokenProvider;
|
||||
};
|
||||
|
||||
export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkbenchAgentOptions) {
|
||||
export function useWorkbenchAgent({
|
||||
onUiEnvelope,
|
||||
onFrontendAction,
|
||||
getAccessToken
|
||||
}: UseWorkbenchAgentOptions) {
|
||||
const collapseTimerRef = useRef<number | null>(null);
|
||||
const mobileCollapseTimerRef = useRef<number | null>(null);
|
||||
const sessionStreamAbortRef = useRef<AbortController | null>(null);
|
||||
const clientRef = useRef(createAgentApiClient());
|
||||
const clientRef = useRef(createAgentApiClient(undefined, { getAccessToken }));
|
||||
const sessionIdRef = useRef<string | null>(null);
|
||||
const approvalModeRef = useRef<AgentApprovalMode>("request");
|
||||
const registryRef = useRef<UIRegistry | null>(null);
|
||||
@@ -205,6 +211,10 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
|
||||
() =>
|
||||
new DefaultChatTransport<AgentUiMessage>({
|
||||
api: `${env.TJWATER_AGENT_API_BASE_URL.replace(/\/$/, "")}/api/v1/agent/chat/stream`,
|
||||
headers: async (): Promise<Record<string, string>> => {
|
||||
const accessToken = await getAccessToken?.();
|
||||
return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
||||
},
|
||||
prepareSendMessagesRequest({ id, messages, body, trigger, messageId }) {
|
||||
return {
|
||||
body: {
|
||||
@@ -220,7 +230,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
|
||||
};
|
||||
}
|
||||
}),
|
||||
[]
|
||||
[getAccessToken]
|
||||
);
|
||||
|
||||
const chat = useChat<AgentUiMessage>({
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
type MapSourceStatus
|
||||
} from "@/features/map/core";
|
||||
import { env } from "@/shared/config/env";
|
||||
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
|
||||
import { AgentTaskTicker } from "./components/agent-task-ticker";
|
||||
import { MapDevPanel } from "./components/map-dev-panel";
|
||||
import { MobileWorkbenchSheet } from "./components/mobile-workbench-sheet";
|
||||
@@ -76,7 +77,8 @@ import type {
|
||||
DetailFeature,
|
||||
ScheduledConditionItem,
|
||||
ScheduledConditionRecord,
|
||||
WorkbenchAlert
|
||||
WorkbenchAlert,
|
||||
WorkbenchUser
|
||||
} from "./types";
|
||||
import {
|
||||
createScheduledConditionAlerts,
|
||||
@@ -86,7 +88,15 @@ import { createAlertQueueConversationPrompt } from "./utils/scheduled-condition-
|
||||
|
||||
const WORKBENCH_LAYOUT_CSS_VARIABLES = getWorkbenchLayoutCssVariables();
|
||||
|
||||
export function MapWorkbenchPage() {
|
||||
export function MapWorkbenchPage({
|
||||
user = WORKBENCH_USER,
|
||||
onLogout,
|
||||
getAccessToken
|
||||
}: {
|
||||
user?: WorkbenchUser;
|
||||
onLogout?: () => Promise<void>;
|
||||
getAccessToken?: AccessTokenProvider;
|
||||
}) {
|
||||
const hasMapboxToken = Boolean(env.TJWATER_MAPBOX_ACCESS_TOKEN);
|
||||
const devPanelEnabled = env.TJWATER_ENABLE_DEV_PANEL;
|
||||
const mapContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -129,7 +139,8 @@ export function MapWorkbenchPage() {
|
||||
|
||||
const agent = useWorkbenchAgent({
|
||||
onUiEnvelope: handleAgentUiEnvelope,
|
||||
onFrontendAction: handleFrontendAction
|
||||
onFrontendAction: handleFrontendAction,
|
||||
getAccessToken
|
||||
});
|
||||
const clearActiveTool = useCallback(() => {
|
||||
setActiveToolId(null);
|
||||
@@ -766,7 +777,7 @@ export function MapWorkbenchPage() {
|
||||
scenarios={WORKBENCH_SCENARIOS}
|
||||
activeScenarioId={activeScenarioId}
|
||||
alerts={workbenchAlerts}
|
||||
user={WORKBENCH_USER}
|
||||
user={user}
|
||||
conditionFeedVisible={isLargeScreen ? shouldShowConditionFeed : mobileSheet === "condition"}
|
||||
taskTickerAvailable={taskTickerAvailable}
|
||||
taskTickerVisible={taskTickerVisible}
|
||||
@@ -785,6 +796,7 @@ export function MapWorkbenchPage() {
|
||||
onRefreshTiles={handleRefreshTiles}
|
||||
onShowShortcuts={handleShowShortcuts}
|
||||
onExportConfig={handleExportConfig}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
|
||||
<WorkbenchAgentPanels
|
||||
|
||||
Reference in New Issue
Block a user