fix: open ai workspace console in ops
This commit is contained in:
@@ -4,23 +4,13 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import useSWR from "swr";
|
||||
import { MessageCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
// components
|
||||
import { AIWorkspaceProductSettingsModal } from "@/components/ai-workspace/product-settings-modal";
|
||||
// services
|
||||
import { WorkspaceAIWorkspaceService } from "@/services/workspace-ai-workspace.service";
|
||||
import type {
|
||||
TAIWorkspaceExecutor,
|
||||
TAIWorkspaceExecutorInput,
|
||||
TAIWorkspaceExecutorListResponse,
|
||||
} from "@/services/workspace-ai-workspace.service";
|
||||
|
||||
const workspaceAIWorkspaceService = new WorkspaceAIWorkspaceService();
|
||||
import { AIWorkspaceProductConsole } from "@/components/ai-workspace/product-console";
|
||||
|
||||
type TAIWorkspaceGlobalControlProps = {
|
||||
workspaceSlug: string;
|
||||
@@ -29,15 +19,6 @@ type TAIWorkspaceGlobalControlProps = {
|
||||
export function AIWorkspaceGlobalControl({ workspaceSlug }: TAIWorkspaceGlobalControlProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [dockSlot, setDockSlot] = useState<Element | null>(null);
|
||||
const [busyId, setBusyId] = useState("");
|
||||
const [notice, setNotice] = useState("");
|
||||
const [actionError, setActionError] = useState("");
|
||||
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
isOpen && workspaceSlug ? `AI_WORKSPACE_EXECUTORS_${workspaceSlug}` : null,
|
||||
() => workspaceAIWorkspaceService.listExecutors(workspaceSlug),
|
||||
{ refreshInterval: isOpen ? 10000 : 0 }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === "undefined") return;
|
||||
@@ -58,98 +39,6 @@ export function AIWorkspaceGlobalControl({ workspaceSlug }: TAIWorkspaceGlobalCo
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setNotice("");
|
||||
setActionError("");
|
||||
}, []);
|
||||
|
||||
const runMutation = useCallback(
|
||||
async <T,>(id: string, action: () => Promise<T>, successText: string): Promise<T | null> => {
|
||||
setBusyId(id);
|
||||
setActionError("");
|
||||
setNotice("");
|
||||
try {
|
||||
const result = await action();
|
||||
setNotice(successText);
|
||||
return result;
|
||||
} catch (mutationError: any) {
|
||||
setActionError(
|
||||
String(mutationError?.message || mutationError?.error || mutationError || "AI Workspace request failed")
|
||||
);
|
||||
return null;
|
||||
} finally {
|
||||
setBusyId("");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const updateRegistry = useCallback(
|
||||
async (payload: TAIWorkspaceExecutorListResponse) => {
|
||||
await mutate(payload, { revalidate: false });
|
||||
return payload;
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
const selectExecutor = useCallback(
|
||||
async (executor: TAIWorkspaceExecutor) =>
|
||||
runMutation(
|
||||
`select:${executor.id}`,
|
||||
async () => updateRegistry(await workspaceAIWorkspaceService.selectExecutor(workspaceSlug, executor.id)),
|
||||
"Устройство выбрано"
|
||||
),
|
||||
[runMutation, updateRegistry, workspaceSlug]
|
||||
);
|
||||
|
||||
const createExecutor = useCallback(
|
||||
async (input: TAIWorkspaceExecutorInput) =>
|
||||
runMutation(
|
||||
"save",
|
||||
async () => updateRegistry(await workspaceAIWorkspaceService.createExecutor(workspaceSlug, input)),
|
||||
"Устройство создано"
|
||||
),
|
||||
[runMutation, updateRegistry, workspaceSlug]
|
||||
);
|
||||
|
||||
const updateExecutor = useCallback(
|
||||
async (executorId: string, input: TAIWorkspaceExecutorInput) =>
|
||||
runMutation(
|
||||
"save",
|
||||
async () => updateRegistry(await workspaceAIWorkspaceService.updateExecutor(workspaceSlug, executorId, input)),
|
||||
"Устройство сохранено"
|
||||
),
|
||||
[runMutation, updateRegistry, workspaceSlug]
|
||||
);
|
||||
|
||||
const deleteExecutor = useCallback(
|
||||
async (executorId: string) =>
|
||||
runMutation(
|
||||
`delete:${executorId}`,
|
||||
async () => updateRegistry(await workspaceAIWorkspaceService.deleteExecutor(workspaceSlug, executorId)),
|
||||
"Устройство удалено"
|
||||
),
|
||||
[runMutation, updateRegistry, workspaceSlug]
|
||||
);
|
||||
|
||||
const downloadAgent = useCallback(
|
||||
async (executorId: string, input: TAIWorkspaceExecutorInput, port: string) => {
|
||||
await runMutation(
|
||||
"download",
|
||||
async () => {
|
||||
const payload = await workspaceAIWorkspaceService.updateExecutor(workspaceSlug, executorId, input);
|
||||
await updateRegistry(payload);
|
||||
window.location.href = workspaceAIWorkspaceService.getWindowsAgentInstallerUrl(workspaceSlug, executorId, {
|
||||
port,
|
||||
});
|
||||
},
|
||||
"Installer подготовлен"
|
||||
);
|
||||
},
|
||||
[runMutation, updateRegistry, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{dockSlot
|
||||
@@ -168,26 +57,7 @@ export function AIWorkspaceGlobalControl({ workspaceSlug }: TAIWorkspaceGlobalCo
|
||||
)
|
||||
: null}
|
||||
|
||||
<AIWorkspaceProductSettingsModal
|
||||
isOpen={isOpen}
|
||||
executors={data?.executors ?? []}
|
||||
selectedExecutorId={data?.selectedExecutorId ?? null}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
notice={notice}
|
||||
actionError={actionError}
|
||||
busyId={busyId}
|
||||
onClose={handleClose}
|
||||
onReload={() => mutate()}
|
||||
onSelect={selectExecutor}
|
||||
onCreate={createExecutor}
|
||||
onUpdate={updateExecutor}
|
||||
onDelete={deleteExecutor}
|
||||
onDownloadAgent={downloadAgent}
|
||||
getWindowsAgentInstallerUrl={(executorId, options) =>
|
||||
workspaceAIWorkspaceService.getWindowsAgentInstallerUrl(workspaceSlug, executorId, options)
|
||||
}
|
||||
/>
|
||||
<AIWorkspaceProductConsole open={isOpen} workspaceSlug={workspaceSlug} onClose={() => setIsOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,64 @@ export type TAIWorkspaceExecutorInput = {
|
||||
status?: TAIWorkspaceExecutorStatus;
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThread = {
|
||||
id: string;
|
||||
title: string;
|
||||
originSurface: "engine" | "ops" | "global" | string;
|
||||
activeContext?: Record<string, any>;
|
||||
enabledToolPacks?: string[];
|
||||
linkedArtifacts?: Record<string, any>[];
|
||||
selectedExecutorId?: string | null;
|
||||
lifecycleState?: "active" | "archived" | string;
|
||||
metadata?: Record<string, any>;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadMessageRole = "system" | "user" | "assistant" | "tool";
|
||||
|
||||
export type TAIWorkspaceThreadMessage = {
|
||||
id: string;
|
||||
threadId: string;
|
||||
sequence?: number;
|
||||
role: TAIWorkspaceThreadMessageRole;
|
||||
content: string;
|
||||
payload?: Record<string, any>;
|
||||
createdAt?: string | null;
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadListResponse = {
|
||||
ok: boolean;
|
||||
threads: TAIWorkspaceThread[];
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadResponse = {
|
||||
ok: boolean;
|
||||
thread: TAIWorkspaceThread;
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadMessagesResponse = {
|
||||
ok: boolean;
|
||||
threadId: string;
|
||||
messages: TAIWorkspaceThreadMessage[];
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadMessageResponse = {
|
||||
ok: boolean;
|
||||
message: TAIWorkspaceThreadMessage;
|
||||
};
|
||||
|
||||
export type TAIWorkspaceThreadInput = {
|
||||
title: string;
|
||||
originSurface?: "engine" | "ops" | "global";
|
||||
activeContext?: Record<string, any>;
|
||||
enabledToolPacks?: string[];
|
||||
linkedArtifacts?: Record<string, any>[];
|
||||
selectedExecutorId?: string | null;
|
||||
lifecycleState?: "active" | "archived";
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
export class WorkspaceAIWorkspaceService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
@@ -63,10 +121,15 @@ export class WorkspaceAIWorkspaceService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async createExecutor(workspaceSlug: string, input: TAIWorkspaceExecutorInput): Promise<TAIWorkspaceExecutorListResponse> {
|
||||
await this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/executors/`, { ...input, select: true }).catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
async createExecutor(
|
||||
workspaceSlug: string,
|
||||
input: TAIWorkspaceExecutorInput
|
||||
): Promise<TAIWorkspaceExecutorListResponse> {
|
||||
await this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/executors/`, { ...input, select: true }).catch(
|
||||
(error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
}
|
||||
);
|
||||
return this.listExecutors(workspaceSlug);
|
||||
}
|
||||
|
||||
@@ -101,4 +164,68 @@ export class WorkspaceAIWorkspaceService extends APIService {
|
||||
if (port) params.set("port", port);
|
||||
return `${API_BASE_URL}/api/workspaces/${workspaceSlug}/ai-workspace/executors/${executorId}/agent/windows.ps1?${params.toString()}`;
|
||||
}
|
||||
|
||||
async listThreads(
|
||||
workspaceSlug: string,
|
||||
options: { surface?: "ops" | "engine" | "global"; limit?: number } = {}
|
||||
): Promise<TAIWorkspaceThreadListResponse> {
|
||||
const params = new URLSearchParams();
|
||||
params.set("surface", options.surface || "ops");
|
||||
params.set("limit", String(options.limit || 100));
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/?${params.toString()}`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async createThread(workspaceSlug: string, input: TAIWorkspaceThreadInput): Promise<TAIWorkspaceThreadResponse> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/`, {
|
||||
originSurface: "ops",
|
||||
enabledToolPacks: ["ops"],
|
||||
...input,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async updateThread(
|
||||
workspaceSlug: string,
|
||||
threadId: string,
|
||||
input: Partial<TAIWorkspaceThreadInput>
|
||||
): Promise<TAIWorkspaceThreadResponse> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/${threadId}/`, input)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async listThreadMessages(
|
||||
workspaceSlug: string,
|
||||
threadId: string,
|
||||
options: { limit?: number } = {}
|
||||
): Promise<TAIWorkspaceThreadMessagesResponse> {
|
||||
const params = new URLSearchParams();
|
||||
params.set("limit", String(options.limit || 200));
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/${threadId}/messages/?${params.toString()}`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async createThreadMessage(
|
||||
workspaceSlug: string,
|
||||
threadId: string,
|
||||
input: { role: TAIWorkspaceThreadMessageRole; content: string; payload?: Record<string, any> }
|
||||
): Promise<TAIWorkspaceThreadMessageResponse> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/${threadId}/messages/`, input)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user