第一次提交
This commit is contained in:
1081
app-instance/frontend/lib/api.ts
Normal file
1081
app-instance/frontend/lib/api.ts
Normal file
File diff suppressed because it is too large
Load Diff
31
app-instance/frontend/lib/auth-portal.ts
Normal file
31
app-instance/frontend/lib/auth-portal.ts
Normal file
@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
const AUTH_PORTAL_URL = process.env.NEXT_PUBLIC_AUTH_PORTAL_URL?.trim();
|
||||
const AUTH_PORTAL_PORT = process.env.NEXT_PUBLIC_AUTH_PORTAL_PORT?.trim() || '3081';
|
||||
|
||||
function normalizeBaseUrl(value?: string | null): string | null {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) return null;
|
||||
return trimmed.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function getPortalBaseUrl(): string {
|
||||
const explicit = normalizeBaseUrl(AUTH_PORTAL_URL);
|
||||
if (explicit) return explicit;
|
||||
if (typeof window !== 'undefined') {
|
||||
const url = new URL(window.location.origin);
|
||||
url.port = AUTH_PORTAL_PORT;
|
||||
return normalizeBaseUrl(url.toString()) || window.location.origin;
|
||||
}
|
||||
return `http://127.0.0.1:${AUTH_PORTAL_PORT}`;
|
||||
}
|
||||
|
||||
export function buildAuthPortalUrl(path: '/login' | '/register', nextPath?: string | null): string {
|
||||
const url = new URL(path, `${getPortalBaseUrl()}/`);
|
||||
const nextValue = nextPath?.trim();
|
||||
if (nextValue) {
|
||||
url.searchParams.set('next', nextValue);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
302
app-instance/frontend/lib/store.ts
Normal file
302
app-instance/frontend/lib/store.ts
Normal file
@ -0,0 +1,302 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
import type {
|
||||
AuthUser,
|
||||
ChatMessage,
|
||||
ProcessArtifact,
|
||||
ProcessEvent,
|
||||
ProcessRun,
|
||||
ProcessWsEvent,
|
||||
Session,
|
||||
UiAgentDescriptor,
|
||||
UiMcpServerDescriptor,
|
||||
} from '@/types';
|
||||
import type { WsStatus } from '@/lib/api';
|
||||
|
||||
interface ChatStore {
|
||||
user: AuthUser | null;
|
||||
isAuthLoading: boolean;
|
||||
sessionId: string;
|
||||
messages: ChatMessage[];
|
||||
isLoading: boolean;
|
||||
streamingContent: string;
|
||||
wsStatus: WsStatus;
|
||||
isThinking: boolean;
|
||||
nanobotReady: boolean | null;
|
||||
sessions: Session[];
|
||||
processRuns: ProcessRun[];
|
||||
processEvents: ProcessEvent[];
|
||||
processArtifacts: ProcessArtifact[];
|
||||
selectedRunId: string | null;
|
||||
selectedArtifactId: string | null;
|
||||
agentRegistry: UiAgentDescriptor[];
|
||||
mcpRegistry: UiMcpServerDescriptor[];
|
||||
mcpToolRegistry: Array<{ server_id: string; tools: Array<Record<string, unknown>> }>;
|
||||
lastCancelAck: { runId: string; ok: boolean } | null;
|
||||
setUser: (user: AuthUser | null) => void;
|
||||
setIsAuthLoading: (loading: boolean) => void;
|
||||
setSessionId: (id: string) => void;
|
||||
setMessages: (msgs: ChatMessage[]) => void;
|
||||
addMessage: (msg: ChatMessage) => void;
|
||||
setIsLoading: (loading: boolean) => void;
|
||||
setStreamingContent: (content: string) => void;
|
||||
appendStreamingContent: (chunk: string) => void;
|
||||
setSessions: (sessions: Session[]) => void;
|
||||
clearMessages: () => void;
|
||||
setWsStatus: (status: WsStatus) => void;
|
||||
setIsThinking: (thinking: boolean) => void;
|
||||
setNanobotReady: (ready: boolean | null) => void;
|
||||
resetProcessState: () => void;
|
||||
ingestProcessEvent: (event: ProcessWsEvent) => void;
|
||||
setSelectedRunId: (runId: string | null) => void;
|
||||
setSelectedArtifactId: (artifactId: string | null) => void;
|
||||
setAgentRegistry: (agents: UiAgentDescriptor[]) => void;
|
||||
setMcpRegistry: (servers: UiMcpServerDescriptor[]) => void;
|
||||
setMcpToolRegistry: (tools: Array<{ server_id: string; tools: Array<Record<string, unknown>> }>) => void;
|
||||
}
|
||||
|
||||
function upsertRun(collection: ProcessRun[], run: ProcessRun): ProcessRun[] {
|
||||
const index = collection.findIndex((item) => item.run_id === run.run_id);
|
||||
if (index === -1) {
|
||||
return [...collection, run];
|
||||
}
|
||||
const next = [...collection];
|
||||
next[index] = {
|
||||
...next[index],
|
||||
...run,
|
||||
metadata: {
|
||||
...(next[index].metadata ?? {}),
|
||||
...(run.metadata ?? {}),
|
||||
},
|
||||
};
|
||||
return next;
|
||||
}
|
||||
|
||||
function upsertArtifact(collection: ProcessArtifact[], artifact: ProcessArtifact): ProcessArtifact[] {
|
||||
const index = collection.findIndex((item) => item.artifact_id === artifact.artifact_id);
|
||||
if (index === -1) {
|
||||
return [...collection, artifact];
|
||||
}
|
||||
const next = [...collection];
|
||||
next[index] = artifact;
|
||||
return next;
|
||||
}
|
||||
|
||||
function appendEvent(collection: ProcessEvent[], event: ProcessEvent): ProcessEvent[] {
|
||||
if (collection.some((item) => item.event_id === event.event_id)) {
|
||||
return collection;
|
||||
}
|
||||
return [...collection, event];
|
||||
}
|
||||
|
||||
function createEventId(event: ProcessWsEvent): string {
|
||||
if (event.type === 'process_cancel_ack') {
|
||||
return `${event.type}:${event.run_id}`;
|
||||
}
|
||||
const suffix =
|
||||
'text' in event && typeof event.text === 'string'
|
||||
? event.text
|
||||
: 'title' in event && typeof event.title === 'string'
|
||||
? event.title
|
||||
: event.type;
|
||||
return `${event.type}:${event.run_id}:${event.created_at}:${suffix}`;
|
||||
}
|
||||
|
||||
export const useChatStore = create<ChatStore>((set) => ({
|
||||
user: null,
|
||||
isAuthLoading: true,
|
||||
sessionId: 'web:default',
|
||||
messages: [],
|
||||
isLoading: false,
|
||||
streamingContent: '',
|
||||
wsStatus: 'disconnected',
|
||||
isThinking: false,
|
||||
nanobotReady: null,
|
||||
sessions: [],
|
||||
processRuns: [],
|
||||
processEvents: [],
|
||||
processArtifacts: [],
|
||||
selectedRunId: null,
|
||||
selectedArtifactId: null,
|
||||
agentRegistry: [],
|
||||
mcpRegistry: [],
|
||||
mcpToolRegistry: [],
|
||||
lastCancelAck: null,
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
setIsAuthLoading: (loading) => set({ isAuthLoading: loading }),
|
||||
setSessionId: (id) => set({ sessionId: id }),
|
||||
setMessages: (msgs) => set({ messages: msgs }),
|
||||
addMessage: (msg) => set((s) => ({ messages: [...s.messages, msg] })),
|
||||
setIsLoading: (loading) => set({ isLoading: loading }),
|
||||
setStreamingContent: (content) => set({ streamingContent: content }),
|
||||
appendStreamingContent: (chunk) =>
|
||||
set((s) => ({ streamingContent: s.streamingContent + chunk })),
|
||||
setSessions: (sessions) => set({ sessions }),
|
||||
clearMessages: () => set({ messages: [], streamingContent: '' }),
|
||||
setWsStatus: (status) => set({ wsStatus: status }),
|
||||
setIsThinking: (thinking) => set({ isThinking: thinking }),
|
||||
setNanobotReady: (ready) => set({ nanobotReady: ready }),
|
||||
resetProcessState: () =>
|
||||
set({
|
||||
processRuns: [],
|
||||
processEvents: [],
|
||||
processArtifacts: [],
|
||||
selectedRunId: null,
|
||||
selectedArtifactId: null,
|
||||
lastCancelAck: null,
|
||||
}),
|
||||
ingestProcessEvent: (event) =>
|
||||
set((state) => {
|
||||
if (event.type === 'process_cancel_ack') {
|
||||
return {
|
||||
lastCancelAck: {
|
||||
runId: event.run_id,
|
||||
ok: event.ok,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const eventId = createEventId(event);
|
||||
let nextRuns = state.processRuns;
|
||||
let nextArtifacts = state.processArtifacts;
|
||||
let nextSelectedRunId = state.selectedRunId;
|
||||
const nextEvents = appendEvent(state.processEvents, {
|
||||
event_id: eventId,
|
||||
run_id: event.run_id,
|
||||
parent_run_id: 'parent_run_id' in event ? event.parent_run_id ?? null : null,
|
||||
kind:
|
||||
event.type === 'process_run_started'
|
||||
? 'run_started'
|
||||
: event.type === 'process_run_progress'
|
||||
? 'run_progress'
|
||||
: event.type === 'process_run_status'
|
||||
? 'run_status'
|
||||
: event.type === 'process_run_artifact'
|
||||
? 'run_artifact'
|
||||
: event.type === 'process_run_finished'
|
||||
? 'run_finished'
|
||||
: 'run_cancelled',
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
text:
|
||||
'text' in event && typeof event.text === 'string'
|
||||
? event.text
|
||||
: 'summary' in event && typeof event.summary === 'string'
|
||||
? event.summary
|
||||
: undefined,
|
||||
status: 'status' in event ? event.status : undefined,
|
||||
metadata: 'metadata' in event ? event.metadata : undefined,
|
||||
created_at: event.created_at,
|
||||
});
|
||||
|
||||
if (event.type === 'process_run_started') {
|
||||
nextRuns = upsertRun(nextRuns, {
|
||||
run_id: event.run_id,
|
||||
parent_run_id: event.parent_run_id ?? null,
|
||||
session_id: event.session_id ?? state.sessionId,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title: event.title,
|
||||
source: event.source ?? null,
|
||||
status: event.status,
|
||||
started_at: event.created_at,
|
||||
metadata: event.metadata,
|
||||
});
|
||||
nextSelectedRunId = event.run_id;
|
||||
}
|
||||
|
||||
if (event.type === 'process_run_status') {
|
||||
nextRuns = upsertRun(nextRuns, {
|
||||
run_id: event.run_id,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title:
|
||||
nextRuns.find((item) => item.run_id === event.run_id)?.title ||
|
||||
event.actor_name,
|
||||
status: event.status,
|
||||
started_at:
|
||||
nextRuns.find((item) => item.run_id === event.run_id)?.started_at ||
|
||||
event.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === 'process_run_progress') {
|
||||
const current = nextRuns.find((item) => item.run_id === event.run_id);
|
||||
nextRuns = upsertRun(nextRuns, {
|
||||
run_id: event.run_id,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title: current?.title || event.actor_name,
|
||||
status: current?.status || 'running',
|
||||
started_at: current?.started_at || event.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === 'process_run_artifact') {
|
||||
nextArtifacts = upsertArtifact(nextArtifacts, {
|
||||
artifact_id: `${event.run_id}:${event.created_at}:${event.title}`,
|
||||
run_id: event.run_id,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title: event.title,
|
||||
artifact_type: event.artifact_type,
|
||||
content: event.content,
|
||||
data: event.data,
|
||||
file_id: event.file_id,
|
||||
url: event.url,
|
||||
metadata: event.metadata,
|
||||
created_at: event.created_at,
|
||||
});
|
||||
nextSelectedRunId = event.run_id;
|
||||
}
|
||||
|
||||
if (event.type === 'process_run_finished') {
|
||||
const current = nextRuns.find((item) => item.run_id === event.run_id);
|
||||
nextRuns = upsertRun(nextRuns, {
|
||||
run_id: event.run_id,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title: current?.title || event.actor_name,
|
||||
status: event.status,
|
||||
started_at: current?.started_at || event.created_at,
|
||||
finished_at: event.created_at,
|
||||
summary: event.summary ?? current?.summary ?? null,
|
||||
metadata: event.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === 'process_run_cancelled') {
|
||||
const current = nextRuns.find((item) => item.run_id === event.run_id);
|
||||
nextRuns = upsertRun(nextRuns, {
|
||||
run_id: event.run_id,
|
||||
actor_type: event.actor_type,
|
||||
actor_id: event.actor_id,
|
||||
actor_name: event.actor_name,
|
||||
title: current?.title || event.actor_name,
|
||||
status: 'cancelled',
|
||||
started_at: current?.started_at || event.created_at,
|
||||
finished_at: event.created_at,
|
||||
summary: current?.summary ?? '已取消',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
processRuns: nextRuns,
|
||||
processEvents: nextEvents,
|
||||
processArtifacts: nextArtifacts,
|
||||
selectedRunId: nextSelectedRunId,
|
||||
};
|
||||
}),
|
||||
setSelectedRunId: (runId) => set({ selectedRunId: runId }),
|
||||
setSelectedArtifactId: (artifactId) => set({ selectedArtifactId: artifactId }),
|
||||
setAgentRegistry: (agents) => set({ agentRegistry: agents }),
|
||||
setMcpRegistry: (servers) => set({ mcpRegistry: servers }),
|
||||
setMcpToolRegistry: (tools) => set({ mcpToolRegistry: tools }),
|
||||
}));
|
||||
6
app-instance/frontend/lib/utils.ts
Normal file
6
app-instance/frontend/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user