From a9014673a0ce9f84556b214a55a1be7cdfd5fae8 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 15 Aug 2024 09:00:11 -0400 Subject: [PATCH] wip export --- invokeai/app/api/routers/style_presets.py | 30 +++++++++- .../components/StylePresetMenu.tsx | 55 ++++++++++++++++++- .../services/api/endpoints/stylePresets.ts | 11 ++++ .../frontend/web/src/services/api/schema.ts | 38 +++++++++++++ 4 files changed, 131 insertions(+), 3 deletions(-) diff --git a/invokeai/app/api/routers/style_presets.py b/invokeai/app/api/routers/style_presets.py index 786c522c20..413b9aff08 100644 --- a/invokeai/app/api/routers/style_presets.py +++ b/invokeai/app/api/routers/style_presets.py @@ -1,10 +1,11 @@ +import csv import io import json import traceback from typing import Optional import pydantic -from fastapi import APIRouter, File, Form, HTTPException, Path, UploadFile +from fastapi import APIRouter, File, Form, HTTPException, Path, Response, UploadFile from fastapi.responses import FileResponse from PIL import Image from pydantic import BaseModel, Field @@ -225,3 +226,30 @@ async def get_style_preset_image( return response except Exception: raise HTTPException(status_code=404) + + +@style_presets_router.get( + "/export", + operation_id="export_style_presets", + responses={200: {"content": {"text/csv": {}}, "description": "A CSV file with the requested data."}}, + status_code=200, +) +async def export_style_presets(): + # Create an in-memory stream to store the CSV data + output = io.StringIO() + writer = csv.writer(output) + + # Write the header + writer.writerow(["name", "prompt", "negative_prompt"]) + + style_presets = ApiDependencies.invoker.services.style_preset_records.get_many() + + for preset in style_presets: + writer.writerow([preset.name, preset.preset_data.positive_prompt, preset.preset_data.negative_prompt]) + + csv_data = output.getvalue() + output.close() + + return Response( + content=csv_data, media_type="text/csv", headers={"Content-Disposition": "attachment; filename=data.csv"} + ) diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx index 021c274048..1218f65d1b 100644 --- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx +++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx @@ -4,12 +4,23 @@ import { useAppSelector } from 'app/store/storeHooks'; import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiPlusBold } from 'react-icons/pi'; +import { PiDownloadBold, PiPlusBold } from 'react-icons/pi'; import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets'; -import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets'; +import { useLazyExportStylePresetsQuery, useListStylePresetsQuery } from 'services/api/endpoints/stylePresets'; import { StylePresetList } from './StylePresetList'; import StylePresetSearch from './StylePresetSearch'; +import { toast } from '../../toast/toast'; + +const generateCSV = (data: any[]) => { + const header = ['Column1', 'Column2', 'Column3']; + const csvRows = [ + header.join(','), // add header row + ...data.map((row) => row.join(',')), // add data rows + ]; + + return csvRows.join('\n'); +}; export const StylePresetMenu = () => { const searchTerm = useAppSelector((s) => s.stylePreset.searchTerm); @@ -47,6 +58,7 @@ export const StylePresetMenu = () => { }); const { t } = useTranslation(); + const [exportStylePresets, { isLoading }] = useLazyExportStylePresetsQuery(); const handleClickAddNew = useCallback(() => { $stylePresetModalState.set({ @@ -56,10 +68,49 @@ export const StylePresetMenu = () => { }); }, []); + const handleClickDownloadCsv = useCallback(async () => { + let blob; + try { + const response = await exportStylePresets().unwrap(); + blob = new Blob([response], { type: 'text/csv' }); + } catch (error) { + toast({ + status: 'error', + title: 'Unable to generate and download export', + }); + } + + if (blob) { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'data.csv'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + } + toast({ + status: 'success', + title: 'Export downloaded', + }); + }, [exportStylePresets]); + return ( + } + tooltip={t('stylePresets.createPromptTemplate')} + aria-label={t('stylePresets.createPromptTemplate')} + onClick={handleClickDownloadCsv} + size="md" + variant="link" + w={8} + h={8} + isDisabled={isLoading} + /> } tooltip={t('stylePresets.createPromptTemplate')} diff --git a/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts b/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts index 2e1b1a7108..a6dccfa32c 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts @@ -92,6 +92,16 @@ export const stylePresetsApi = api.injectEndpoints({ }), providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }], }), + exportStylePresets: build.query< + string, + void + >({ + query: () => ({ + url: buildStylePresetsUrl("/export"), + responseHandler: response => response.text() + }), + providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }], + }), }), }); @@ -100,4 +110,5 @@ export const { useDeleteStylePresetMutation, useUpdateStylePresetMutation, useListStylePresetsQuery, + useLazyExportStylePresetsQuery } = stylePresetsApi; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 02c1c88412..e422cca904 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -1344,6 +1344,23 @@ export type paths = { patch?: never; trace?: never; }; + "/api/v1/style_presets/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Export Style Presets */ + get: operations["export_style_presets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; }; export type webhooks = Record; export type components = { @@ -18083,4 +18100,25 @@ export interface operations { }; }; }; + export_style_presets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A CSV file with the requested data. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + "text/csv": unknown; + }; + }; + }; + }; }