feat: add image management functionality
- Implemented image folder repository with CRUD operations. - Added image reference repository for managing image references to articles. - Created image repository for handling image assets, including listing, inserting, updating, and deleting images. - Introduced image usage repository to track storage usage and quotas for tenants. - Added SQL queries for image assets, folders, references, and usage. - Developed image handler for HTTP endpoints to manage images and folders. - Created database migration scripts for image-related tables and structures.
This commit is contained in:
@@ -71,6 +71,14 @@ import type {
|
||||
TemplateGenerateResponse,
|
||||
TemplateListItem,
|
||||
TemplateCard,
|
||||
ImageFolder,
|
||||
ImageAssetItem,
|
||||
ImageAssetListResponse,
|
||||
ImageUploadResponse,
|
||||
ImageReferenceDetail,
|
||||
FolderDeletePreview,
|
||||
ImageStorageUsage,
|
||||
ImageUpdateRequest,
|
||||
UpdateQuestionRequest,
|
||||
UpdateArticleRequest,
|
||||
UserInfo,
|
||||
@@ -83,6 +91,12 @@ import {
|
||||
getStoredRefreshToken,
|
||||
setStoredTokens,
|
||||
} from "./session";
|
||||
import {
|
||||
convertImageFileToWebp,
|
||||
markImageUploadRequestFailed,
|
||||
markImageUploadRequestFinished,
|
||||
markImageUploadRequestStarted,
|
||||
} from "./image-webp";
|
||||
|
||||
const baseURL = import.meta.env.VITE_API_BASE_URL ?? "";
|
||||
const generationStreamEnabled = import.meta.env.VITE_GENERATION_STREAM_ENABLED === "true";
|
||||
@@ -242,18 +256,27 @@ export const articlesApi = {
|
||||
return apiClient.put<ArticleDetail, UpdateArticleRequest>(`/api/tenant/articles/${id}`, payload);
|
||||
},
|
||||
async uploadImage(id: number, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const normalizedFile = await convertImageFileToWebp(file);
|
||||
const progressToken = markImageUploadRequestStarted(normalizedFile.name);
|
||||
|
||||
const response = await apiClient.raw.post<ApiEnvelope<ArticleImageUploadResponse>>(
|
||||
`/api/tenant/articles/${id}/images`,
|
||||
formData,
|
||||
);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", normalizedFile, normalizedFile.name);
|
||||
|
||||
return {
|
||||
...response.data.data,
|
||||
url: resolveApiURL(response.data.data.url),
|
||||
};
|
||||
const response = await apiClient.raw.post<ApiEnvelope<ArticleImageUploadResponse>>(
|
||||
`/api/tenant/articles/${id}/images`,
|
||||
formData,
|
||||
);
|
||||
|
||||
markImageUploadRequestFinished(progressToken);
|
||||
return {
|
||||
...response.data.data,
|
||||
url: resolveApiURL(response.data.data.url),
|
||||
};
|
||||
} catch (error) {
|
||||
markImageUploadRequestFailed(progressToken);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
versions(id: number) {
|
||||
return apiClient.get<ArticleVersion[]>(`/api/tenant/articles/${id}/versions`);
|
||||
@@ -320,6 +343,65 @@ export const knowledgeApi = {
|
||||
},
|
||||
};
|
||||
|
||||
export const imagesApi = {
|
||||
listFolders() {
|
||||
return apiClient.get<ImageFolder[]>("/api/tenant/images/folders");
|
||||
},
|
||||
createFolder(name: string) {
|
||||
return apiClient.post<ImageFolder, { name: string }>("/api/tenant/images/folders", { name });
|
||||
},
|
||||
updateFolder(id: number, name: string) {
|
||||
return apiClient.put<null, { name: string }>(`/api/tenant/images/folders/${id}`, { name });
|
||||
},
|
||||
folderDeletePreview(id: number) {
|
||||
return apiClient.get<FolderDeletePreview>(`/api/tenant/images/folders/${id}/delete-preview`);
|
||||
},
|
||||
deleteFolder(id: number, force?: boolean) {
|
||||
return apiClient.remove<null>(`/api/tenant/images/folders/${id}`, { params: force ? { force: 1 } : undefined });
|
||||
},
|
||||
list(params?: { folder_id?: number | null; q?: string; page?: number; page_size?: number }) {
|
||||
return apiClient.get<ImageAssetListResponse>("/api/tenant/images", { params });
|
||||
},
|
||||
async upload(file: File, folderId?: number | null) {
|
||||
const normalizedFile = await convertImageFileToWebp(file);
|
||||
const progressToken = markImageUploadRequestStarted(normalizedFile.name);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", normalizedFile, normalizedFile.name);
|
||||
if (folderId != null) {
|
||||
formData.append("folder_id", String(folderId));
|
||||
}
|
||||
|
||||
const response = await apiClient.raw.post<ApiEnvelope<ImageUploadResponse>>(
|
||||
"/api/tenant/images",
|
||||
formData,
|
||||
);
|
||||
|
||||
markImageUploadRequestFinished(progressToken);
|
||||
return {
|
||||
...response.data.data,
|
||||
url: resolveApiURL(response.data.data.url),
|
||||
};
|
||||
} catch (error) {
|
||||
markImageUploadRequestFailed(progressToken);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
update(id: number, payload: ImageUpdateRequest) {
|
||||
return apiClient.put<null, ImageUpdateRequest>(`/api/tenant/images/${id}`, payload);
|
||||
},
|
||||
getReferences(id: number) {
|
||||
return apiClient.get<ImageReferenceDetail>(`/api/tenant/images/${id}/references`);
|
||||
},
|
||||
delete(id: number, force?: boolean) {
|
||||
return apiClient.remove<null>(`/api/tenant/images/${id}`, { params: force ? { force: 1 } : undefined });
|
||||
},
|
||||
storageUsage() {
|
||||
return apiClient.get<ImageStorageUsage>("/api/tenant/images/storage-usage");
|
||||
},
|
||||
};
|
||||
|
||||
export interface ArticleGenerationStreamEvent {
|
||||
type: string;
|
||||
article_id: number;
|
||||
|
||||
Reference in New Issue
Block a user