Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OpenAI Ingredient Parsing #3581

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
606bfc9
added openai settings
michael-genson May 9, 2024
64f5a42
added WIP openai service
michael-genson May 9, 2024
f184cd2
added openai ingredient parser backend
michael-genson May 10, 2024
19a98b4
improve confidence calculation
michael-genson May 10, 2024
74ef0f5
explicit definition for input string
michael-genson May 10, 2024
009f0bf
docs
michael-genson May 10, 2024
ec23dfb
added openai parser to frontend
michael-genson May 10, 2024
abc20a6
match parser response with food/unit store
michael-genson May 10, 2024
8ef9c80
add more parsing instructions to prompt
michael-genson May 10, 2024
50ed3e7
remember last chosen parser
michael-genson May 10, 2024
54676fc
refactored and added more openai customization/optimization
michael-genson May 10, 2024
8fa252d
docs
michael-genson May 10, 2024
2e0bffd
hide openai parser if openai is disabled
michael-genson May 10, 2024
e03c241
lowered default workers to 2
michael-genson May 10, 2024
70c6c39
added loading animation
michael-genson May 10, 2024
5b5832f
additional docs
michael-genson May 10, 2024
f475f6b
removed food injection
michael-genson May 10, 2024
334d1f8
tweaked prompt
michael-genson May 10, 2024
32d5b89
add openai parse test
michael-genson May 10, 2024
b26e775
typos
michael-genson May 10, 2024
798ff34
prompt tweaks
michael-genson May 10, 2024
5943402
lint
michael-genson May 10, 2024
5607ca1
fixed bad import
michael-genson May 10, 2024
b3a3816
Merge branch 'mealie-next' into feat/open-ai-ingredient-parsing
michael-genson May 10, 2024
5796c1f
more lint
michael-genson May 10, 2024
73e5332
prompt tweak
michael-genson May 11, 2024
5611189
Merge branch 'mealie-next' into feat/open-ai-ingredient-parsing
michael-genson May 11, 2024
dbfa518
changed default model to gpt-4o
michael-genson May 14, 2024
36c991c
added configurable base URL
michael-genson May 14, 2024
d1e977d
add openAI status to site settings
michael-genson May 14, 2024
eec111d
added docstring
michael-genson May 14, 2024
a0dcf99
Merge branch 'mealie-next' into feat/open-ai-ingredient-parsing
michael-genson May 16, 2024
5f253bc
Merge branch 'mealie-next' into feat/open-ai-ingredient-parsing
boc-the-git May 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/docs/documentation/getting-started/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Do the following for each recipe you want to intelligently handle ingredients.
6. Click the Edit button/icon again
7. Scroll to the ingredients and you should see new fields for Amount, Unit, Food, and Note. The Note in particular will contain the original text of the Recipe.
8. Click `Parse` and you will be taken to the ingredient parsing page.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled OpenAI support](./installation/backend-config.md#openai).
10. Click `Parse All`, and your ingredients should be separated out into Units and Foods based on your seeding in Step 1 above.
11. For ingredients where the Unit or Food was not found, you can click a button to accept an automatically suggested Food to add to the database. Or, manually enter the Unit/Food and hit `Enter` (or click `Create`) to add it to the database
12. When done, click `Save All` and you will be taken back to the recipe. Now the Unit and Food fields of the recipe should be filled out.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim**|
| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |

### OpenAI

:octicons-tag-24: v1.7.0

Mealie supports various integrations using OpenAI. To enable OpenAI, [you must provide your OpenAI API key](https://platform.openai.com/api-keys). You can tweak how OpenAI is used using these backend settings. Please note that while OpenAI usage is optimized to reduce API costs, you're unlikely to be able to use OpenAI features with the free tier limits.

| 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_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_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 |
boc-the-git marked this conversation as resolved.
Show resolved Hide resolved

### Themeing

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.
Expand Down
55 changes: 31 additions & 24 deletions frontend/components/global/BaseOverflowButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,50 @@
</v-icon>
</v-btn>
</template>
<!-- Model -->
<!-- Model -->
<v-list v-if="mode === MODES.model" dense>
<v-list-item-group v-model="itemGroup">
<template v-for="(item, index) in items">
<v-list-item :key="index" @click="setValue(item)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
<div v-if="!item.hide" :key="index">
<v-list-item @click="setValue(item)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</div>
</template>
</v-list-item-group>
</v-list>
<!-- Links -->
<!-- Links -->
<v-list v-else-if="mode === MODES.link" dense>
<v-list-item-group v-model="itemGroup">
<template v-for="(item, index) in items">
<v-list-item :key="index" :to="item.to">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
<div v-if="!item.hide" :key="index">
<v-list-item :to="item.to">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>\
</div>
</template>
</v-list-item-group>
</v-list>
<!-- Event -->
<!-- Event -->
<v-list v-else-if="mode === MODES.event" dense>
<template v-for="(item, index) in items">
<v-list-item :key="index" @click="$emit(item.event)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
<div v-if="!item.hide" :key="index">
<v-list-item @click="$emit(item.event)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</div>
</template>
</v-list>
</v-menu>
Expand All @@ -74,6 +80,7 @@ export interface MenuItem {
value?: string;
event?: string;
divider?: boolean;
hide?:boolean;
}

export default defineComponent({
Expand Down
20 changes: 19 additions & 1 deletion frontend/composables/use-users/preferences.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
import { TimelineEventType } from "~/lib/api/types/recipe";
import { RegisteredParser, TimelineEventType } from "~/lib/api/types/recipe";

export interface UserPrintPreferences {
imagePosition: string;
Expand Down Expand Up @@ -36,6 +36,10 @@ export interface UserTimelinePreferences {
types: TimelineEventType[];
}

export interface UserParsingPreferences {
parser: RegisteredParser;
}

export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
const fromStorage = useLocalStorage(
"recipe-print-preferences",
Expand Down Expand Up @@ -116,3 +120,17 @@ export function useTimelinePreferences(): Ref<UserTimelinePreferences> {

return fromStorage;
}

export function useParsingPreferences(): Ref<UserParsingPreferences> {
const fromStorage = useLocalStorage(
"parsing-preferences",
{
parser: "nlp",
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref<UserParsingPreferences>;

return fromStorage;
}
7 changes: 6 additions & 1 deletion frontend/lang/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
Expand Down Expand Up @@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"oidc-ready": "OIDC Ready",
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
"oidc-ready-success-text": "Required OIDC variables are all set."
"oidc-ready-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
Expand Down Expand Up @@ -1170,6 +1174,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",
Expand Down
3 changes: 3 additions & 0 deletions frontend/lib/api/types/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface AdminAboutInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
versionLatest: string;
apiPort: number;
apiDocs: boolean;
Expand Down Expand Up @@ -40,6 +41,7 @@ export interface AppInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
}
export interface AppStartupInfo {
isFirstLogin: boolean;
Expand Down Expand Up @@ -80,6 +82,7 @@ export interface CheckAppConfig {
emailReady: boolean;
ldapReady: boolean;
oidcReady: boolean;
enableOpenai: boolean;
baseUrlSet: boolean;
isUpToDate: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/api/types/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

export type ExportTypes = "json";
export type RegisteredParser = "nlp" | "brute";
export type RegisteredParser = "nlp" | "brute" | "openai";
export type TimelineEventType = "system" | "info" | "comment";
export type TimelineEventImage = "has image" | "does not have image";

Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/api/user/recipes/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "~/lib/api/types/recipe";
import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";

export type Parser = "nlp" | "brute";
export type Parser = "nlp" | "brute" | "openai";

export interface CreateAsset {
name: string;
Expand Down
3 changes: 2 additions & 1 deletion frontend/pages/admin/parser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<v-btn-toggle v-model="parser" dense mandatory @change="processIngredient">
<v-btn value="nlp"> {{ $t('admin.nlp') }} </v-btn>
<v-btn value="brute"> {{ $t('admin.brute') }} </v-btn>
<v-btn value="openai"> {{ $t('admin.openai') }} </v-btn>
</v-btn-toggle>

<v-checkbox v-model="showConfidence" class="ml-5" :label="$t('admin.show-individual-confidence')"></v-checkbox>
Expand Down Expand Up @@ -63,8 +64,8 @@

<script lang="ts">
import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { IngredientConfidence } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
import { IngredientConfidence } from "~/lib/api/types/recipe";
import { Parser } from "~/lib/api/user/recipes/recipe";

type ConfidenceAttribute = "average" | "comment" | "name" | "unit" | "quantity" | "food";
Expand Down
9 changes: 9 additions & 0 deletions frontend/pages/admin/site-settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ export default defineComponent({
color: appConfig.value.oidcReady ? goodColor : warningColor,
icon: appConfig.value.oidcReady ? goodIcon : warningIcon,
},
{
id: "openai-ready",
text: i18n.t("settings.openai-ready"),
status: appConfig.value.enableOpenai,
errorText: i18n.t("settings.openai-ready-error-text"),
successText: i18n.t("settings.openai-ready-success-text"),
color: appConfig.value.enableOpenai ? goodColor : warningColor,
icon: appConfig.value.enableOpenai ? goodIcon : warningIcon,
},
];
return data;
});
Expand Down
Loading
Loading