diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 08bb468..48c82b9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,6 +29,14 @@ type EditableAgent = { llmName: string; }; +type LaunchCredentials = { + url: string; + authType: string; + authHeaderName: string; + authKey: string; + authKeyMasked: string; +}; + const DEFAULT_ENTRY_POINT = "fastapi_server/server_dashscope.py"; const DEFAULT_LLM_NAME = "qwen-plus"; const DEFAULT_PORT = 8100; @@ -106,6 +114,7 @@ export default function App() { const [selectedId, setSelectedId] = useState(null); const [editor, setEditor] = useState(null); const [statusMessage, setStatusMessage] = useState(""); + const [launchCredentials, setLaunchCredentials] = useState(null); const [busy, setBusy] = useState(false); const configKeySet = useMemo( @@ -401,6 +410,7 @@ export default function App() { setBusy(true); setStatusMessage("Starting agent..."); + setLaunchCredentials(null); try { const resp = await createPipeline({ graph_id: editor.graphId, @@ -414,6 +424,13 @@ export default function App() { }); await refreshRunning(); setStatusMessage(`Agent started. URL: ${resp.url}`); + setLaunchCredentials({ + url: resp.url, + authType: resp.auth_type, + authHeaderName: resp.auth_header_name, + authKey: resp.auth_key_once, + authKeyMasked: resp.auth_key_masked, + }); } catch (error) { setStatusMessage((error as Error).message); } finally { @@ -459,6 +476,23 @@ export default function App() { })), ]; const graphArchImage = editor ? getGraphArchImage(editor.graphId) : null; + const authHeaderValue = launchCredentials + ? `${launchCredentials.authHeaderName}: Bearer ${launchCredentials.authKey}` + : ""; + const canUseClipboard = typeof navigator !== "undefined" && Boolean(navigator.clipboard); + + async function copyText(text: string, label: string): Promise { + if (!canUseClipboard) { + setStatusMessage(`Clipboard is not available. Please copy ${label} manually.`); + return; + } + try { + await navigator.clipboard.writeText(text); + setStatusMessage(`${label} copied.`); + } catch { + setStatusMessage(`Failed to copy ${label}.`); + } + } return (
@@ -517,6 +551,47 @@ export default function App() { {statusMessage ?

{statusMessage}

: null} + {launchCredentials ? ( +
+

Access Credentials (shown once)

+
+ URL:{" "} + + {launchCredentials.url} + + +
+
+ {launchCredentials.authType} key: {launchCredentials.authKey} + +
+
+ Header: {authHeaderValue} + +
+

+ Stored after launch as masked value: {launchCredentials.authKeyMasked} +

+
+ ) : null} {!editor ? (
@@ -646,6 +721,9 @@ export default function App() { {run.url}
+
+ auth: {run.auth_header_name} Bearer {run.auth_key_masked} +
)) )} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index ce2ba13..ea7987c 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -108,6 +108,33 @@ button:disabled { padding: 10px; } +.launch-credentials { + background: #fff4df; + border: 1px solid #f0d5a8; + border-radius: 8px; + margin-top: 12px; + padding: 12px; +} + +.launch-credentials h3 { + margin: 0 0 8px; +} + +.launch-credentials > div { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 6px 0; +} + +.launch-credentials code { + background: #fff; + border: 1px solid #f0d5a8; + border-radius: 4px; + padding: 2px 6px; +} + .empty-panel { margin-top: 30px; } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 520f12d..1ded571 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -62,9 +62,14 @@ export type PipelineRunInfo = { prompt_set_id: string; url: string; port: number; + auth_type: string; + auth_header_name: string; + auth_key_masked: string; }; -export type PipelineCreateResponse = PipelineRunInfo; +export type PipelineCreateResponse = PipelineRunInfo & { + auth_key_once: string; +}; export type PipelineListResponse = { items: PipelineRunInfo[];