mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-27 08:14:30 -04:00 
			
		
		
		
	feat: OpenAI Custom Headers/Params and Debug Page (#4227)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
		| @@ -105,18 +105,21 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md) | |||||||
| :octicons-tag-24: v1.7.0 | :octicons-tag-24: v1.7.0 | ||||||
|  |  | ||||||
| Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md). | Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md). | ||||||
|  | For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values as JSON encoded strings (e.g. `OPENAI_CUSTOM_PARAMS='{"k1": "v1", "k2": "v2"}'`) | ||||||
|  |  | ||||||
| | Variables                    | Default | Description                                                                                                                                                                  | | | Variables                    | Default | Description                                                                                                                                                                  | | ||||||
| | ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | | ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||||||
| | OPENAI_BASE_URL              |  None   | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform                                                                    | | | OPENAI_BASE_URL              |  None   | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform                                                                    | | ||||||
| | OPENAI_API_KEY               |  None   | Your OpenAI API Key. Enables OpenAI-related features                                                                                                                         | | | OPENAI_API_KEY               |  None   | Your OpenAI API Key. Enables OpenAI-related features                                                                                                                         | | ||||||
| | OPENAI_MODEL                 | gpt-4o  | Which OpenAI model to use. If you're not sure, leave this empty                                                                                                              | | | OPENAI_MODEL                 | gpt-4o  | Which OpenAI model to use. If you're not sure, leave this empty                                                                                                              | | ||||||
|  | | OPENAI_CUSTOM_HEADERS        |  None   | Custom HTTP headers to add to all OpenAI requests. This should generally be left empty unless your custom service requires them                                              | | ||||||
|  | | OPENAI_CUSTOM_PARAMS         |  None   | Custom HTTP query params to add to all OpenAI requests. This should generally be left empty unless your custom service requires them                                         | | ||||||
| | OPENAI_ENABLE_IMAGE_SERVICES |  True   | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs     | | | OPENAI_ENABLE_IMAGE_SERVICES |  True   | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs     | | ||||||
| | OPENAI_WORKERS               |    2    | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs                                                       | | | OPENAI_WORKERS               |    2    | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs                                                       | | ||||||
| | OPENAI_SEND_DATABASE_DATA    |  True   | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs                                                                      | | | OPENAI_SEND_DATABASE_DATA    |  True   | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs                                                                      | | ||||||
| | OPENAI_REQUEST_TIMEOUT       |  60     | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware | | | OPENAI_REQUEST_TIMEOUT       |  60     | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware | | ||||||
|  |  | ||||||
| ### Themeing | ### Theming | ||||||
|  |  | ||||||
| Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x. | Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x. | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -84,13 +84,12 @@ | |||||||
|                 <v-list-item-title>{{ nav.title }}</v-list-item-title> |                 <v-list-item-title>{{ nav.title }}</v-list-item-title> | ||||||
|               </template> |               </template> | ||||||
|  |  | ||||||
|               <v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to"> |               <v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to" class="ml-2"> | ||||||
|                 <v-list-item-icon> |                 <v-list-item-icon> | ||||||
|                   <v-icon>{{ child.icon }}</v-icon> |                   <v-icon>{{ child.icon }}</v-icon> | ||||||
|                 </v-list-item-icon> |                 </v-list-item-icon> | ||||||
|                 <v-list-item-title>{{ child.title }}</v-list-item-title> |                 <v-list-item-title>{{ child.title }}</v-list-item-title> | ||||||
|               </v-list-item> |               </v-list-item> | ||||||
|               <v-divider class="mb-4"></v-divider> |  | ||||||
|             </v-list-group> |             </v-list-group> | ||||||
|  |  | ||||||
|             <!-- Single Item --> |             <!-- Single Item --> | ||||||
|   | |||||||
| @@ -1246,7 +1246,11 @@ | |||||||
|       "here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie", |       "here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie", | ||||||
|       "restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.", |       "restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.", | ||||||
|       "manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others." |       "manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others." | ||||||
|     } |     }, | ||||||
|  |     "debug-openai-services": "Debug OpenAI Services", | ||||||
|  |     "debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.", | ||||||
|  |     "run-test": "Run Test", | ||||||
|  |     "test-results": "Test Results" | ||||||
|   }, |   }, | ||||||
|   "profile": { |   "profile": { | ||||||
|     "welcome-user": "👋 Welcome, {0}!", |     "welcome-user": "👋 Welcome, {0}!", | ||||||
|   | |||||||
| @@ -92,10 +92,23 @@ export default defineComponent({ | |||||||
|         restricted: true, |         restricted: true, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         icon: $globals.icons.slotMachine, |         icon: $globals.icons.robot, | ||||||
|         to: "/admin/parser", |         title: i18n.tc("recipe.debug"), | ||||||
|         title: i18n.tc("sidebar.parser"), |  | ||||||
|         restricted: true, |         restricted: true, | ||||||
|  |         children: [ | ||||||
|  |           { | ||||||
|  |             icon: $globals.icons.robot, | ||||||
|  |             to: "/admin/debug/openai", | ||||||
|  |             title: i18n.tc("admin.openai"), | ||||||
|  |             restricted: true, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             icon: $globals.icons.slotMachine, | ||||||
|  |             to: "/admin/debug/parser", | ||||||
|  |             title: i18n.tc("sidebar.parser"), | ||||||
|  |             restricted: true, | ||||||
|  |           }, | ||||||
|  |         ] | ||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								frontend/lib/api/admin/admin-debug.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/lib/api/admin/admin-debug.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | import { BaseAPI } from "../base/base-clients"; | ||||||
|  | import { DebugResponse } from "~/lib/api/types/admin"; | ||||||
|  |  | ||||||
|  | const prefix = "/api"; | ||||||
|  |  | ||||||
|  | const routes = { | ||||||
|  |   openai: `${prefix}/admin/debug/openai`, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export class AdminDebugAPI extends BaseAPI { | ||||||
|  |   async debugOpenAI(fileObject: Blob | File | undefined = undefined, fileName = "") { | ||||||
|  |     let formData: FormData | null = null; | ||||||
|  |     if (fileObject) { | ||||||
|  |       formData = new FormData(); | ||||||
|  |       formData.append("image", fileObject); | ||||||
|  |       formData.append("extension", fileName.split(".").pop() ?? ""); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return await this.requests.post<DebugResponse>(routes.openai, formData); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ import { AdminGroupsApi } from "./admin/admin-groups"; | |||||||
| import { AdminBackupsApi } from "./admin/admin-backups"; | import { AdminBackupsApi } from "./admin/admin-backups"; | ||||||
| import { AdminMaintenanceApi } from "./admin/admin-maintenance"; | import { AdminMaintenanceApi } from "./admin/admin-maintenance"; | ||||||
| import { AdminAnalyticsApi } from "./admin/admin-analytics"; | import { AdminAnalyticsApi } from "./admin/admin-analytics"; | ||||||
|  | import { AdminDebugAPI } from "./admin/admin-debug"; | ||||||
| import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||||
|  |  | ||||||
| export class AdminAPI { | export class AdminAPI { | ||||||
| @@ -15,6 +16,7 @@ export class AdminAPI { | |||||||
|   public backups: AdminBackupsApi; |   public backups: AdminBackupsApi; | ||||||
|   public maintenance: AdminMaintenanceApi; |   public maintenance: AdminMaintenanceApi; | ||||||
|   public analytics: AdminAnalyticsApi; |   public analytics: AdminAnalyticsApi; | ||||||
|  |   public debug: AdminDebugAPI; | ||||||
|  |  | ||||||
|   constructor(requests: ApiRequestInstance) { |   constructor(requests: ApiRequestInstance) { | ||||||
|     this.about = new AdminAboutAPI(requests); |     this.about = new AdminAboutAPI(requests); | ||||||
| @@ -24,6 +26,7 @@ export class AdminAPI { | |||||||
|     this.backups = new AdminBackupsApi(requests); |     this.backups = new AdminBackupsApi(requests); | ||||||
|     this.maintenance = new AdminMaintenanceApi(requests); |     this.maintenance = new AdminMaintenanceApi(requests); | ||||||
|     this.analytics = new AdminAnalyticsApi(requests); |     this.analytics = new AdminAnalyticsApi(requests); | ||||||
|  |     this.debug = new AdminDebugAPI(requests); | ||||||
|  |  | ||||||
|     Object.freeze(this); |     Object.freeze(this); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -173,6 +173,10 @@ export interface CustomPageOut { | |||||||
|   categories?: RecipeCategoryResponse[]; |   categories?: RecipeCategoryResponse[]; | ||||||
|   id: number; |   id: number; | ||||||
| } | } | ||||||
|  | export interface DebugResponse { | ||||||
|  |   success: boolean; | ||||||
|  |   response?: string | null; | ||||||
|  | } | ||||||
| export interface EmailReady { | export interface EmailReady { | ||||||
|   ready: boolean; |   ready: boolean; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										127
									
								
								frontend/pages/admin/debug/openai.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								frontend/pages/admin/debug/openai.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | <template> | ||||||
|  |   <v-container class="pa-0"> | ||||||
|  |     <v-container> | ||||||
|  |       <BaseCardSectionTitle :title="$tc('admin.debug-openai-services')"> | ||||||
|  |         {{ $t('admin.debug-openai-services-description') }} | ||||||
|  |         <br /> | ||||||
|  |         <DocLink class="mt-2" link="/documentation/getting-started/installation/open-ai" /> | ||||||
|  |       </BaseCardSectionTitle> | ||||||
|  |     </v-container> | ||||||
|  |     <v-form ref="uploadForm" @submit.prevent="testOpenAI"> | ||||||
|  |       <div> | ||||||
|  |         <v-card-text> | ||||||
|  |           <v-container class="pa-0"> | ||||||
|  |             <v-row> | ||||||
|  |               <v-col cols="auto" align-self="center"> | ||||||
|  |                 <AppButtonUpload | ||||||
|  |                   v-if="!uploadedImage" | ||||||
|  |                   class="ml-auto" | ||||||
|  |                   url="none" | ||||||
|  |                   file-name="image" | ||||||
|  |                   accept="image/*" | ||||||
|  |                   :text="$i18n.tc('recipe.upload-image')" | ||||||
|  |                   :text-btn="false" | ||||||
|  |                   :post="false" | ||||||
|  |                   @uploaded="uploadImage" | ||||||
|  |                 /> | ||||||
|  |                 <v-btn | ||||||
|  |                   v-if="!!uploadedImage" | ||||||
|  |                   color="error" | ||||||
|  |                   @click="clearImage" | ||||||
|  |                 > | ||||||
|  |                   <v-icon left>{{ $globals.icons.close }}</v-icon> | ||||||
|  |                   {{ $i18n.tc("recipe.remove-image") }} | ||||||
|  |                 </v-btn> | ||||||
|  |               </v-col> | ||||||
|  |               <v-spacer /> | ||||||
|  |             </v-row> | ||||||
|  |             <v-row v-if="uploadedImage && uploadedImagePreviewUrl" style="max-width: 25%;"> | ||||||
|  |               <v-spacer /> | ||||||
|  |               <v-col cols="12"> | ||||||
|  |                 <v-img :src="uploadedImagePreviewUrl" /> | ||||||
|  |               </v-col> | ||||||
|  |               <v-spacer /> | ||||||
|  |             </v-row> | ||||||
|  |           </v-container> | ||||||
|  |         </v-card-text> | ||||||
|  |         <v-card-actions> | ||||||
|  |           <BaseButton | ||||||
|  |             type="submit" | ||||||
|  |             :text="$i18n.tc('admin.run-test')" | ||||||
|  |             :icon="$globals.icons.check" | ||||||
|  |             :loading="loading" | ||||||
|  |             class="ml-auto" | ||||||
|  |           /> | ||||||
|  |         </v-card-actions> | ||||||
|  |       </div> | ||||||
|  |     </v-form> | ||||||
|  |     <v-divider v-if="response" class="mt-4" /> | ||||||
|  |     <v-container v-if="response" class="ma-0 pa-0"> | ||||||
|  |       <v-card-title> {{ $t('admin.test-results') }} </v-card-title> | ||||||
|  |       <v-card-text> {{ response }} </v-card-text> | ||||||
|  |     </v-container> | ||||||
|  |   </v-container> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, ref } from "@nuxtjs/composition-api"; | ||||||
|  | import { useAdminApi } from "~/composables/api"; | ||||||
|  | import { alert } from "~/composables/use-toast"; | ||||||
|  | import { VForm } from "~/types/vuetify"; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  |   layout: "admin", | ||||||
|  |   setup() { | ||||||
|  |     const api = useAdminApi(); | ||||||
|  |     const loading = ref(false); | ||||||
|  |     const response = ref(""); | ||||||
|  |  | ||||||
|  |     const uploadForm = ref<VForm | null>(null); | ||||||
|  |     const uploadedImage = ref<Blob | File>(); | ||||||
|  |     const uploadedImageName = ref<string>(""); | ||||||
|  |     const uploadedImagePreviewUrl = ref<string>(); | ||||||
|  |  | ||||||
|  |     function uploadImage(fileObject: File) { | ||||||
|  |       uploadedImage.value = fileObject; | ||||||
|  |       uploadedImageName.value = fileObject.name; | ||||||
|  |       uploadedImagePreviewUrl.value = URL.createObjectURL(fileObject); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function clearImage() { | ||||||
|  |       uploadedImage.value = undefined; | ||||||
|  |       uploadedImageName.value = ""; | ||||||
|  |       uploadedImagePreviewUrl.value = undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async function testOpenAI() { | ||||||
|  |       response.value = ""; | ||||||
|  |  | ||||||
|  |       loading.value = true; | ||||||
|  |       const { data } = await api.debug.debugOpenAI(uploadedImage.value); | ||||||
|  |       loading.value = false; | ||||||
|  |  | ||||||
|  |       if (!data) { | ||||||
|  |         alert.error("Unable to test OpenAI services"); | ||||||
|  |       } else { | ||||||
|  |         response.value = data.response || (data.success ? "Test Successful" : "Test Failed"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       loading, | ||||||
|  |       response, | ||||||
|  |       uploadForm, | ||||||
|  |       uploadedImage, | ||||||
|  |       uploadedImagePreviewUrl, | ||||||
|  |       uploadImage, | ||||||
|  |       clearImage, | ||||||
|  |       testOpenAI, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   head() { | ||||||
|  |     return { | ||||||
|  |       title: this.$t("admin.debug-openai-services"), | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
| @@ -3,7 +3,7 @@ import os | |||||||
| import secrets | import secrets | ||||||
| from datetime import datetime, timezone | from datetime import datetime, timezone | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import NamedTuple | from typing import Any, NamedTuple | ||||||
|  |  | ||||||
| from dateutil.tz import tzlocal | from dateutil.tz import tzlocal | ||||||
| from pydantic import field_validator | from pydantic import field_validator | ||||||
| @@ -305,6 +305,10 @@ class AppSettings(AppLoggingSettings): | |||||||
|     """Your OpenAI API key. Required to enable OpenAI features""" |     """Your OpenAI API key. Required to enable OpenAI features""" | ||||||
|     OPENAI_MODEL: str = "gpt-4o" |     OPENAI_MODEL: str = "gpt-4o" | ||||||
|     """Which OpenAI model to send requests to. Leave this unset for most usecases""" |     """Which OpenAI model to send requests to. Leave this unset for most usecases""" | ||||||
|  |     OPENAI_CUSTOM_HEADERS: dict[str, str] = {} | ||||||
|  |     """Custom HTTP headers to send with each OpenAI request""" | ||||||
|  |     OPENAI_CUSTOM_PARAMS: dict[str, Any] = {} | ||||||
|  |     """Custom HTTP parameters to send with each OpenAI request""" | ||||||
|     OPENAI_ENABLE_IMAGE_SERVICES: bool = True |     OPENAI_ENABLE_IMAGE_SERVICES: bool = True | ||||||
|     """Whether to enable image-related features in OpenAI""" |     """Whether to enable image-related features in OpenAI""" | ||||||
|     OPENAI_WORKERS: int = 2 |     OPENAI_WORKERS: int = 2 | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ from mealie.routes._base.routers import AdminAPIRouter | |||||||
| from . import ( | from . import ( | ||||||
|     admin_about, |     admin_about, | ||||||
|     admin_backups, |     admin_backups, | ||||||
|  |     admin_debug, | ||||||
|     admin_email, |     admin_email, | ||||||
|     admin_maintenance, |     admin_maintenance, | ||||||
|     admin_management_groups, |     admin_management_groups, | ||||||
| @@ -19,3 +20,4 @@ router.include_router(admin_management_groups.router, tags=["Admin: Manage Group | |||||||
| router.include_router(admin_email.router, tags=["Admin: Email"]) | router.include_router(admin_email.router, tags=["Admin: Email"]) | ||||||
| router.include_router(admin_backups.router, tags=["Admin: Backups"]) | router.include_router(admin_backups.router, tags=["Admin: Backups"]) | ||||||
| router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"]) | router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"]) | ||||||
|  | router.include_router(admin_debug.router, tags=["Admin: Debug"]) | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								mealie/routes/admin/admin_debug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								mealie/routes/admin/admin_debug.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | import os | ||||||
|  | import shutil | ||||||
|  |  | ||||||
|  | from fastapi import APIRouter, File, UploadFile | ||||||
|  |  | ||||||
|  | from mealie.core.dependencies.dependencies import get_temporary_path | ||||||
|  | from mealie.routes._base import BaseAdminController, controller | ||||||
|  | from mealie.schema.admin.debug import DebugResponse | ||||||
|  | from mealie.services.openai import OpenAILocalImage, OpenAIService | ||||||
|  |  | ||||||
|  | router = APIRouter(prefix="/debug") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @controller(router) | ||||||
|  | class AdminDebugController(BaseAdminController): | ||||||
|  |     @router.post("/openai", response_model=DebugResponse) | ||||||
|  |     async def debug_openai(self, image: UploadFile | None = File(None)): | ||||||
|  |         if not self.settings.OPENAI_ENABLED: | ||||||
|  |             return DebugResponse(success=False, response="OpenAI is not enabled") | ||||||
|  |         if image and not self.settings.OPENAI_ENABLE_IMAGE_SERVICES: | ||||||
|  |             return DebugResponse( | ||||||
|  |                 success=False, response="Image was provided, but OpenAI image services are not enabled" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         with get_temporary_path() as temp_path: | ||||||
|  |             if image: | ||||||
|  |                 with temp_path.joinpath(image.filename).open("wb") as buffer: | ||||||
|  |                     shutil.copyfileobj(image.file, buffer) | ||||||
|  |                 local_image_path = temp_path.joinpath(image.filename) | ||||||
|  |                 local_images = [OpenAILocalImage(filename=os.path.basename(local_image_path), path=local_image_path)] | ||||||
|  |             else: | ||||||
|  |                 local_images = None | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 openai_service = OpenAIService() | ||||||
|  |                 prompt = openai_service.get_prompt("debug") | ||||||
|  |  | ||||||
|  |                 message = "Hello, checking to see if I can reach you." | ||||||
|  |                 if local_images: | ||||||
|  |                     message = f"{message} Here is an image to test with:" | ||||||
|  |  | ||||||
|  |                 response = await openai_service.get_response( | ||||||
|  |                     prompt, message, images=local_images, force_json_response=False | ||||||
|  |                 ) | ||||||
|  |                 return DebugResponse(success=True, response=f'OpenAI is working. Response: "{response}"') | ||||||
|  |  | ||||||
|  |             except Exception as e: | ||||||
|  |                 self.logger.exception(e) | ||||||
|  |                 return DebugResponse( | ||||||
|  |                     success=False, | ||||||
|  |                     response=f'OpenAI request failed. Full error has been logged. {e.__class__.__name__}: "{e}"', | ||||||
|  |                 ) | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| # This file is auto-generated by gen_schema_exports.py | # This file is auto-generated by gen_schema_exports.py | ||||||
| from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, OIDCInfo | from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, OIDCInfo | ||||||
| from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob | from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob | ||||||
|  | from .debug import DebugResponse | ||||||
| from .email import EmailReady, EmailSuccess, EmailTest | from .email import EmailReady, EmailSuccess, EmailTest | ||||||
| from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary | from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary | ||||||
| from .migration import ChowdownURL, MigrationFile, MigrationImport, Migrations | from .migration import ChowdownURL, MigrationFile, MigrationImport, Migrations | ||||||
| @@ -49,4 +50,5 @@ __all__ = [ | |||||||
|     "EmailReady", |     "EmailReady", | ||||||
|     "EmailSuccess", |     "EmailSuccess", | ||||||
|     "EmailTest", |     "EmailTest", | ||||||
|  |     "DebugResponse", | ||||||
| ] | ] | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								mealie/schema/admin/debug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								mealie/schema/admin/debug.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | from mealie.schema._mealie import MealieModel | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DebugResponse(MealieModel): | ||||||
|  |     success: bool | ||||||
|  |     response: str | None = None | ||||||
| @@ -90,6 +90,8 @@ class OpenAIService(BaseService): | |||||||
|             base_url=settings.OPENAI_BASE_URL, |             base_url=settings.OPENAI_BASE_URL, | ||||||
|             api_key=settings.OPENAI_API_KEY, |             api_key=settings.OPENAI_API_KEY, | ||||||
|             timeout=settings.OPENAI_REQUEST_TIMEOUT, |             timeout=settings.OPENAI_REQUEST_TIMEOUT, | ||||||
|  |             default_headers=settings.OPENAI_CUSTOM_HEADERS, | ||||||
|  |             default_query=settings.OPENAI_CUSTOM_PARAMS, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         super().__init__() |         super().__init__() | ||||||
| @@ -176,6 +178,5 @@ class OpenAIService(BaseService): | |||||||
|             if not response.choices: |             if not response.choices: | ||||||
|                 return None |                 return None | ||||||
|             return response.choices[0].message.content |             return response.choices[0].message.content | ||||||
|         except Exception: |         except Exception as e: | ||||||
|             self.logger.exception("OpenAI Request Failed") |             raise Exception(f"OpenAI Request Failed. {e.__class__.__name__}: {e}") from e | ||||||
|             return None |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								mealie/services/openai/prompts/debug.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mealie/services/openai/prompts/debug.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | You are a simple chatbot being used for debugging purposes. | ||||||
| @@ -80,10 +80,20 @@ class OpenAIParser(ABCIngredientParser): | |||||||
|             tasks.append(service.get_response(prompt, message, force_json_response=True)) |             tasks.append(service.get_response(prompt, message, force_json_response=True)) | ||||||
|  |  | ||||||
|         # re-combine chunks into one response |         # re-combine chunks into one response | ||||||
|         responses_json = await asyncio.gather(*tasks) |         try: | ||||||
|         responses = [ |             responses_json = await asyncio.gather(*tasks) | ||||||
|             OpenAIIngredients.parse_openai_response(response_json) for response_json in responses_json if responses_json |         except Exception as e: | ||||||
|         ] |             raise Exception("Failed to call OpenAI services") from e | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             responses = [ | ||||||
|  |                 OpenAIIngredients.parse_openai_response(response_json) | ||||||
|  |                 for response_json in responses_json | ||||||
|  |                 if responses_json | ||||||
|  |             ] | ||||||
|  |         except Exception as e: | ||||||
|  |             raise Exception("Failed to parse OpenAI response") from e | ||||||
|  |  | ||||||
|         if not responses: |         if not responses: | ||||||
|             raise Exception("No response from OpenAI") |             raise Exception("No response from OpenAI") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -487,7 +487,13 @@ class OpenAIRecipeService(RecipeServiceBase): | |||||||
|         if translate_language: |         if translate_language: | ||||||
|             message += f" Please translate the recipe to {translate_language}." |             message += f" Please translate the recipe to {translate_language}." | ||||||
|  |  | ||||||
|         response = await openai_service.get_response(prompt, message, images=openai_images, force_json_response=True) |         try: | ||||||
|  |             response = await openai_service.get_response( | ||||||
|  |                 prompt, message, images=openai_images, force_json_response=True | ||||||
|  |             ) | ||||||
|  |         except Exception as e: | ||||||
|  |             raise Exception("Failed to call OpenAI services") from e | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             openai_recipe = OpenAIRecipe.parse_openai_response(response) |             openai_recipe = OpenAIRecipe.parse_openai_response(response) | ||||||
|             recipe = self._convert_recipe(openai_recipe) |             recipe = self._convert_recipe(openai_recipe) | ||||||
|   | |||||||
| @@ -11,6 +11,8 @@ admin_backups = "/api/admin/backups" | |||||||
| """`/api/admin/backups`""" | """`/api/admin/backups`""" | ||||||
| admin_backups_upload = "/api/admin/backups/upload" | admin_backups_upload = "/api/admin/backups/upload" | ||||||
| """`/api/admin/backups/upload`""" | """`/api/admin/backups/upload`""" | ||||||
|  | admin_debug_openai = "/api/admin/debug/openai" | ||||||
|  | """`/api/admin/debug/openai`""" | ||||||
| admin_email = "/api/admin/email" | admin_email = "/api/admin/email" | ||||||
| """`/api/admin/email`""" | """`/api/admin/email`""" | ||||||
| admin_groups = "/api/admin/groups" | admin_groups = "/api/admin/groups" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user