2026-04-01 00:58:42 +08:00
|
|
|
import { createApiClient } from "@geo/http-client";
|
|
|
|
|
import type {
|
2026-04-05 22:10:05 +08:00
|
|
|
ApiEnvelope,
|
2026-04-01 00:58:42 +08:00
|
|
|
ArticleDetail,
|
2026-04-05 22:10:05 +08:00
|
|
|
ArticleImageUploadResponse,
|
2026-04-01 00:58:42 +08:00
|
|
|
ArticleListParams,
|
|
|
|
|
ArticleListResponse,
|
2026-04-06 22:18:55 +08:00
|
|
|
CreateArticleRequest,
|
2026-04-01 00:58:42 +08:00
|
|
|
ArticleVersion,
|
|
|
|
|
AuthTokens,
|
|
|
|
|
Brand,
|
|
|
|
|
BrandRequest,
|
|
|
|
|
Competitor,
|
|
|
|
|
CompetitorRequest,
|
2026-04-03 00:39:15 +08:00
|
|
|
CreatePluginSessionRequest,
|
|
|
|
|
CreatePluginSessionResponse,
|
|
|
|
|
CreatePublishBatchRequest,
|
|
|
|
|
CreatePublishBatchResponse,
|
2026-04-02 00:31:28 +08:00
|
|
|
GenerateFromRuleRequest,
|
|
|
|
|
GenerateFromRuleResponse,
|
2026-04-02 21:16:12 +08:00
|
|
|
InstantTaskListParams,
|
|
|
|
|
InstantTaskListResponse,
|
2026-04-01 00:58:42 +08:00
|
|
|
JsonValue,
|
2026-04-05 17:14:13 +08:00
|
|
|
KnowledgeGroup,
|
|
|
|
|
KnowledgeGroupRequest,
|
|
|
|
|
KnowledgeItem,
|
2026-04-05 20:41:42 +08:00
|
|
|
KnowledgeItemDetail,
|
2026-04-05 17:14:13 +08:00
|
|
|
KnowledgeTextItemRequest,
|
|
|
|
|
KnowledgeURLItemRequest,
|
2026-04-01 00:58:42 +08:00
|
|
|
Keyword,
|
|
|
|
|
KeywordRequest,
|
|
|
|
|
LoginRequest,
|
|
|
|
|
LoginResponse,
|
2026-04-03 00:39:15 +08:00
|
|
|
MediaPlatform,
|
|
|
|
|
PlatformAccount,
|
|
|
|
|
PublishRecord,
|
|
|
|
|
RegisterPluginInstallationRequest,
|
|
|
|
|
RegisterPluginInstallationResponse,
|
2026-04-02 00:31:28 +08:00
|
|
|
PromptRule,
|
|
|
|
|
PromptRuleGroup,
|
|
|
|
|
PromptRuleGroupRequest,
|
|
|
|
|
PromptRuleListParams,
|
|
|
|
|
PromptRuleListResponse,
|
|
|
|
|
PromptRuleRequest,
|
|
|
|
|
PromptRuleSimple,
|
|
|
|
|
PromptRuleStatusRequest,
|
2026-04-01 00:58:42 +08:00
|
|
|
QuotaSummary,
|
|
|
|
|
Question,
|
|
|
|
|
QuestionRequest,
|
|
|
|
|
RecentArticle,
|
|
|
|
|
RefreshResponse,
|
2026-04-02 00:31:28 +08:00
|
|
|
ScheduleTask,
|
|
|
|
|
ScheduleTaskListParams,
|
|
|
|
|
ScheduleTaskListResponse,
|
|
|
|
|
ScheduleTaskRequest,
|
|
|
|
|
ScheduleTaskStatusRequest,
|
2026-04-01 00:58:42 +08:00
|
|
|
TemplateDetail,
|
2026-04-02 00:31:28 +08:00
|
|
|
TemplateAnalyzeTaskRequest,
|
|
|
|
|
TemplateAnalyzeTaskResultResponse,
|
|
|
|
|
TemplateAssistTaskCreateResponse,
|
|
|
|
|
TemplateOutlineTaskRequest,
|
|
|
|
|
TemplateOutlineTaskResultResponse,
|
|
|
|
|
TemplateSaveDraftRequest,
|
|
|
|
|
TemplateSaveDraftResponse,
|
|
|
|
|
TemplateTitleTaskRequest,
|
|
|
|
|
TemplateTitleTaskResultResponse,
|
2026-04-01 00:58:42 +08:00
|
|
|
TemplateGenerateRequest,
|
|
|
|
|
TemplateGenerateResponse,
|
|
|
|
|
TemplateListItem,
|
|
|
|
|
TemplateCard,
|
|
|
|
|
UpdateQuestionRequest,
|
2026-04-02 00:31:28 +08:00
|
|
|
UpdateArticleRequest,
|
2026-04-01 00:58:42 +08:00
|
|
|
UserInfo,
|
|
|
|
|
WorkspaceOverview,
|
|
|
|
|
} from "@geo/shared-types";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
clearStoredSession,
|
|
|
|
|
getStoredAccessToken,
|
|
|
|
|
getStoredRefreshToken,
|
|
|
|
|
setStoredTokens,
|
|
|
|
|
} from "./session";
|
|
|
|
|
|
|
|
|
|
const baseURL = import.meta.env.VITE_API_BASE_URL ?? "";
|
|
|
|
|
const generationStreamEnabled = import.meta.env.VITE_GENERATION_STREAM_ENABLED === "true";
|
|
|
|
|
|
|
|
|
|
const publicClient = createApiClient({ baseURL });
|
|
|
|
|
|
2026-04-03 00:39:15 +08:00
|
|
|
export function getApiBaseURL(): string {
|
|
|
|
|
if (baseURL) {
|
|
|
|
|
return baseURL;
|
|
|
|
|
}
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
return window.location.origin;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:58:42 +08:00
|
|
|
export const apiClient = createApiClient({
|
|
|
|
|
baseURL,
|
|
|
|
|
auth: {
|
|
|
|
|
getAccessToken: getStoredAccessToken,
|
|
|
|
|
getRefreshToken: getStoredRefreshToken,
|
|
|
|
|
setTokens: setStoredTokens,
|
|
|
|
|
clearTokens: clearStoredSession,
|
|
|
|
|
async refresh(refreshToken: string): Promise<AuthTokens> {
|
|
|
|
|
const data = await publicClient.post<RefreshResponse, { refresh_token: string }>(
|
|
|
|
|
"/api/auth/refresh",
|
|
|
|
|
{ refresh_token: refreshToken },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
accessToken: data.access_token,
|
|
|
|
|
refreshToken: data.refresh_token,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const authApi = {
|
|
|
|
|
login(payload: LoginRequest) {
|
|
|
|
|
return publicClient.post<LoginResponse, LoginRequest>("/api/auth/login", payload);
|
|
|
|
|
},
|
|
|
|
|
me() {
|
|
|
|
|
return apiClient.get<UserInfo>("/api/auth/me");
|
|
|
|
|
},
|
|
|
|
|
logout() {
|
|
|
|
|
return apiClient.post<null>("/api/auth/logout");
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const workspaceApi = {
|
|
|
|
|
overview() {
|
|
|
|
|
return apiClient.get<WorkspaceOverview>("/api/tenant/workspace/overview");
|
|
|
|
|
},
|
|
|
|
|
recentArticles() {
|
|
|
|
|
return apiClient.get<RecentArticle[]>("/api/tenant/workspace/recent-articles");
|
|
|
|
|
},
|
|
|
|
|
quotaSummary() {
|
|
|
|
|
return apiClient.get<QuotaSummary>("/api/tenant/workspace/quota-summary");
|
|
|
|
|
},
|
|
|
|
|
templateCards() {
|
|
|
|
|
return apiClient.get<TemplateCard[]>("/api/tenant/workspace/template-cards");
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const templatesApi = {
|
|
|
|
|
list() {
|
|
|
|
|
return apiClient.get<TemplateListItem[]>("/api/tenant/templates");
|
|
|
|
|
},
|
|
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<TemplateDetail>(`/api/tenant/templates/${id}`);
|
|
|
|
|
},
|
2026-04-02 00:31:28 +08:00
|
|
|
createAnalyzeTask(id: number, payload: TemplateAnalyzeTaskRequest) {
|
|
|
|
|
return apiClient.post<TemplateAssistTaskCreateResponse, TemplateAnalyzeTaskRequest>(
|
|
|
|
|
`/api/tenant/templates/${id}/analyze-tasks`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getAnalyzeTaskResult(id: number, taskId: string) {
|
|
|
|
|
return apiClient.get<TemplateAnalyzeTaskResultResponse>(
|
|
|
|
|
`/api/tenant/templates/${id}/analyze_task_result`,
|
|
|
|
|
{ params: { task_id: taskId } },
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
createTitleTask(id: number, payload: TemplateTitleTaskRequest) {
|
|
|
|
|
return apiClient.post<TemplateAssistTaskCreateResponse, TemplateTitleTaskRequest>(
|
|
|
|
|
`/api/tenant/templates/${id}/title-tasks`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getTitleTaskResult(id: number, taskId: string) {
|
|
|
|
|
return apiClient.get<TemplateTitleTaskResultResponse>(
|
|
|
|
|
`/api/tenant/templates/${id}/gen_title_task_result`,
|
|
|
|
|
{ params: { task_id: taskId } },
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
createOutlineTask(id: number, payload: TemplateOutlineTaskRequest) {
|
|
|
|
|
return apiClient.post<TemplateAssistTaskCreateResponse, TemplateOutlineTaskRequest>(
|
|
|
|
|
`/api/tenant/templates/${id}/outline-tasks`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getOutlineTaskResult(id: number, taskId: string) {
|
|
|
|
|
return apiClient.get<TemplateOutlineTaskResultResponse>(
|
|
|
|
|
`/api/tenant/templates/${id}/gen_outline_task_result`,
|
|
|
|
|
{ params: { task_id: taskId } },
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
createAssistTask(id: number, payload: TemplateAnalyzeTaskRequest) {
|
|
|
|
|
return this.createAnalyzeTask(id, payload);
|
|
|
|
|
},
|
|
|
|
|
getAssistTask(id: number, taskId: string) {
|
|
|
|
|
return this.getAnalyzeTaskResult(id, taskId);
|
2026-04-01 00:58:42 +08:00
|
|
|
},
|
|
|
|
|
generate(id: number, payload: TemplateGenerateRequest) {
|
|
|
|
|
return apiClient.post<TemplateGenerateResponse, TemplateGenerateRequest>(
|
|
|
|
|
`/api/tenant/templates/${id}/generate`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-04-02 00:31:28 +08:00
|
|
|
saveDraft(id: number, payload: TemplateSaveDraftRequest) {
|
|
|
|
|
return apiClient.post<TemplateSaveDraftResponse, TemplateSaveDraftRequest>(
|
|
|
|
|
`/api/tenant/templates/${id}/drafts`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-04-01 00:58:42 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const articlesApi = {
|
|
|
|
|
list(params: ArticleListParams) {
|
|
|
|
|
return apiClient.get<ArticleListResponse>("/api/tenant/articles", { params });
|
|
|
|
|
},
|
2026-04-06 22:18:55 +08:00
|
|
|
create(payload: CreateArticleRequest = {}) {
|
|
|
|
|
return apiClient.post<ArticleDetail, CreateArticleRequest>("/api/tenant/articles", payload);
|
|
|
|
|
},
|
2026-04-01 00:58:42 +08:00
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<ArticleDetail>(`/api/tenant/articles/${id}`);
|
|
|
|
|
},
|
2026-04-02 00:31:28 +08:00
|
|
|
update(id: number, payload: UpdateArticleRequest) {
|
|
|
|
|
return apiClient.put<ArticleDetail, UpdateArticleRequest>(`/api/tenant/articles/${id}`, payload);
|
|
|
|
|
},
|
2026-04-05 22:10:05 +08:00
|
|
|
async uploadImage(id: number, file: File) {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("file", file);
|
|
|
|
|
|
|
|
|
|
const response = await apiClient.raw.post<ApiEnvelope<ArticleImageUploadResponse>>(
|
|
|
|
|
`/api/tenant/articles/${id}/images`,
|
|
|
|
|
formData,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.data.data;
|
|
|
|
|
},
|
2026-04-01 00:58:42 +08:00
|
|
|
versions(id: number) {
|
|
|
|
|
return apiClient.get<ArticleVersion[]>(`/api/tenant/articles/${id}/versions`);
|
|
|
|
|
},
|
2026-04-03 00:39:15 +08:00
|
|
|
publishBatch(id: number, payload: CreatePublishBatchRequest) {
|
|
|
|
|
return apiClient.post<CreatePublishBatchResponse, CreatePublishBatchRequest>(
|
|
|
|
|
`/api/tenant/articles/${id}/publish-batches`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
publishRecords(id: number) {
|
|
|
|
|
return apiClient.get<PublishRecord[]>(`/api/tenant/articles/${id}/publish-records`);
|
|
|
|
|
},
|
2026-04-01 00:58:42 +08:00
|
|
|
remove(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/articles/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-05 17:14:13 +08:00
|
|
|
export const knowledgeApi = {
|
|
|
|
|
listGroups() {
|
|
|
|
|
return apiClient.get<KnowledgeGroup[]>("/api/tenant/knowledge/groups");
|
|
|
|
|
},
|
|
|
|
|
createGroup(payload: KnowledgeGroupRequest) {
|
|
|
|
|
return apiClient.post<KnowledgeGroup, KnowledgeGroupRequest>("/api/tenant/knowledge/groups", payload);
|
|
|
|
|
},
|
|
|
|
|
updateGroup(id: number, payload: KnowledgeGroupRequest) {
|
|
|
|
|
return apiClient.put<null, KnowledgeGroupRequest>(`/api/tenant/knowledge/groups/${id}`, payload);
|
|
|
|
|
},
|
|
|
|
|
removeGroup(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/knowledge/groups/${id}`);
|
|
|
|
|
},
|
|
|
|
|
listItems(groupId?: number) {
|
|
|
|
|
return apiClient.get<KnowledgeItem[]>("/api/tenant/knowledge/items", {
|
|
|
|
|
params: groupId ? { group_id: groupId } : undefined,
|
|
|
|
|
});
|
|
|
|
|
},
|
2026-04-05 20:41:42 +08:00
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<KnowledgeItemDetail>(`/api/tenant/knowledge/items/${id}`);
|
|
|
|
|
},
|
2026-04-05 17:14:13 +08:00
|
|
|
createTextItem(payload: KnowledgeTextItemRequest) {
|
|
|
|
|
return apiClient.post<KnowledgeItem, KnowledgeTextItemRequest>(
|
|
|
|
|
"/api/tenant/knowledge/items/text",
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
createURLItem(payload: KnowledgeURLItemRequest) {
|
|
|
|
|
return apiClient.post<KnowledgeItem, KnowledgeURLItemRequest>(
|
|
|
|
|
"/api/tenant/knowledge/items/url",
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
async createFileItem(payload: { group_id: number; name?: string; file: File }) {
|
|
|
|
|
const form = new FormData();
|
|
|
|
|
form.set("group_id", String(payload.group_id));
|
|
|
|
|
if (payload.name?.trim()) {
|
|
|
|
|
form.set("name", payload.name.trim());
|
|
|
|
|
}
|
|
|
|
|
form.set("file", payload.file);
|
|
|
|
|
const { data } = await apiClient.raw.post("/api/tenant/knowledge/items/file", form);
|
|
|
|
|
return data.data as KnowledgeItem;
|
|
|
|
|
},
|
|
|
|
|
removeItem(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/knowledge/items/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-01 00:58:42 +08:00
|
|
|
export interface ArticleGenerationStreamEvent {
|
|
|
|
|
type: string;
|
|
|
|
|
article_id: number;
|
|
|
|
|
task_id?: number;
|
|
|
|
|
title?: string;
|
|
|
|
|
status?: string;
|
|
|
|
|
delta?: string;
|
|
|
|
|
content?: string;
|
|
|
|
|
error?: string;
|
|
|
|
|
done?: boolean;
|
|
|
|
|
updated_at?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
export interface ArticleSelectionOptimizeRequest {
|
|
|
|
|
title: string;
|
|
|
|
|
markdown_content: string;
|
|
|
|
|
selected_text: string;
|
|
|
|
|
instruction: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ArticleSelectionOptimizeStreamEvent {
|
|
|
|
|
type: string;
|
|
|
|
|
article_id: number;
|
|
|
|
|
status?: string;
|
|
|
|
|
delta?: string;
|
|
|
|
|
content?: string;
|
|
|
|
|
error?: string;
|
|
|
|
|
model?: string;
|
|
|
|
|
done?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:58:42 +08:00
|
|
|
export function isGenerationStreamEnabled(): boolean {
|
|
|
|
|
return generationStreamEnabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function subscribeArticleGeneration(
|
|
|
|
|
articleId: number,
|
|
|
|
|
handlers: {
|
|
|
|
|
onEvent: (event: ArticleGenerationStreamEvent) => void;
|
|
|
|
|
onClose?: () => void;
|
|
|
|
|
},
|
|
|
|
|
signal: AbortSignal,
|
2026-04-06 22:18:55 +08:00
|
|
|
): Promise<void> {
|
|
|
|
|
return subscribeSSE<ArticleGenerationStreamEvent>(
|
|
|
|
|
`/api/tenant/articles/${articleId}/stream`,
|
|
|
|
|
{
|
|
|
|
|
method: "GET",
|
|
|
|
|
},
|
|
|
|
|
handlers,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function streamArticleSelectionOptimize(
|
|
|
|
|
articleId: number,
|
|
|
|
|
payload: ArticleSelectionOptimizeRequest,
|
|
|
|
|
handlers: {
|
|
|
|
|
onEvent: (event: ArticleSelectionOptimizeStreamEvent) => void;
|
|
|
|
|
onClose?: () => void;
|
|
|
|
|
},
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
return subscribeSSE<ArticleSelectionOptimizeStreamEvent>(
|
|
|
|
|
`/api/tenant/articles/${articleId}/selection-optimize/stream`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
handlers,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SSEEventShape = {
|
|
|
|
|
type: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async function subscribeSSE<TEvent extends SSEEventShape>(
|
|
|
|
|
path: string,
|
|
|
|
|
init: RequestInit,
|
|
|
|
|
handlers: {
|
|
|
|
|
onEvent: (event: TEvent) => void;
|
|
|
|
|
onClose?: () => void;
|
|
|
|
|
},
|
|
|
|
|
signal: AbortSignal,
|
2026-04-01 00:58:42 +08:00
|
|
|
): Promise<void> {
|
|
|
|
|
const accessToken = getStoredAccessToken();
|
|
|
|
|
if (!accessToken) {
|
|
|
|
|
throw new Error("missing access token");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
const requestHeaders = new Headers(init.headers);
|
|
|
|
|
requestHeaders.set("Accept", "text/event-stream");
|
|
|
|
|
requestHeaders.set("Authorization", `Bearer ${accessToken}`);
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${baseURL}${path}`, {
|
|
|
|
|
...init,
|
|
|
|
|
headers: requestHeaders,
|
2026-04-01 00:58:42 +08:00
|
|
|
signal,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`stream request failed with status ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
if (!response.body) {
|
|
|
|
|
throw new Error("stream response body is empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
let buffer = "";
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
while (true) {
|
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
|
if (done) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
2026-04-06 22:18:55 +08:00
|
|
|
buffer = consumeSSEBuffer<TEvent>(buffer, handlers.onEvent);
|
2026-04-01 00:58:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buffer += decoder.decode();
|
2026-04-06 22:18:55 +08:00
|
|
|
consumeSSEBuffer<TEvent>(buffer, handlers.onEvent);
|
2026-04-01 00:58:42 +08:00
|
|
|
} finally {
|
|
|
|
|
reader.releaseLock();
|
|
|
|
|
handlers.onClose?.();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const brandsApi = {
|
|
|
|
|
list() {
|
|
|
|
|
return apiClient.get<Brand[]>("/api/tenant/brands");
|
|
|
|
|
},
|
|
|
|
|
create(payload: BrandRequest) {
|
|
|
|
|
return apiClient.post<Brand, BrandRequest>("/api/tenant/brands", payload);
|
|
|
|
|
},
|
|
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<Brand>(`/api/tenant/brands/${id}`);
|
|
|
|
|
},
|
|
|
|
|
update(id: number, payload: BrandRequest) {
|
|
|
|
|
return apiClient.put<null, BrandRequest>(`/api/tenant/brands/${id}`, payload);
|
|
|
|
|
},
|
|
|
|
|
remove(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/brands/${id}`);
|
|
|
|
|
},
|
|
|
|
|
listKeywords(brandId: number) {
|
|
|
|
|
return apiClient.get<Keyword[]>(`/api/tenant/brands/${brandId}/keywords`);
|
|
|
|
|
},
|
|
|
|
|
createKeyword(brandId: number, payload: KeywordRequest) {
|
|
|
|
|
return apiClient.post<Keyword, KeywordRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/keywords`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateKeyword(brandId: number, keywordId: number, payload: KeywordRequest) {
|
|
|
|
|
return apiClient.put<null, KeywordRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/keywords/${keywordId}`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
removeKeyword(brandId: number, keywordId: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/brands/${brandId}/keywords/${keywordId}`);
|
|
|
|
|
},
|
|
|
|
|
listQuestions(brandId: number, keywordId?: number | null) {
|
|
|
|
|
return apiClient.get<Question[]>(`/api/tenant/brands/${brandId}/questions`, {
|
|
|
|
|
params: keywordId ? { keyword_id: keywordId } : undefined,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
createQuestion(brandId: number, payload: QuestionRequest) {
|
|
|
|
|
return apiClient.post<Question, QuestionRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/questions`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateQuestion(brandId: number, questionId: number, payload: UpdateQuestionRequest) {
|
|
|
|
|
return apiClient.put<null, UpdateQuestionRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/questions/${questionId}`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
removeQuestion(brandId: number, questionId: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/brands/${brandId}/questions/${questionId}`);
|
|
|
|
|
},
|
|
|
|
|
listCompetitors(brandId: number) {
|
|
|
|
|
return apiClient.get<Competitor[]>(`/api/tenant/brands/${brandId}/competitors`);
|
|
|
|
|
},
|
|
|
|
|
createCompetitor(brandId: number, payload: CompetitorRequest) {
|
|
|
|
|
return apiClient.post<Competitor, CompetitorRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/competitors`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateCompetitor(brandId: number, competitorId: number, payload: CompetitorRequest) {
|
|
|
|
|
return apiClient.put<null, CompetitorRequest>(
|
|
|
|
|
`/api/tenant/brands/${brandId}/competitors/${competitorId}`,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
removeCompetitor(brandId: number, competitorId: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/brands/${brandId}/competitors/${competitorId}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-03 00:39:15 +08:00
|
|
|
export const mediaApi = {
|
|
|
|
|
platforms() {
|
|
|
|
|
return apiClient.get<MediaPlatform[]>("/api/tenant/media/platforms");
|
|
|
|
|
},
|
|
|
|
|
accounts() {
|
|
|
|
|
return apiClient.get<PlatformAccount[]>("/api/tenant/media/platform-accounts");
|
|
|
|
|
},
|
|
|
|
|
createPluginSession(payload: CreatePluginSessionRequest) {
|
|
|
|
|
return apiClient.post<CreatePluginSessionResponse, CreatePluginSessionRequest>(
|
|
|
|
|
"/api/tenant/media/plugin-sessions",
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
registerPluginInstallation(payload: RegisterPluginInstallationRequest) {
|
|
|
|
|
return apiClient.post<RegisterPluginInstallationResponse, RegisterPluginInstallationRequest>(
|
|
|
|
|
"/api/tenant/media/plugin-installations/register",
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
unbindAccount(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/media/platform-accounts/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-02 00:31:28 +08:00
|
|
|
export const promptRulesApi = {
|
|
|
|
|
list(params?: PromptRuleListParams) {
|
|
|
|
|
return apiClient.get<PromptRuleListResponse>("/api/tenant/prompt-rules", { params });
|
|
|
|
|
},
|
|
|
|
|
listSimple() {
|
|
|
|
|
return apiClient.get<PromptRuleSimple[]>("/api/tenant/prompt-rules/simple");
|
|
|
|
|
},
|
|
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<PromptRule>(`/api/tenant/prompt-rules/${id}`);
|
|
|
|
|
},
|
|
|
|
|
create(payload: PromptRuleRequest) {
|
|
|
|
|
return apiClient.post<PromptRule, PromptRuleRequest>("/api/tenant/prompt-rules", payload);
|
|
|
|
|
},
|
|
|
|
|
update(id: number, payload: PromptRuleRequest) {
|
|
|
|
|
return apiClient.put<null, PromptRuleRequest>(`/api/tenant/prompt-rules/${id}`, payload);
|
|
|
|
|
},
|
|
|
|
|
remove(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/prompt-rules/${id}`);
|
|
|
|
|
},
|
|
|
|
|
toggleStatus(id: number, payload: PromptRuleStatusRequest) {
|
|
|
|
|
return apiClient.put<null, PromptRuleStatusRequest>(`/api/tenant/prompt-rules/${id}/status`, payload);
|
|
|
|
|
},
|
|
|
|
|
listGroups() {
|
|
|
|
|
return apiClient.get<PromptRuleGroup[]>("/api/tenant/prompt-rules/groups");
|
|
|
|
|
},
|
|
|
|
|
createGroup(payload: PromptRuleGroupRequest) {
|
|
|
|
|
return apiClient.post<PromptRuleGroup, PromptRuleGroupRequest>("/api/tenant/prompt-rules/groups", payload);
|
|
|
|
|
},
|
|
|
|
|
updateGroup(id: number, payload: PromptRuleGroupRequest) {
|
|
|
|
|
return apiClient.put<null, PromptRuleGroupRequest>(`/api/tenant/prompt-rules/groups/${id}`, payload);
|
|
|
|
|
},
|
|
|
|
|
removeGroup(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/prompt-rules/groups/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const schedulesApi = {
|
|
|
|
|
list(params?: ScheduleTaskListParams) {
|
|
|
|
|
return apiClient.get<ScheduleTaskListResponse>("/api/tenant/schedules", { params });
|
|
|
|
|
},
|
|
|
|
|
detail(id: number) {
|
|
|
|
|
return apiClient.get<ScheduleTask>(`/api/tenant/schedules/${id}`);
|
|
|
|
|
},
|
|
|
|
|
create(payload: ScheduleTaskRequest) {
|
|
|
|
|
return apiClient.post<ScheduleTask, ScheduleTaskRequest>("/api/tenant/schedules", payload);
|
|
|
|
|
},
|
|
|
|
|
update(id: number, payload: ScheduleTaskRequest) {
|
|
|
|
|
return apiClient.put<null, ScheduleTaskRequest>(`/api/tenant/schedules/${id}`, payload);
|
|
|
|
|
},
|
|
|
|
|
remove(id: number) {
|
|
|
|
|
return apiClient.remove<null>(`/api/tenant/schedules/${id}`);
|
|
|
|
|
},
|
|
|
|
|
toggleStatus(id: number, payload: ScheduleTaskStatusRequest) {
|
|
|
|
|
return apiClient.put<null, ScheduleTaskStatusRequest>(`/api/tenant/schedules/${id}/status`, payload);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const generateApi = {
|
|
|
|
|
fromRule(payload: GenerateFromRuleRequest) {
|
|
|
|
|
return apiClient.post<GenerateFromRuleResponse, GenerateFromRuleRequest>(
|
|
|
|
|
"/api/tenant/articles/generate-from-rule",
|
|
|
|
|
payload,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-02 21:16:12 +08:00
|
|
|
export const instantTasksApi = {
|
|
|
|
|
list(params?: InstantTaskListParams) {
|
|
|
|
|
return apiClient.get<InstantTaskListResponse>("/api/tenant/instant-tasks", { params });
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-01 00:58:42 +08:00
|
|
|
export function normalizeInputParams(
|
|
|
|
|
payload: Record<string, JsonValue>,
|
2026-04-02 00:31:28 +08:00
|
|
|
articleId?: number,
|
|
|
|
|
wizardState?: Record<string, JsonValue>,
|
2026-04-01 00:58:42 +08:00
|
|
|
): TemplateGenerateRequest {
|
2026-04-02 00:31:28 +08:00
|
|
|
return {
|
|
|
|
|
article_id: articleId,
|
|
|
|
|
input_params: payload,
|
|
|
|
|
wizard_state: wizardState,
|
|
|
|
|
};
|
2026-04-01 00:58:42 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
function consumeSSEBuffer<TEvent extends SSEEventShape>(
|
2026-04-01 00:58:42 +08:00
|
|
|
buffer: string,
|
2026-04-06 22:18:55 +08:00
|
|
|
onEvent: (event: TEvent) => void,
|
2026-04-01 00:58:42 +08:00
|
|
|
): string {
|
|
|
|
|
let next = buffer;
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
const delimiterIndex = next.indexOf("\n\n");
|
|
|
|
|
if (delimiterIndex === -1) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rawEvent = next.slice(0, delimiterIndex);
|
|
|
|
|
next = next.slice(delimiterIndex + 2);
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
const parsed = parseSSEEvent<TEvent>(rawEvent);
|
2026-04-01 00:58:42 +08:00
|
|
|
if (parsed) {
|
|
|
|
|
onEvent(parsed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
function parseSSEEvent<TEvent extends SSEEventShape>(raw: string): TEvent | null {
|
2026-04-01 00:58:42 +08:00
|
|
|
const lines = raw.split(/\r?\n/);
|
|
|
|
|
let event = "message";
|
|
|
|
|
const dataLines: string[] = [];
|
|
|
|
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
if (line.startsWith("event:")) {
|
|
|
|
|
event = line.slice(6).trim();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (line.startsWith("data:")) {
|
|
|
|
|
dataLines.push(line.slice(5).trimStart());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dataLines.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 22:18:55 +08:00
|
|
|
const data = JSON.parse(dataLines.join("\n")) as Record<string, unknown> | null;
|
|
|
|
|
if (!data || Array.isArray(data)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:58:42 +08:00
|
|
|
return {
|
|
|
|
|
...data,
|
|
|
|
|
type: event,
|
2026-04-06 22:18:55 +08:00
|
|
|
} as TEvent;
|
2026-04-01 00:58:42 +08:00
|
|
|
}
|