mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-25 11:19:39 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed8f97e9e0 | ||
|
|
034f68fc28 | ||
|
|
0158087a0b | ||
|
|
cb6bfd741d | ||
|
|
afeee5f7cb | ||
|
|
b43d6e08d4 | ||
|
|
1188624376 | ||
|
|
9ac837c969 | ||
|
|
fc4b017d30 | ||
|
|
4636ac28f9 | ||
|
|
397912e87f | ||
|
|
d0b860e623 | ||
|
|
8a90ed1274 | ||
|
|
286d707347 | ||
|
|
98d308aee9 | ||
|
|
a7c5240227 | ||
|
|
75fcff8e70 | ||
|
|
2f27cf4deb | ||
|
|
686b595f45 | ||
|
|
0f9f9e8f7c | ||
|
|
e6abdf8cd4 | ||
|
|
741e9eb370 | ||
|
|
7db523d8c4 | ||
|
|
41f0060c43 | ||
|
|
5572833f64 | ||
|
|
780e441a3b | ||
|
|
c4fd2d0b4e | ||
|
|
1c6618f452 | ||
|
|
8c96a75a1e | ||
|
|
f099e2e5d3 |
@@ -15,14 +15,14 @@
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer"><img src="https://badgen.net/badge/icon/discord?icon=discord&label" ></a>
|
||||
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
<a href="https://app.tandoor.dev/e/demo-auto-login/" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://tandoor.dev" target="_blank" rel="noopener noreferrer">Website</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Docs</a> •
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://app.tandoor.dev/e/demo-auto-login/" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://community.tandoor.dev" target="_blank" rel="noopener noreferrer">Community</a> •
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer">Discord</a>
|
||||
</p>
|
||||
|
||||
@@ -17,7 +17,7 @@ from .models import (BookmarkletImport, Comment, CookLog, CustomFilter, Food, Im
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
|
||||
TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
|
||||
ViewLog, ConnectorConfig)
|
||||
ViewLog, ConnectorConfig, AiProvider, AiLog)
|
||||
|
||||
admin.site.login = secure_admin_login(admin.site.login)
|
||||
|
||||
@@ -90,6 +90,20 @@ class SearchPreferenceAdmin(admin.ModelAdmin):
|
||||
admin.site.register(SearchPreference, SearchPreferenceAdmin)
|
||||
|
||||
|
||||
class AiProviderAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'space', 'model',)
|
||||
search_fields = ('name', 'space', 'model',)
|
||||
|
||||
|
||||
admin.site.register(AiProvider, AiProviderAdmin)
|
||||
|
||||
|
||||
class AiLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('ai_provider', 'function', 'credit_cost', 'created_by', 'created_at',)
|
||||
|
||||
admin.site.register(AiLog, AiLogAdmin)
|
||||
|
||||
|
||||
class StorageAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'method')
|
||||
search_fields = ('name',)
|
||||
|
||||
80
cookbook/helper/ai_helper.py
Normal file
80
cookbook/helper/ai_helper.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db.models import Sum
|
||||
from litellm import CustomLogger
|
||||
|
||||
from cookbook.models import AiLog
|
||||
|
||||
|
||||
def get_monthly_token_usage(space):
|
||||
"""
|
||||
returns the number of credits the space has used in the current month
|
||||
"""
|
||||
token_usage = AiLog.objects.filter(space=space, credits_from_balance=False, created_at__month=timezone.now().month).aggregate(Sum('credit_cost'))['credit_cost__sum']
|
||||
if token_usage is None:
|
||||
token_usage = 0
|
||||
return token_usage
|
||||
|
||||
|
||||
def has_monthly_token(space):
|
||||
"""
|
||||
checks if the monthly credit limit has been exceeded
|
||||
"""
|
||||
return get_monthly_token_usage(space) < space.ai_credits_monthly
|
||||
|
||||
|
||||
def can_perform_ai_request(space):
|
||||
return (has_monthly_token(space) or space.ai_credits_balance > 0) and space.ai_enabled
|
||||
|
||||
|
||||
class AiCallbackHandler(CustomLogger):
|
||||
space = None
|
||||
user = None
|
||||
ai_provider = None
|
||||
|
||||
def __init__(self, space, user, ai_provider):
|
||||
super().__init__()
|
||||
self.space = space
|
||||
self.user = user
|
||||
self.ai_provider = ai_provider
|
||||
|
||||
def log_pre_api_call(self, model, messages, kwargs):
|
||||
pass
|
||||
|
||||
def log_post_api_call(self, kwargs, response_obj, start_time, end_time):
|
||||
pass
|
||||
|
||||
def log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
self.create_ai_log(kwargs, response_obj, start_time, end_time)
|
||||
|
||||
def log_failure_event(self, kwargs, response_obj, start_time, end_time):
|
||||
self.create_ai_log(kwargs, response_obj, start_time, end_time)
|
||||
|
||||
def create_ai_log(self, kwargs, response_obj, start_time, end_time):
|
||||
credit_cost = 0
|
||||
credits_from_balance = False
|
||||
if self.ai_provider.log_credit_cost:
|
||||
credit_cost = kwargs.get("response_cost", 0) * 100
|
||||
|
||||
if (not has_monthly_token(self.space)) and self.space.ai_credits_balance > 0:
|
||||
remaining_balance = self.space.ai_credits_balance - Decimal(str(credit_cost))
|
||||
if remaining_balance < 0:
|
||||
remaining_balance = 0
|
||||
|
||||
self.space.ai_credits_balance = remaining_balance
|
||||
credits_from_balance = True
|
||||
self.space.save()
|
||||
|
||||
AiLog.objects.create(
|
||||
created_by=self.user,
|
||||
space=self.space,
|
||||
ai_provider=self.ai_provider,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
input_tokens=response_obj['usage']['prompt_tokens'],
|
||||
output_tokens=response_obj['usage']['completion_tokens'],
|
||||
function=AiLog.F_FILE_IMPORT,
|
||||
credit_cost=credit_cost,
|
||||
credits_from_balance=credits_from_balance,
|
||||
)
|
||||
22
cookbook/helper/batch_edit_helper.py
Normal file
22
cookbook/helper/batch_edit_helper.py
Normal file
@@ -0,0 +1,22 @@
|
||||
def add_to_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
|
||||
"""
|
||||
given a model, the base and related field and the base and related ids, bulk create relation objects
|
||||
"""
|
||||
relation_objects = []
|
||||
for b in base_ids:
|
||||
for r in related_ids:
|
||||
relation_objects.append(relation_model(**{base_field_name: b, related_field_name: r}))
|
||||
relation_model.objects.bulk_create(relation_objects, ignore_conflicts=True, unique_fields=(base_field_name, related_field_name,))
|
||||
|
||||
|
||||
def remove_from_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
|
||||
relation_model.objects.filter(**{f'{base_field_name}__in': base_ids, f'{related_field_name}__in': related_ids}).delete()
|
||||
|
||||
|
||||
def remove_all_from_relation(relation_model, base_field_name, base_ids):
|
||||
relation_model.objects.filter(**{f'{base_field_name}__in': base_ids}).delete()
|
||||
|
||||
|
||||
def set_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
|
||||
remove_all_from_relation(relation_model, base_field_name, base_ids)
|
||||
add_to_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids)
|
||||
@@ -330,6 +330,24 @@ class CustomRecipePermission(permissions.BasePermission):
|
||||
return ((has_group_permission(request.user, ['guest']) and request.method in SAFE_METHODS)
|
||||
or has_group_permission(request.user, ['user'])) and obj.space == request.space
|
||||
|
||||
class CustomAiProviderPermission(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for the AiProvider api endpoint
|
||||
users: can read all
|
||||
admins: can read and write
|
||||
superusers: can read and write + write providers without a space
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view): # user is either at least a user and the request is safe
|
||||
return (has_group_permission(request.user, ['user']) and request.method in SAFE_METHODS) or (has_group_permission(request.user, ['admin']) or request.user.is_superuser)
|
||||
|
||||
# editing of global providers allowed for superusers, space providers by admins and users can read only access
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return ((obj.space is None and request.user.is_superuser)
|
||||
or (obj.space == request.space and has_group_permission(request.user, ['admin']))
|
||||
or (obj.space == request.space and has_group_permission(request.user, ['user']) and request.method in SAFE_METHODS))
|
||||
|
||||
|
||||
class CustomUserPermission(permissions.BasePermission):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 4.2.22 on 2025-09-05 06:51
|
||||
|
||||
import cookbook.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0223_auto_20250831_1111'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='ai_credits_balance',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='ai_credits_monthly',
|
||||
field=models.IntegerField(default=100),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AiProvider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=128)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('api_key', models.CharField(max_length=2048)),
|
||||
('model_name', models.CharField(max_length=256)),
|
||||
('url', models.CharField(blank=True, max_length=2048, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('space', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AiLog',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('function', models.CharField(max_length=64)),
|
||||
('credit_cost', models.DecimalField(decimal_places=4, max_digits=16)),
|
||||
('credits_from_balance', models.BooleanField(default=False)),
|
||||
('input_tokens', models.IntegerField(default=0)),
|
||||
('output_tokens', models.IntegerField(default=0)),
|
||||
('start_time', models.DateTimeField(null=True)),
|
||||
('end_time', models.DateTimeField(null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('ai_provider', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.aiprovider')),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
|
||||
],
|
||||
bases=(models.Model, cookbook.models.PermissionModelMixin),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0225_space_ai_enabled.py
Normal file
18
cookbook/migrations/0225_space_ai_enabled.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.22 on 2025-09-08 19:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0224_space_ai_credits_balance_space_ai_credits_monthly_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='ai_enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.22 on 2025-09-08 20:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0225_space_ai_enabled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='aiprovider',
|
||||
name='log_credit_cost',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='space',
|
||||
name='ai_credits_monthly',
|
||||
field=models.IntegerField(default=10000),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.22 on 2025-09-09 11:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0226_aiprovider_log_credit_cost_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='ai_default_provider',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_ai_default_provider', to='cookbook.aiprovider'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='space',
|
||||
name='ai_credits_balance',
|
||||
field=models.DecimalField(decimal_places=4, default=0, max_digits=16),
|
||||
),
|
||||
]
|
||||
@@ -329,6 +329,11 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
demo = models.BooleanField(default=False)
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
|
||||
ai_enabled = models.BooleanField(default=True)
|
||||
ai_credits_monthly = models.IntegerField(default=100)
|
||||
ai_credits_balance = models.DecimalField(default=0, max_digits=16, decimal_places=4)
|
||||
ai_default_provider = models.ForeignKey("AiProvider", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_ai_default_provider')
|
||||
|
||||
internal_note = models.TextField(blank=True, null=True)
|
||||
|
||||
def safe_delete(self):
|
||||
@@ -341,6 +346,9 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
BookmarkletImport.objects.filter(space=self).delete()
|
||||
CustomFilter.objects.filter(space=self).delete()
|
||||
|
||||
AiLog.objects.filter(space=self).delete()
|
||||
AiProvider.objects.filter(space=self).delete()
|
||||
|
||||
Property.objects.filter(space=self).delete()
|
||||
PropertyType.objects.filter(space=self).delete()
|
||||
|
||||
@@ -393,6 +401,41 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
return self.name
|
||||
|
||||
|
||||
class AiProvider(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
description = models.TextField(blank=True)
|
||||
# AiProviders can be global, so space=null is allowed (configurable by superusers)
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
|
||||
|
||||
api_key = models.CharField(max_length=2048)
|
||||
model_name = models.CharField(max_length=256)
|
||||
url = models.CharField(max_length=2048, blank=True, null=True)
|
||||
log_credit_cost = models.BooleanField(default=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class AiLog(models.Model, PermissionModelMixin):
|
||||
F_FILE_IMPORT = 'FILE_IMPORT'
|
||||
|
||||
ai_provider = models.ForeignKey(AiProvider, on_delete=models.SET_NULL, null=True)
|
||||
function = models.CharField(max_length=64)
|
||||
credit_cost = models.DecimalField(max_digits=16, decimal_places=4)
|
||||
# if credits from balance were used, else its from monthly quota
|
||||
credits_from_balance = models.BooleanField(default=False)
|
||||
|
||||
input_tokens = models.IntegerField(default=0)
|
||||
output_tokens = models.IntegerField(default=0)
|
||||
start_time = models.DateTimeField(null=True)
|
||||
end_time = models.DateTimeField(null=True)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class ConnectorConfig(models.Model, PermissionModelMixin):
|
||||
HOMEASSISTANT = 'HomeAssistant'
|
||||
CONNECTER_TYPE = ((HOMEASSISTANT, 'HomeAssistant'),)
|
||||
|
||||
@@ -24,6 +24,7 @@ from rest_framework.fields import IntegerField
|
||||
|
||||
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
|
||||
from cookbook.helper.HelperFunctions import str2bool
|
||||
from cookbook.helper.ai_helper import get_monthly_token_usage
|
||||
from cookbook.helper.image_processing import is_file_type_allowed
|
||||
from cookbook.helper.permission_helper import above_space_limit
|
||||
from cookbook.helper.property_helper import FoodPropertyHelper
|
||||
@@ -36,7 +37,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
|
||||
ShareLink, ShoppingListEntry, ShoppingListRecipe, Space,
|
||||
Step, Storage, Supermarket, SupermarketCategory,
|
||||
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
|
||||
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields)
|
||||
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider)
|
||||
from cookbook.templatetags.custom_tags import markdown
|
||||
from recipes.settings import AWS_ENABLED, MEDIA_URL, EMAIL_HOST
|
||||
|
||||
@@ -325,11 +326,52 @@ class UserFileViewSerializer(serializers.ModelSerializer):
|
||||
read_only_fields = ('id', 'file', 'file_download', 'file_size_kb', 'preview', 'created_by', 'created_at')
|
||||
|
||||
|
||||
class AiProviderSerializer(serializers.ModelSerializer):
|
||||
api_key = serializers.CharField(required=False, write_only=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data = self.handle_global_space_logic(validated_data)
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
validated_data = self.handle_global_space_logic(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def handle_global_space_logic(self, validated_data):
|
||||
"""
|
||||
allow superusers to create AI providers without a space but make sure everyone else only uses their own space
|
||||
"""
|
||||
if ('space' not in validated_data or not validated_data['space']) and self.context['request'].user.is_superuser:
|
||||
validated_data['space'] = None
|
||||
else:
|
||||
validated_data['space'] = self.context['request'].space
|
||||
|
||||
return validated_data
|
||||
|
||||
class Meta:
|
||||
model = AiProvider
|
||||
fields = ('id', 'name', 'description', 'api_key', 'model_name', 'url', 'log_credit_cost', 'space', 'created_at', 'updated_at')
|
||||
read_only_fields = ('created_at', 'updated_at',)
|
||||
|
||||
|
||||
class AiLogSerializer(serializers.ModelSerializer):
|
||||
ai_provider = AiProviderSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AiLog
|
||||
fields = ('id', 'ai_provider', 'function', 'credit_cost', 'credits_from_balance', 'input_tokens', 'output_tokens', 'start_time', 'end_time', 'created_by', 'created_at',
|
||||
'updated_at')
|
||||
read_only_fields = ('__all__',)
|
||||
|
||||
|
||||
class SpaceSerializer(WritableNestedModelSerializer):
|
||||
created_by = UserSerializer(read_only=True)
|
||||
user_count = serializers.SerializerMethodField('get_user_count')
|
||||
recipe_count = serializers.SerializerMethodField('get_recipe_count')
|
||||
file_size_mb = serializers.SerializerMethodField('get_file_size_mb')
|
||||
ai_monthly_credits_used = serializers.SerializerMethodField('get_ai_monthly_credits_used')
|
||||
ai_default_provider = AiProviderSerializer(required=False, allow_null=True)
|
||||
food_inherit = FoodInheritFieldSerializer(many=True)
|
||||
image = UserFileViewSerializer(required=False, many=False, allow_null=True)
|
||||
nav_logo = UserFileViewSerializer(required=False, many=False, allow_null=True)
|
||||
@@ -350,6 +392,10 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
||||
def get_recipe_count(self, obj):
|
||||
return Recipe.objects.filter(space=obj).count()
|
||||
|
||||
@extend_schema_field(int)
|
||||
def get_ai_monthly_credits_used(self, obj):
|
||||
return get_monthly_token_usage(obj)
|
||||
|
||||
@extend_schema_field(float)
|
||||
def get_file_size_mb(self, obj):
|
||||
try:
|
||||
@@ -360,16 +406,29 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
||||
def create(self, validated_data):
|
||||
raise ValidationError('Cannot create using this endpoint')
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
if 'ai_enabled' in validated_data and not self.context['request'].user.is_superuser:
|
||||
del validated_data['ai_enabled']
|
||||
|
||||
if 'ai_credits_monthly' in validated_data and not self.context['request'].user.is_superuser:
|
||||
del validated_data['ai_credits_monthly']
|
||||
|
||||
if 'ai_credits_balance' in validated_data and not self.context['request'].user.is_superuser:
|
||||
del validated_data['ai_credits_balance']
|
||||
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Space
|
||||
fields = (
|
||||
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
|
||||
'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb',
|
||||
'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color',
|
||||
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg',)
|
||||
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg', 'ai_credits_monthly',
|
||||
'ai_credits_balance', 'ai_monthly_credits_used', 'ai_enabled', 'ai_default_provider')
|
||||
read_only_fields = (
|
||||
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
|
||||
'demo',)
|
||||
'demo', 'ai_monthly_credits_used')
|
||||
|
||||
|
||||
class UserSpaceSerializer(WritableNestedModelSerializer):
|
||||
@@ -1038,7 +1097,7 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'working_time',
|
||||
'waiting_time', 'created_by', 'created_at', 'updated_at',
|
||||
'internal', 'private','servings', 'servings_text', 'rating', 'last_cooked', 'new', 'recent'
|
||||
'internal', 'private', 'servings', 'servings_text', 'rating', 'last_cooked', 'new', 'recent'
|
||||
)
|
||||
# TODO having these readonly fields makes "RecipeOverview.ts" (API Client) not generate the RecipeOverviewToJSON second else block which leads to errors when using the api
|
||||
# TODO find a solution (custom schema?) to have these fields readonly (to save performance) and generate a proper client (two serializers would probably do the trick)
|
||||
@@ -1134,6 +1193,35 @@ class RecipeBatchUpdateSerializer(serializers.Serializer):
|
||||
clear_description = serializers.BooleanField(required=False, allow_null=True)
|
||||
|
||||
|
||||
class FoodBatchUpdateSerializer(serializers.Serializer):
|
||||
foods = serializers.ListField(child=serializers.IntegerField())
|
||||
|
||||
category = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
substitute_add = serializers.ListField(child=serializers.IntegerField())
|
||||
substitute_remove = serializers.ListField(child=serializers.IntegerField())
|
||||
substitute_set = serializers.ListField(child=serializers.IntegerField())
|
||||
substitute_remove_all = serializers.BooleanField(default=False)
|
||||
|
||||
inherit_fields_add = serializers.ListField(child=serializers.IntegerField())
|
||||
inherit_fields_remove = serializers.ListField(child=serializers.IntegerField())
|
||||
inherit_fields_set = serializers.ListField(child=serializers.IntegerField())
|
||||
inherit_fields_remove_all = serializers.BooleanField(default=False)
|
||||
|
||||
child_inherit_fields_add = serializers.ListField(child=serializers.IntegerField())
|
||||
child_inherit_fields_remove = serializers.ListField(child=serializers.IntegerField())
|
||||
child_inherit_fields_set = serializers.ListField(child=serializers.IntegerField())
|
||||
child_inherit_fields_remove_all = serializers.BooleanField(default=False)
|
||||
|
||||
substitute_children = serializers.BooleanField(required=False, allow_null=True)
|
||||
substitute_siblings = serializers.BooleanField(required=False, allow_null=True)
|
||||
ignore_shopping = serializers.BooleanField(required=False, allow_null=True)
|
||||
on_hand = serializers.BooleanField(required=False, allow_null=True)
|
||||
|
||||
parent_remove = serializers.BooleanField(required=False, allow_null=True)
|
||||
parent_set = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
|
||||
class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
shared = UserSerializer(many=True, required=False)
|
||||
|
||||
@@ -1564,7 +1652,6 @@ class ServerSettingsSerializer(serializers.Serializer):
|
||||
# TODO add all other relevant settings including path/url related ones?
|
||||
shopping_min_autosync_interval = serializers.CharField()
|
||||
enable_pdf_export = serializers.BooleanField()
|
||||
enable_ai_import = serializers.BooleanField()
|
||||
disable_external_connectors = serializers.BooleanField()
|
||||
terms_url = serializers.CharField()
|
||||
privacy_url = serializers.CharField()
|
||||
@@ -1788,6 +1875,7 @@ class RecipeFromSourceResponseSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class AiImportSerializer(serializers.Serializer):
|
||||
ai_provider_id = serializers.IntegerField()
|
||||
file = serializers.FileField(allow_null=True)
|
||||
text = serializers.CharField(allow_null=True, allow_blank=True)
|
||||
recipe_id = serializers.CharField(allow_null=True, allow_blank=True)
|
||||
|
||||
162
cookbook/tests/api/test_api_ai_provider.py
Normal file
162
cookbook/tests/api/test_api_ai_provider.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import MealType, PropertyType, AiProvider
|
||||
|
||||
LIST_URL = 'api:aiprovider-list'
|
||||
DETAIL_URL = 'api:aiprovider-detail'
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def obj_1(space_1, a1_s1):
|
||||
return AiProvider.objects.get_or_create(name='test_1', space=space_1)[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def obj_2(space_1, a1_s1):
|
||||
return AiProvider.objects.get_or_create(name='test_2', space=None)[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 200],
|
||||
['a1_s1', 200],
|
||||
])
|
||||
def test_list_permission(arg, request):
|
||||
c = request.getfixturevalue(arg[0])
|
||||
assert c.get(reverse(LIST_URL)).status_code == arg[1]
|
||||
|
||||
|
||||
def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 2
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
|
||||
obj_1.space = space_2
|
||||
obj_1.save()
|
||||
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 403],
|
||||
['a1_s1', 200],
|
||||
['g1_s2', 403],
|
||||
['u1_s2', 403],
|
||||
['a1_s2', 404],
|
||||
])
|
||||
def test_update(arg, request, obj_1):
|
||||
c = request.getfixturevalue(arg[0])
|
||||
r = c.patch(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_1.id}
|
||||
),
|
||||
{'name': 'new'},
|
||||
content_type='application/json'
|
||||
)
|
||||
response = json.loads(r.content)
|
||||
assert r.status_code == arg[1]
|
||||
if r.status_code == 200:
|
||||
assert response['name'] == 'new'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 403],
|
||||
['a1_s1', 403],
|
||||
['g1_s2', 403],
|
||||
['u1_s2', 403],
|
||||
['a1_s2', 403],
|
||||
['s1_s1', 200],
|
||||
])
|
||||
def test_update_global(arg, request, obj_2):
|
||||
c = request.getfixturevalue(arg[0])
|
||||
r = c.patch(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_2.id}
|
||||
),
|
||||
{'name': 'new'},
|
||||
content_type='application/json'
|
||||
)
|
||||
response = json.loads(r.content)
|
||||
assert r.status_code == arg[1]
|
||||
if r.status_code == 200:
|
||||
assert response['name'] == 'new'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 403],
|
||||
['a1_s1', 201],
|
||||
])
|
||||
def test_add(arg, request, u1_s2):
|
||||
c = request.getfixturevalue(arg[0])
|
||||
r = c.post(
|
||||
reverse(LIST_URL),
|
||||
{'name': 'test', 'api_key': 'test', 'model_name': 'test'},
|
||||
content_type='application/json'
|
||||
)
|
||||
response = json.loads(r.content)
|
||||
assert r.status_code == arg[1]
|
||||
if r.status_code == 201:
|
||||
assert response['name'] == 'test'
|
||||
r = c.get(reverse(DETAIL_URL, args={response['id']}))
|
||||
assert r.status_code == 200
|
||||
r = u1_s2.get(reverse(DETAIL_URL, args={response['id']}))
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_delete(a1_s1, a1_s2, obj_1):
|
||||
# admins cannot delete foreign space providers
|
||||
r = a1_s2.delete(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_1.id}
|
||||
)
|
||||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
# admins can delete their space providers
|
||||
r = a1_s1.delete(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_1.id}
|
||||
)
|
||||
)
|
||||
|
||||
assert r.status_code == 204
|
||||
with scopes_disabled():
|
||||
assert AiProvider.objects.count() == 0
|
||||
|
||||
|
||||
def test_delete_global(a1_s1, s1_s1, obj_2):
|
||||
# admins cant delete global providers
|
||||
r = a1_s1.delete(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_2.id}
|
||||
)
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
# superusers can delete global providers
|
||||
r = s1_s1.delete(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={obj_2.id}
|
||||
)
|
||||
)
|
||||
|
||||
assert r.status_code == 204
|
||||
with scopes_disabled():
|
||||
assert AiProvider.objects.count() == 0
|
||||
@@ -298,3 +298,11 @@ def a1_s2(client, space_2):
|
||||
@pytest.fixture()
|
||||
def a2_s2(client, space_2):
|
||||
return create_user(client, space_2, group='admin')
|
||||
|
||||
@pytest.fixture()
|
||||
def s1_s1(client, space_1):
|
||||
client = create_user(client, space_1, group='admin')
|
||||
user = auth.get_user(client)
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
return client
|
||||
|
||||
@@ -61,6 +61,8 @@ router.register(r'search-preference', api.SearchPreferenceViewSet)
|
||||
router.register(r'user-space', api.UserSpaceViewSet)
|
||||
router.register(r'view-log', api.ViewLogViewSet)
|
||||
router.register(r'access-token', api.AccessTokenViewSet)
|
||||
router.register(r'ai-provider', api.AiProviderViewSet)
|
||||
router.register(r'ai-log', api.AiLogViewSet)
|
||||
|
||||
router.register(r'localization', api.LocalizationViewSet, basename='localization')
|
||||
router.register(r'server-settings', api.ServerSettingsViewSet, basename='server-settings')
|
||||
|
||||
@@ -65,6 +65,8 @@ from cookbook.connectors.connector_manager import ConnectorManager, ActionType
|
||||
from cookbook.forms import ImportForm, ImportExportBase
|
||||
from cookbook.helper import recipe_url_import as helper
|
||||
from cookbook.helper.HelperFunctions import str2bool, validate_import_url
|
||||
from cookbook.helper.ai_helper import has_monthly_token, can_perform_ai_request, AiCallbackHandler
|
||||
from cookbook.helper.batch_edit_helper import add_to_relation, remove_from_relation, remove_all_from_relation, set_relation
|
||||
from cookbook.helper.image_processing import handle_image
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.helper.open_data_importer import OpenDataImporter
|
||||
@@ -74,7 +76,7 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, Cus
|
||||
CustomTokenHasScope, CustomUserPermission, IsReadOnlyDRF,
|
||||
above_space_limit,
|
||||
group_required, has_group_permission, is_space_owner,
|
||||
switch_user_active_space
|
||||
switch_user_active_space, CustomAiProviderPermission
|
||||
)
|
||||
from cookbook.helper.recipe_search import RecipeSearch
|
||||
from cookbook.helper.recipe_url_import import clean_dict, get_from_youtube_scraper, get_images_from_soup
|
||||
@@ -85,7 +87,7 @@ from cookbook.models import (Automation, BookmarkletImport, ConnectorConfig, Coo
|
||||
RecipeBookEntry, ShareLink, ShoppingListEntry,
|
||||
ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory,
|
||||
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
|
||||
UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields
|
||||
UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields, AiLog, AiProvider
|
||||
)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
@@ -110,12 +112,13 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, Au
|
||||
UserSerializer, UserSpaceSerializer, ViewLogSerializer,
|
||||
LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer, FdcQuerySerializer,
|
||||
AiImportSerializer, ImportOpenDataSerializer, ImportOpenDataMetaDataSerializer, ImportOpenDataResponseSerializer, ExportRequestSerializer,
|
||||
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer
|
||||
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer,
|
||||
AiProviderSerializer, AiLogSerializer, FoodBatchUpdateSerializer
|
||||
)
|
||||
from cookbook.version_info import TANDOOR_VERSION
|
||||
from cookbook.views.import_export import get_integration
|
||||
from recipes import settings
|
||||
from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY, AI_RATELIMIT, AI_API_KEY, AI_MODEL_NAME
|
||||
from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY, AI_RATELIMIT
|
||||
|
||||
DateExample = OpenApiExample('Date Format', value='1972-12-05', request_only=True)
|
||||
BeforeDateExample = OpenApiExample('Before Date Format', value='-1972-12-05', request_only=True)
|
||||
@@ -617,6 +620,29 @@ class SearchPreferenceViewSet(LoggingMixin, viewsets.ModelViewSet):
|
||||
return self.queryset.filter(user=self.request.user)
|
||||
|
||||
|
||||
class AiProviderViewSet(LoggingMixin, viewsets.ModelViewSet):
|
||||
queryset = AiProvider.objects
|
||||
serializer_class = AiProviderSerializer
|
||||
permission_classes = [CustomAiProviderPermission & CustomTokenHasReadWriteScope]
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
def get_queryset(self):
|
||||
# read only access to all space and global AiProviders
|
||||
with scopes_disabled():
|
||||
return self.queryset.filter(Q(space=self.request.space) | Q(space__isnull=True))
|
||||
|
||||
|
||||
class AiLogViewSet(LoggingMixin, viewsets.ModelViewSet):
|
||||
queryset = AiLog.objects
|
||||
serializer_class = AiLogSerializer
|
||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||
http_method_names = ['get']
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(space=self.request.space)
|
||||
|
||||
|
||||
class StorageViewSet(LoggingMixin, viewsets.ModelViewSet):
|
||||
# TODO handle delete protect error and adjust test
|
||||
queryset = Storage.objects
|
||||
@@ -915,6 +941,94 @@ class FoodViewSet(LoggingMixin, TreeMixin):
|
||||
content = {'error': True, 'msg': e.args[0]}
|
||||
return Response(content, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@decorators.action(detail=False, methods=['PUT'], serializer_class=FoodBatchUpdateSerializer)
|
||||
def batch_update(self, request):
|
||||
serializer = self.serializer_class(data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
foods = Food.objects.filter(id__in=serializer.validated_data['foods'], space=self.request.space)
|
||||
safe_food_ids = Food.objects.filter(id__in=serializer.validated_data['foods'], space=self.request.space).values_list('id', flat=True)
|
||||
|
||||
if 'category' in serializer.validated_data:
|
||||
foods.update(supermarket_category_id=serializer.validated_data['category'])
|
||||
|
||||
if 'ignore_shopping' in serializer.validated_data and serializer.validated_data['ignore_shopping'] is not None:
|
||||
foods.update(ignore_shopping=serializer.validated_data['ignore_shopping'])
|
||||
|
||||
if 'on_hand' in serializer.validated_data and serializer.validated_data['on_hand'] is not None:
|
||||
if serializer.validated_data['on_hand']:
|
||||
user_relation = []
|
||||
for f in safe_food_ids:
|
||||
user_relation.append(Food.onhand_users.through(food_id=f, user_id=request.user.id))
|
||||
Food.onhand_users.through.objects.bulk_create(user_relation, ignore_conflicts=True, unique_fields=('food_id', 'user_id',))
|
||||
else:
|
||||
Food.onhand_users.through.objects.filter(food_id__in=safe_food_ids, user_id=request.user.id).delete()
|
||||
|
||||
if 'substitute_children' in serializer.validated_data and serializer.validated_data['substitute_children'] is not None:
|
||||
foods.update(substitute_children=serializer.validated_data['substitute_children'])
|
||||
|
||||
if 'substitute_siblings' in serializer.validated_data and serializer.validated_data['substitute_siblings'] is not None:
|
||||
foods.update(substitute_siblings=serializer.validated_data['substitute_siblings'])
|
||||
|
||||
# ---------- substitutes -------------
|
||||
if 'substitute_add' in serializer.validated_data:
|
||||
add_to_relation(Food.substitute.through, 'from_food_id', safe_food_ids, 'to_food_id', serializer.validated_data['substitute_add'])
|
||||
|
||||
if 'substitute_remove' in serializer.validated_data:
|
||||
remove_from_relation(Food.substitute.through, 'from_food_id', safe_food_ids, 'to_food_id', serializer.validated_data['substitute_remove'])
|
||||
|
||||
if 'substitute_set' in serializer.validated_data and len(serializer.validated_data['substitute_set']) > 0:
|
||||
set_relation(Food.substitute.through, 'from_food_id', safe_food_ids, 'to_food_id', serializer.validated_data['substitute_set'])
|
||||
|
||||
if 'substitute_remove_all' in serializer.validated_data and serializer.validated_data['substitute_remove_all']:
|
||||
remove_all_from_relation(Food.substitute.through, 'from_food_id', safe_food_ids)
|
||||
|
||||
# ---------- inherit fields -------------
|
||||
if 'inherit_fields_add' in serializer.validated_data:
|
||||
add_to_relation(Food.inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['inherit_fields_add'])
|
||||
|
||||
if 'inherit_fields_remove' in serializer.validated_data:
|
||||
remove_from_relation(Food.inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['inherit_fields_remove'])
|
||||
|
||||
if 'inherit_fields_set' in serializer.validated_data and len(serializer.validated_data['inherit_fields_set']) > 0:
|
||||
set_relation(Food.inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['inherit_fields_set'])
|
||||
|
||||
if 'inherit_fields_remove_all' in serializer.validated_data and serializer.validated_data['inherit_fields_remove_all']:
|
||||
remove_all_from_relation(Food.inherit_fields.through, 'food_id', safe_food_ids)
|
||||
|
||||
# ---------- child inherit fields -------------
|
||||
if 'child_inherit_fields_add' in serializer.validated_data:
|
||||
add_to_relation(Food.child_inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['child_inherit_fields_add'])
|
||||
|
||||
if 'child_inherit_fields_remove' in serializer.validated_data:
|
||||
remove_from_relation(Food.child_inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['child_inherit_fields_remove'])
|
||||
|
||||
if 'child_inherit_fields_set' in serializer.validated_data and len(serializer.validated_data['child_inherit_fields_set']) > 0:
|
||||
set_relation(Food.child_inherit_fields.through, 'food_id', safe_food_ids, 'foodinheritfield_id', serializer.validated_data['child_inherit_fields_set'])
|
||||
|
||||
if 'child_inherit_fields_remove_all' in serializer.validated_data and serializer.validated_data['child_inherit_fields_remove_all']:
|
||||
remove_all_from_relation(Food.child_inherit_fields.through, 'food_id', safe_food_ids)
|
||||
|
||||
# ------- parent --------
|
||||
if self.model.node_order_by:
|
||||
node_location = 'sorted'
|
||||
else:
|
||||
node_location = 'last'
|
||||
|
||||
if 'parent_remove' in serializer.validated_data and serializer.validated_data['parent_remove']:
|
||||
for f in foods:
|
||||
f.move(Food.get_first_root_node(), f'{node_location}-sibling')
|
||||
|
||||
if 'parent_set' in serializer.validated_data:
|
||||
parent_food = Food.objects.filter(space=request.space, id=serializer.validated_data['parent_set']).first()
|
||||
if parent_food:
|
||||
for f in foods:
|
||||
f.move(parent_food, f'{node_location}-child')
|
||||
|
||||
return Response({}, 200)
|
||||
|
||||
return Response(serializer.errors, 400)
|
||||
|
||||
|
||||
@extend_schema_view(list=extend_schema(parameters=[
|
||||
OpenApiParameter(name='order_field', description='Field to order recipe books on', type=str,
|
||||
@@ -2000,6 +2114,24 @@ class AiImportView(APIView):
|
||||
if serializer.is_valid():
|
||||
# TODO max file size check
|
||||
|
||||
if 'ai_provider_id' not in serializer.validated_data:
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': _('You must select an AI provider to perform your request.'),
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not can_perform_ai_request(request.space):
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': _("You don't have any credits remaining to use AI or AI features are not enabled for your space."),
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
ai_provider = AiProvider.objects.filter(pk=serializer.validated_data['ai_provider_id']).filter(Q(space=request.space) | Q(space__isnull=True)).first()
|
||||
|
||||
litellm.callbacks = [AiCallbackHandler(request.space, request.user, ai_provider)]
|
||||
|
||||
messages = []
|
||||
uploaded_file = serializer.validated_data['file']
|
||||
|
||||
@@ -2068,7 +2200,15 @@ class AiImportView(APIView):
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
ai_response = completion(api_key=AI_API_KEY, model=AI_MODEL_NAME, response_format={"type": "json_object"}, messages=messages, )
|
||||
ai_request = {
|
||||
'api_key': ai_provider.api_key,
|
||||
'model': ai_provider.model_name,
|
||||
'response_format': {"type": "json_object"},
|
||||
'messages': messages,
|
||||
}
|
||||
if ai_provider.url:
|
||||
ai_request['api_base'] = ai_provider.url
|
||||
ai_response = completion(**ai_request)
|
||||
except BadRequestError as err:
|
||||
response = {
|
||||
'error': True,
|
||||
@@ -2373,7 +2513,6 @@ class ServerSettingsViewSet(viewsets.GenericViewSet):
|
||||
# Attention: No login required, do not return sensitive data
|
||||
s['shopping_min_autosync_interval'] = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL
|
||||
s['enable_pdf_export'] = settings.ENABLE_PDF_EXPORT
|
||||
s['enable_ai_import'] = settings.AI_API_KEY != ''
|
||||
s['disable_external_connectors'] = settings.DISABLE_EXTERNAL_CONNECTORS
|
||||
s['terms_url'] = settings.TERMS_URL
|
||||
s['privacy_url'] = settings.PRIVACY_URL
|
||||
@@ -2546,10 +2685,9 @@ def ingredient_from_string(request):
|
||||
|
||||
if unit:
|
||||
if unit_obj := Unit.objects.filter(space=request.space).filter(Q(name=unit) | Q(plural_name=unit)).first():
|
||||
ingredient['food'] = {'name': unit_obj.name, 'id': unit_obj.id}
|
||||
ingredient['unit'] = {'name': unit_obj.name, 'id': unit_obj.id}
|
||||
else:
|
||||
unit_obj = Unit.objects.create(space=request.space, name=unit)
|
||||
ingredient['food'] = {'name': unit_obj.name, 'id': unit_obj.id}
|
||||
ingredient['unit'] = {'name': unit.name, 'id': unit.id}
|
||||
ingredient['unit'] = {'name': unit_obj.name, 'id': unit_obj.id}
|
||||
|
||||
return JsonResponse(ingredient, status=200)
|
||||
|
||||
@@ -97,7 +97,8 @@ def space_overview(request):
|
||||
max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES,
|
||||
max_users=settings.SPACE_DEFAULT_MAX_USERS,
|
||||
allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING,
|
||||
)
|
||||
ai_enabled=settings.SPACE_AI_ENABLED,
|
||||
ai_credits_monthly=settings.SPACE_AI_CREDITS_MONTHLY,)
|
||||
|
||||
user_space = UserSpace.objects.create(space=created_space, user=request.user, active=False)
|
||||
user_space.groups.add(Group.objects.filter(name='admin').get())
|
||||
|
||||
18
docs/features/ai.md
Normal file
18
docs/features/ai.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Tandoor has several AI based features. To allow maximum flexibility, you can configure different AI providers and select them based on the task you want to perform.
|
||||
To prevent accidental cost escalation Tandoor has a robust system of tracking and limiting AI costs.
|
||||
|
||||
## Default Configuration
|
||||
By default the AI features are enabled for every space. Each space has a spending limit of roughly 1 USD per month.
|
||||
This can be changed using the [configuration variables](https://docs.tandoor.dev/system/configuration/#ai-integration)
|
||||
|
||||
You can change these settings any time using the django admin. If you do not care about AI cost you can enter a very high limit or disable cost tracking for your providers.
|
||||
The limit resets on the first of every month.
|
||||
|
||||
## Configure AI Providers
|
||||
When AI support is enabled for a space every user in a space can configure AI providers.
|
||||
The models shown in the editor have been tested and work with Tandoor. Most other models that can parse images/files and return text should also work.
|
||||
|
||||
Superusers also have the ability to configure global AI providers that every space can use.
|
||||
|
||||
## AI Log
|
||||
The AI Log allows you to track the usage of AI calls. Here you can also see the usage.
|
||||
@@ -196,6 +196,7 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://unix:/var/www/recipes/recipes.sock;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -472,15 +472,20 @@ S3_CUSTOM_DOMAIN= # when using a CDN/proxy to S3 (see https://github.com/Tandoor
|
||||
|
||||
#### AI Integration
|
||||
|
||||
To use AI to perform different tasks you need to configure an API key and the AI provider. [LiteLLM](https://www.litellm.ai/) is used
|
||||
to make a standardized request to different AI providers of your liking.
|
||||
|
||||
Configuring this via environment parameters is a temporary solution. In the future I plan on adding support for multiple AI providers per Tandoor instance
|
||||
with the option to select them for various tasks. For now only gemini 2.0 flash has been tested but feel free to try out other models.
|
||||
Most AI features are configured trough the AI Provider settings in the Tandoor web interface. Some defaults can be set for new spaces on your instance.
|
||||
|
||||
Enables AI features for spaces by default
|
||||
```
|
||||
SPACE_AI_ENABLED=1
|
||||
```
|
||||
|
||||
Sets the monthly default credit limit for AI usage
|
||||
```
|
||||
SPACE_AI_CREDITS_MONTHLY=100
|
||||
```
|
||||
|
||||
Ratelimit for AI API
|
||||
```
|
||||
AI_API_KEY=
|
||||
AI_MODEL_NAME=gemini/gemini-2.0-flash
|
||||
AI_RATELIMIT=60/hour
|
||||
```
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ SPACE_DEFAULT_MAX_RECIPES = int(os.getenv('SPACE_DEFAULT_MAX_RECIPES', 0))
|
||||
SPACE_DEFAULT_MAX_USERS = int(os.getenv('SPACE_DEFAULT_MAX_USERS', 0))
|
||||
SPACE_DEFAULT_MAX_FILES = int(os.getenv('SPACE_DEFAULT_MAX_FILES', 0))
|
||||
SPACE_DEFAULT_ALLOW_SHARING = extract_bool('SPACE_DEFAULT_ALLOW_SHARING', True)
|
||||
SPACE_AI_ENABLED = extract_bool('SPACE_AI_ENABLED', True)
|
||||
SPACE_AI_CREDITS_MONTHLY = int(os.getenv('SPACE_AI_CREDITS_MONTHLY', 10000))
|
||||
|
||||
INTERNAL_IPS = extract_comma_list('INTERNAL_IPS', '127.0.0.1')
|
||||
|
||||
@@ -137,8 +139,6 @@ HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
|
||||
|
||||
FDC_API_KEY = os.getenv('FDC_API_KEY', 'DEMO_KEY')
|
||||
|
||||
AI_API_KEY = os.getenv('AI_API_KEY', '')
|
||||
AI_MODEL_NAME = os.getenv('AI_MODEL_NAME', 'gemini/gemini-2.0-flash')
|
||||
AI_RATELIMIT = os.getenv('AI_RATELIMIT', '60/hour')
|
||||
|
||||
SHARING_ABUSE = extract_bool('SHARING_ABUSE', False)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Django==4.2.22
|
||||
Django==4.2.24
|
||||
cryptography===45.0.5
|
||||
django-annoying==0.10.6
|
||||
django-cleanup==9.0.0
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 13 KiB |
186
vue3/src/components/dialogs/BatchEditFoodDialog.vue
Normal file
186
vue3/src/components/dialogs/BatchEditFoodDialog.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<v-dialog max-width="1200px" :activator="props.activator" v-model="dialog">
|
||||
<v-card :loading="loading">
|
||||
<v-closable-card-title
|
||||
:title="$t('BatchEdit')"
|
||||
:sub-title="$t('BatchEditUpdatingItemsCount', {type: $t('Foods'), count: updateItems.length})"
|
||||
:icon="TFood.icon"
|
||||
v-model="dialog"
|
||||
></v-closable-card-title>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
|
||||
<v-form>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card :title="$t('Miscellaneous')" prepend-icon="fa-solid fa-list" variant="flat">
|
||||
<v-card-text>
|
||||
<model-select model="SupermarketCategory" v-model="batchUpdateRequest.foodBatchUpdate.category" :object="false" allow-create mode="single">
|
||||
</model-select>
|
||||
|
||||
<v-select :items="boolUpdateOptions" :label="$t('Ignore_Shopping')" clearable v-model="batchUpdateRequest.foodBatchUpdate.ignoreShopping"></v-select>
|
||||
<v-select :items="boolUpdateOptions" :label="$t('OnHand')" clearable v-model="batchUpdateRequest.foodBatchUpdate.onHand"></v-select>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-label :text="$t('Substitutes')"></v-label>
|
||||
<model-select model="Food" v-model="batchUpdateRequest.foodBatchUpdate.substituteAdd" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-add"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="Food" v-model="batchUpdateRequest.foodBatchUpdate.substituteRemove" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-minus"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="Food" v-model="batchUpdateRequest.foodBatchUpdate.substituteSet" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-equals"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<v-checkbox :label="$t('RemoveAllType', {type: $t('Substitutes')})" hide-details
|
||||
v-model="batchUpdateRequest.foodBatchUpdate.substituteRemoveAll"></v-checkbox>
|
||||
|
||||
<v-select :items="boolUpdateOptions" :label="$t('substitute_siblings')" clearable v-model="batchUpdateRequest.foodBatchUpdate.substituteChildren"></v-select>
|
||||
<v-select :items="boolUpdateOptions" :label="$t('substitute_children')" clearable v-model="batchUpdateRequest.foodBatchUpdate.substituteSiblings"></v-select>
|
||||
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card :title="$t('Hierarchy')" prepend-icon="fa-solid fa-folder-tree" variant="flat">
|
||||
<v-card-text>
|
||||
<model-select model="Food" :label="$t('Parent')" :object="false" allow-create clearable v-model="batchUpdateRequest.foodBatchUpdate.parentSet">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-equals"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
|
||||
<v-select :items="boolUpdateOptions" :label="$t('RemoveParent')" clearable v-model="batchUpdateRequest.foodBatchUpdate.parentRemove"></v-select>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-label :text="$t('InheritFields')"></v-label>
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.inheritFieldsAdd" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-add"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.inheritFieldsRemove" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-minus"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.inheritFieldsSet" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-equals"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<v-checkbox :label="$t('RemoveAllType', {type: $t('InheritFields')})" hide-details
|
||||
v-model="batchUpdateRequest.foodBatchUpdate.inheritFieldsRemoveAll"></v-checkbox>
|
||||
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-label :text="$t('ChildInheritFields')"></v-label>
|
||||
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.childInheritFieldsAdd" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-add"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.childInheritFieldsRemove" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-minus"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<model-select model="FoodInheritField" v-model="batchUpdateRequest.foodBatchUpdate.childInheritFieldsSet" :object="false" allow-create mode="tags">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-equals"></v-icon>
|
||||
</template>
|
||||
</model-select>
|
||||
<v-checkbox :label="$t('RemoveAllType', {type: $t('ChildInheritFields')})" hide-details
|
||||
v-model="batchUpdateRequest.foodBatchUpdate.childInheritFieldsRemoveAll"></v-checkbox>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
|
||||
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn :disabled="loading" @click="dialog = false">{{ $t('Cancel') }}</v-btn>
|
||||
<v-btn color="warning" :loading="loading" @click="batchUpdateFoods()" :disabled="updateItems.length < 1">{{ $t('Update') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType, ref, watch} from "vue";
|
||||
import {EditorSupportedModels, EditorSupportedTypes, getGenericModelFromString, TFood, TKeyword, TRecipe} from "@/types/Models.ts";
|
||||
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {ApiApi, ApiFoodBatchUpdateUpdateRequest, ApiRecipeBatchUpdateUpdateRequest, Food, Recipe, RecipeOverview} from "@/openapi";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const props = defineProps({
|
||||
items: {type: Array as PropType<Array<Food>>, required: true},
|
||||
activator: {type: String, default: 'parent'},
|
||||
})
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const dialog = defineModel<boolean>({default: false})
|
||||
const loading = ref(false)
|
||||
|
||||
const updateItems = ref([] as Food[])
|
||||
const batchUpdateRequest = ref({foodBatchUpdate: {}} as ApiFoodBatchUpdateUpdateRequest)
|
||||
|
||||
const boolUpdateOptions = ref([
|
||||
{value: true, title: t('Yes')},
|
||||
{value: false, title: t('No')},
|
||||
])
|
||||
|
||||
/**
|
||||
* copy prop when dialog opens so that items remain when parent is updated after change is emitted
|
||||
*/
|
||||
watch(dialog, (newValue, oldValue) => {
|
||||
if (!oldValue && newValue && props.items != undefined) {
|
||||
batchUpdateRequest.value.foodBatchUpdate.foods = props.items.flatMap(r => r.id!)
|
||||
updateItems.value = JSON.parse(JSON.stringify(props.items))
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* perform batch request to update recipes
|
||||
*/
|
||||
function batchUpdateFoods() {
|
||||
let api = new ApiApi()
|
||||
loading.value = true
|
||||
|
||||
api.apiFoodBatchUpdateUpdate(batchUpdateRequest.value).then(r => {
|
||||
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
}).finally(() => {
|
||||
emit('change')
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -14,6 +14,7 @@
|
||||
<v-list-item link title="Space" @click="window = 'space'" prepend-icon="fa-solid fa-database"></v-list-item>
|
||||
<v-list-item link :title="$t('Recipes')" @click="window = 'recipes'" prepend-icon="$recipes"></v-list-item>
|
||||
<v-list-item link :title="$t('Import')" @click="window = 'import'" prepend-icon="$import"></v-list-item>
|
||||
<v-list-item link :title="$t('AI')" @click="window = 'ai'" prepend-icon="$ai"></v-list-item>
|
||||
<v-list-item link :title="$t('Unit')" @click="window = 'unit'" prepend-icon="fa-solid fa-scale-balanced"></v-list-item>
|
||||
<v-list-item link :title="$t('Food')" @click="window = 'food'" prepend-icon="fa-solid fa-carrot"></v-list-item>
|
||||
<v-list-item link :title="$t('Keyword')" @click="window = 'keyword'" prepend-icon="fa-solid fa-tags"></v-list-item>
|
||||
@@ -45,7 +46,7 @@
|
||||
<v-btn class="mt-2 ms-2" color="info" href="https://github.com/TandoorRecipes/recipes" target="_blank" prepend-icon="fa-solid fa-code-branch">GitHub
|
||||
</v-btn>
|
||||
|
||||
<v-alert class="mt-3" border="start" variant="tonal" color="success">
|
||||
<v-alert class="mt-3" border="start" variant="tonal" color="success" v-if="(!useUserPreferenceStore().serverSettings.hosted && !useUserPreferenceStore().activeSpace.demo)">
|
||||
<v-alert-title>Did you know?</v-alert-title>
|
||||
Tandoor is Open Source and available to anyone for free to host on their own server. Thousands of hours have been spend
|
||||
making Tandoor what it is today. You can help make Tandoor even better by contributing or helping financing the effort.
|
||||
@@ -105,6 +106,35 @@
|
||||
|
||||
<v-btn color="primary" variant="tonal" prepend-icon="$import" class="me-2" :to="{name: 'RecipeImportPage'}">{{ $t('Import') }}</v-btn>
|
||||
|
||||
</v-window-item>
|
||||
<v-window-item value="ai">
|
||||
<p class="mt-3">Tandoor has several functions that allow you to use AI to automatically perform certain tasks like importing recipes from a PDFs or images.
|
||||
</p>
|
||||
|
||||
<p class="mt-3" v-if="useUserPreferenceStore().serverSettings.hosted">
|
||||
To use AI you must first configure an AI Provider. This can also be done globally for all spaces by the person operating your Tandoor Server.
|
||||
</p>
|
||||
<p class="mt-3" v-if="!useUserPreferenceStore().serverSettings.hosted">
|
||||
Some AI Providers are available globally for every space to use. You can also configure additional AI Providers for your space only.
|
||||
</p>
|
||||
|
||||
<p class="mt-3" v-if="useUserPreferenceStore().serverSettings.hosted">
|
||||
To prevent accidental AI cost you can review your AI usage using the AI Log. The Server Administrator can also set AI usage limits for your space (either monthly or using a balance).
|
||||
</p>
|
||||
<p class="mt-3" v-if="!useUserPreferenceStore().serverSettings.hosted">
|
||||
Depending on your subscription you will have different AI Credits available for your space every month. Additionally you might have a Credit balance
|
||||
that will be used once your monthly limit is reached.
|
||||
</p>
|
||||
|
||||
<v-btn color="primary" variant="tonal" prepend-icon="$ai" class="me-2" :to="{name: 'ModelListPage', params: {model: 'AiProvider'}}">
|
||||
{{ $t('AiProvider') }}
|
||||
</v-btn>
|
||||
<v-btn color="primary" variant="tonal" prepend-icon="$ai" class="me-2" :to="{name: 'ModelListPage', params: {model: 'AiLog'}}">
|
||||
{{ $t('AiLog') }}
|
||||
</v-btn>
|
||||
<v-btn color="primary" variant="tonal" prepend-icon="$ai" class="me-2" :to="{name: 'SpaceSettings'}">{{ $t('SpaceSettings') }}</v-btn>
|
||||
<v-btn color="primary" variant="tonal" prepend-icon="$import" class="me-2" :to="{name: 'RecipeImportPage'}">{{ $t('Import') }}</v-btn>
|
||||
|
||||
</v-window-item>
|
||||
<v-window-item value="unit">
|
||||
<p class="mt-3">Units allow you to measure how much of something you need in a recipe or on a shopping list.
|
||||
@@ -337,6 +367,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ref} from "vue";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
|
||||
const drawer = defineModel()
|
||||
const window = ref('start')
|
||||
|
||||
@@ -36,11 +36,16 @@
|
||||
<td style="width: 1%; text-wrap: nowrap" class="pa-0 d-print-none" v-if="showCheckbox">
|
||||
<v-checkbox-btn v-model="i.checked" color="success" v-if="!i.isHeader"></v-checkbox-btn>
|
||||
</td>
|
||||
<td style="width: 1%; text-wrap: nowrap" class="pr-1"
|
||||
v-html="calculateFoodAmount(i.amount, props.ingredientFactor, useUserPreferenceStore().userSettings.useFractions)" v-if="!i.noAmount"></td>
|
||||
<td style="width: 1%; text-wrap: nowrap" class="pr-1" v-if="i.noAmount"></td>
|
||||
<!-- display calculated food amount or empty cell -->
|
||||
<td style="width: 1%; text-wrap: nowrap"
|
||||
class="pr-1"
|
||||
v-html="calculateFoodAmount(i.amount, props.ingredientFactor, useUserPreferenceStore().userSettings.useFractions)"
|
||||
v-if="!i.noAmount && i.amount != 0">
|
||||
</td>
|
||||
<td style="width: 1%; text-wrap: nowrap" class="pr-1" v-else></td>
|
||||
|
||||
<td style="width: 1%; text-wrap: nowrap" class="pr-1">
|
||||
<template v-if="i.unit && !i.noAmount"> {{ ingredientToUnitString(i, ingredientFactor) }}</template>
|
||||
<template v-if="i.unit && !i.noAmount && i.amount != 0"> {{ ingredientToUnitString(i, ingredientFactor) }}</template>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="i.food">
|
||||
|
||||
@@ -121,11 +121,16 @@
|
||||
<template v-if="recipe.filePath">
|
||||
<external-recipe-viewer class="mt-2" :recipe="recipe"></external-recipe-viewer>
|
||||
|
||||
<v-card :title="$t('AI')" prepend-icon="$ai" @click="aiConvertRecipe()" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading"
|
||||
<v-card :title="$t('AI')" prepend-icon="$ai" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading || !useUserPreferenceStore().activeSpace.aiEnabled"
|
||||
v-if="!recipe.internal">
|
||||
<v-card-text>
|
||||
Convert the recipe using AI
|
||||
{{$t('ConvertUsingAI')}}
|
||||
|
||||
<model-select model="AiProvider" v-model="selectedAiProvider">
|
||||
<template #append>
|
||||
<v-btn @click="aiConvertRecipe()" icon="fa-solid fa-person-running" color="success"></v-btn>
|
||||
</template>
|
||||
</model-select>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -191,7 +196,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {computed, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
||||
import {ApiApi, Recipe} from "@/openapi"
|
||||
import {AiProvider, ApiApi, Recipe} from "@/openapi"
|
||||
import NumberScalerDialog from "@/components/inputs/NumberScalerDialog.vue"
|
||||
import StepsOverview from "@/components/display/StepsOverview.vue";
|
||||
import RecipeActivity from "@/components/display/RecipeActivity.vue";
|
||||
@@ -207,6 +212,7 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
|
||||
import {useFileApi} from "@/composables/useFileApi.ts";
|
||||
import PrivateRecipeBadge from "@/components/display/PrivateRecipeBadge.vue";
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
|
||||
const {request, release} = useWakeLock()
|
||||
const {doAiImport, fileApiLoading} = useFileApi()
|
||||
@@ -217,6 +223,8 @@ const recipe = defineModel<Recipe>({required: true})
|
||||
const servings = ref(1)
|
||||
const showFullRecipeName = ref(false)
|
||||
|
||||
const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().activeSpace.aiDefaultProvider)
|
||||
|
||||
/**
|
||||
* factor for multiplying ingredient amounts based on recipe base servings and user selected servings
|
||||
*/
|
||||
@@ -249,7 +257,7 @@ onBeforeUnmount(() => {
|
||||
function aiConvertRecipe() {
|
||||
let api = new ApiApi()
|
||||
|
||||
doAiImport(null, '', recipe.value.id!).then(r => {
|
||||
doAiImport(selectedAiProvider.value.id!,null, '', recipe.value.id!).then(r => {
|
||||
if (r.recipe) {
|
||||
recipe.value.internal = true
|
||||
recipe.value.steps = r.recipe.steps
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const emit = defineEmits(['stop'])
|
||||
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
@@ -24,6 +26,8 @@ const props = defineProps({
|
||||
seconds: {type: Number, required: true}
|
||||
})
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const initialDurationSeconds = ref(props.seconds)
|
||||
const durationSeconds = ref(initialDurationSeconds.value)
|
||||
const timerRunning = ref(true)
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
<template v-if="hasMoreItems && !loading" #afterlist>
|
||||
<span class="text-disabled font-italic text-caption ms-3">{{ $t('ModelSelectResultsHelp') }}</span>
|
||||
</template>
|
||||
|
||||
</Multiselect>
|
||||
|
||||
<template #append v-if="$slots.append">
|
||||
@@ -73,7 +72,7 @@
|
||||
import {computed, onBeforeMount, onMounted, PropType, ref, useTemplateRef} from "vue"
|
||||
import {EditorSupportedModels, GenericModel, getGenericModelFromString} from "@/types/Models"
|
||||
import Multiselect from '@vueform/multiselect'
|
||||
import {ErrorMessageType, MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {ErrorMessageType, MessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {t} = useI18n()
|
||||
@@ -171,7 +170,7 @@ function search(query: string) {
|
||||
*/
|
||||
async function createObject(object: any, select$: Multiselect) {
|
||||
return await modelClass.value.create({name: object[itemLabel.value]}).then((createdObj: any) => {
|
||||
useMessageStore().addMessage(MessageType.SUCCESS, 'Created', 5000, createdObj)
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.CREATE_SUCCESS, createdObj)
|
||||
emit('create', object)
|
||||
return createdObj
|
||||
}).catch((err: any) => {
|
||||
|
||||
105
vue3/src/components/model_editors/AiProviderEditor.vue
Normal file
105
vue3/src/components/model_editors/AiProviderEditor.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<model-editor-base
|
||||
:loading="loading"
|
||||
:dialog="dialog"
|
||||
@save="saveObject"
|
||||
@delete="deleteObject"
|
||||
@close="emit('close'); editingObjChanged = false"
|
||||
:is-update="isUpdate()"
|
||||
:is-changed="editingObjChanged"
|
||||
:model-class="modelClass"
|
||||
:object-name="editingObjName()">
|
||||
<v-card-text>
|
||||
<v-form :disabled="loading">
|
||||
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
|
||||
<v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea>
|
||||
|
||||
|
||||
<v-text-field :label="$t('APIKey')" v-model="editingObj.apiKey"></v-text-field>
|
||||
|
||||
<v-combobox :label="$t('Model')" :items="aiModels" v-model="editingObj.modelName" hide-details>
|
||||
|
||||
</v-combobox>
|
||||
|
||||
<p class="mt-2 mb-2">{{ $t('AiModelHelp') }} <a href="https://docs.litellm.ai/docs/providers" target="_blank">LiteLLM</a></p>
|
||||
|
||||
<v-checkbox :label="$t('LogCredits')" :hint="$t('LogCreditsHelp')" v-model="editingObj.logCreditCost" v-if="useUserPreferenceStore().userSettings.user.isSuperuser" persistent-hint
|
||||
class="mb-2"></v-checkbox>
|
||||
<v-text-field :label="$t('Url')" v-model="editingObj.url"></v-text-field>
|
||||
|
||||
<v-checkbox :label="$t('Global')" :hint="$t('GlobalHelp')" v-model="globalProvider" v-if="useUserPreferenceStore().userSettings.user.isSuperuser" persistent-hint
|
||||
class="mb-2"></v-checkbox>
|
||||
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</model-editor-base>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType, ref, watch} from "vue";
|
||||
import {AiProvider} from "@/openapi";
|
||||
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
import editor from "mavon-editor";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as PropType<AiProvider>, required: false, default: null},
|
||||
itemId: {type: [Number, String], required: false, default: undefined},
|
||||
itemDefaults: {type: {} as PropType<AiProvider>, required: false, default: {} as AiProvider},
|
||||
dialog: {type: Boolean, default: false}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['create', 'save', 'delete', 'close', 'changedState'])
|
||||
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, editingObjChanged, modelClass} = useModelEditorFunctions<AiProvider>('AiProvider', emit)
|
||||
|
||||
/**
|
||||
* watch prop changes and re-initialize editor
|
||||
* required to embed editor directly into pages and be able to change item from the outside
|
||||
*/
|
||||
watch([() => props.item, () => props.itemId], () => {
|
||||
initializeEditor()
|
||||
})
|
||||
|
||||
// object specific data (for selects/display)
|
||||
const aiModels = ref(['gemini/gemini-2.5-pro', 'gemini/gemini-2.5-flash', 'gemini/gemini-2.5-flash-lite', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'])
|
||||
|
||||
const globalProvider = ref(false)
|
||||
|
||||
watch(() => globalProvider.value, () => {
|
||||
if (globalProvider.value) {
|
||||
editingObj.value.space = undefined
|
||||
} else {
|
||||
editingObj.value.space = useUserPreferenceStore().activeSpace.id!
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initializeEditor()
|
||||
})
|
||||
|
||||
/**
|
||||
* component specific state setup logic
|
||||
*/
|
||||
function initializeEditor() {
|
||||
setupState(props.item, props.itemId, {
|
||||
itemDefaults: props.itemDefaults,
|
||||
newItemFunction: () => {
|
||||
editingObj.value.logCreditCost = true
|
||||
editingObj.value.space = useUserPreferenceStore().activeSpace.id!
|
||||
},
|
||||
}).then(() => {
|
||||
globalProvider.value = editingObj.value.space == undefined
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -54,7 +54,7 @@
|
||||
<properties-editor v-model="editingObj.properties" :amount-for="propertiesAmountFor"></properties-editor>
|
||||
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 60px;"></v-spacer>
|
||||
<v-spacer style="margin-top: 80px;"></v-spacer>
|
||||
</v-form>
|
||||
</v-tabs-window-item>
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
</v-card>
|
||||
</v-form>
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 60px;"></v-spacer>
|
||||
<v-spacer style="margin-top: 80px;"></v-spacer>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="hierarchy">
|
||||
@@ -119,6 +119,9 @@
|
||||
mode="tags"></ModelSelect>
|
||||
<ModelSelect model="FoodInheritField" v-model="editingObj.childInheritFields" :label="$t('ChildInheritFields')" :hint="$t('ChildInheritFields_help')"
|
||||
mode="tags"></ModelSelect>
|
||||
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 100px;"></v-spacer>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="misc">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<v-date-input :label="$t('Valid Until')" v-model="editingObj.validUntil"></v-date-input>
|
||||
<v-textarea :label="$t('Note')" v-model="editingObj.internalNote"></v-textarea>
|
||||
<v-checkbox :label="$t('Reusable')" v-model="editingObj.reusable"></v-checkbox>
|
||||
<v-text-field :label="$t('Link')" readonly :model-value="inviteLinkUrl(editingObj)">
|
||||
<v-text-field :label="$t('Link')" readonly :model-value="inviteLinkUrl(editingObj)" v-if="isUpdate()">
|
||||
<template #append-inner>
|
||||
<btn-copy variant="plain" color="undefined" :copy-value="inviteLinkUrl(editingObj)"></btn-copy>
|
||||
</template>
|
||||
@@ -37,6 +37,7 @@ import {DateTime} from "luxon";
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
import BtnCopy from "@/components/buttons/BtnCopy.vue";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls.ts";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
@@ -91,7 +92,7 @@ function initializeEditor(){
|
||||
* @param inviteLink InviteLink object to create url for
|
||||
*/
|
||||
function inviteLinkUrl(inviteLink: InviteLink) {
|
||||
return `${location.protocol}//${location.host}/invite/${inviteLink.uuid}`
|
||||
return useDjangoUrls().getDjangoUrl(`/invite/${inviteLink.uuid}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
<v-text-field :label="$t('Username')" v-model="editingObj.username" v-if="editingObj.method == 'NEXTCLOUD' || editingObj.method == 'DB'"></v-text-field>
|
||||
|
||||
<v-text-field :label="$t('Password')" :hint="$t('StoragePasswordTokenHelp')" persistent-hint v-model="editingObj.password" v-if="editingObj.method == 'NEXTCLOUD'"></v-text-field>
|
||||
<v-text-field :label="$t('Access_Token')" :hint="$t('StoragePasswordTokenHelp')" persistent-hint v-model="editingObj.token" v-if="editingObj.method == 'DB'"></v-text-field>
|
||||
<v-text-field :label="$t('Access_Token')" :hint="$t('StoragePasswordTokenHelp')" persistent-hint v-model="editingObj.token" v-if="editingObj.method == 'DB'"></v-text-field>
|
||||
|
||||
<v-text-field :label="$t('Path')" v-model="editingObj.path"></v-text-field>
|
||||
<v-text-field :label="$t('Path')" v-model="editingObj.path"></v-text-field>
|
||||
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -33,7 +33,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType, watch} from "vue";
|
||||
import { Storage } from "@/openapi";
|
||||
import {Storage} from "@/openapi";
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
|
||||
@@ -64,7 +64,7 @@ onMounted(() => {
|
||||
/**
|
||||
* component specific state setup logic
|
||||
*/
|
||||
function initializeEditor(){
|
||||
function initializeEditor() {
|
||||
setupState(props.item, props.itemId, {itemDefaults: props.itemDefaults})
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import {useI18n} from "vue-i18n";
|
||||
import BtnCopy from "@/components/buttons/BtnCopy.vue";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls.ts";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
@@ -111,7 +112,7 @@ function deleteInviteLink(inviteLink: InviteLink) {
|
||||
* @param inviteLink InviteLink object to create url for
|
||||
*/
|
||||
function inviteLinkUrl(inviteLink: InviteLink) {
|
||||
return `${location.protocol}//${location.host}/invite/${inviteLink.uuid}`
|
||||
return useDjangoUrls().getDjangoUrl(`/invite/${inviteLink.uuid}`)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -78,19 +78,38 @@
|
||||
|
||||
|
||||
<v-textarea v-model="space.message" :label="$t('Message')"></v-textarea>
|
||||
<v-btn color="success" @click="updateSpace()" prepend-icon="$save">{{ $t('Save') }}</v-btn>
|
||||
|
||||
<!-- <model-select v-model="space.foodInherit" model="FoodInheritField" mode="tags"></model-select>-->
|
||||
<p class="text-h6 mt-2">{{ $t('AI') }}</p>
|
||||
<v-divider class="mb-2"></v-divider>
|
||||
<p class="text-disabled font-italic text-body-2">
|
||||
<span v-if="useUserPreferenceStore().serverSettings.hosted">
|
||||
{{ $t('AISettingsHostedHelp') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('SettingsOnlySuperuser') }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<v-checkbox v-model="space.aiEnabled" :label="$t('Enabled')" :disabled="!useUserPreferenceStore().userSettings.user.isSuperuser" hide-details></v-checkbox>
|
||||
|
||||
<template v-if="space.aiEnabled">
|
||||
<model-select model="AiProvider" :label="$t('Default')" v-model="space.aiDefaultProvider"></model-select>
|
||||
|
||||
<v-number-input v-model="space.aiCreditsMonthly" :precision="2" :label="$t('MonthlyCredits')" :disabled="!useUserPreferenceStore().userSettings.user.isSuperuser"></v-number-input>
|
||||
<v-number-input v-model="space.aiCreditsBalance" :precision="4" :label="$t('AiCreditsBalance')" :disabled="!useUserPreferenceStore().userSettings.user.isSuperuser"></v-number-input>
|
||||
|
||||
</template>
|
||||
<v-btn color="success" @click="updateSpace()" prepend-icon="$save">{{ $t('Save') }}</v-btn>
|
||||
|
||||
<v-divider class="mt-4 mb-2"></v-divider>
|
||||
<h2>{{$t('Cosmetic')}}</h2>
|
||||
<span>{{$t('Space_Cosmetic_Settings')}}</span>
|
||||
<h2>{{ $t('Cosmetic') }}</h2>
|
||||
<span>{{ $t('Space_Cosmetic_Settings') }}</span>
|
||||
|
||||
<v-label class="mt-4">{{ $t('Nav_Color') }}</v-label>
|
||||
<v-color-picker v-model="space.navBgColor" class="mb-4" mode="hex" :modes="['hex']" show-swatches
|
||||
:swatches="[['#ddbf86'],['#b98766'],['#b55e4f'],['#82aa8b'],['#385f84']]"></v-color-picker>
|
||||
<v-btn class="mb-4" @click="space.navBgColor = ''">{{$t('Reset')}}</v-btn>
|
||||
<v-btn class="mb-4" @click="space.navBgColor = ''">{{ $t('Reset') }}</v-btn>
|
||||
|
||||
<user-file-field v-model="space.navLogo" :label="$t('Logo')" :hint="$t('CustomNavLogoHelp')" persistent-hint></user-file-field>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import {ref} from "vue";
|
||||
import {getCookie} from "@/utils/cookie";
|
||||
import {RecipeFromSourceResponseFromJSON, RecipeImageFromJSON, ResponseError, UserFile, UserFileFromJSON} from "@/openapi";
|
||||
import {AiProvider, RecipeFromSourceResponseFromJSON, RecipeImageFromJSON, ResponseError, UserFile, UserFileFromJSON} from "@/openapi";
|
||||
|
||||
|
||||
/**
|
||||
@@ -86,7 +86,7 @@ export function useFileApi() {
|
||||
* @param text text to import
|
||||
* @param recipeId id of a recipe to use as import base (for external recipes
|
||||
*/
|
||||
function doAiImport(file: File | null, text: string = '', recipeId: string = '') {
|
||||
function doAiImport(providerId: number, file: File | null, text: string = '', recipeId: string = '') {
|
||||
let formData = new FormData()
|
||||
|
||||
if (file != null) {
|
||||
@@ -96,6 +96,7 @@ export function useFileApi() {
|
||||
}
|
||||
formData.append('text', text)
|
||||
formData.append('recipe_id', recipeId)
|
||||
formData.append('ai_provider_id', providerId)
|
||||
fileApiLoading.value = true
|
||||
|
||||
return fetch(getDjangoUrl(`api/ai-import/`), {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"Add": "",
|
||||
@@ -14,6 +15,12 @@
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"App": "",
|
||||
"Apply": "",
|
||||
"Are_You_Sure": "",
|
||||
@@ -44,6 +51,7 @@
|
||||
"Color": "",
|
||||
"Coming_Soon": "",
|
||||
"Completed": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
@@ -58,6 +66,7 @@
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Credits": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -102,10 +111,13 @@
|
||||
"FoodOnHand": "",
|
||||
"Food_Alias": "",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
@@ -148,6 +160,8 @@
|
||||
"Keywords": "",
|
||||
"Link": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"Make_Header": "",
|
||||
@@ -165,6 +179,8 @@
|
||||
"Message": "",
|
||||
"MissingProperties": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"Move_Down": "",
|
||||
@@ -256,6 +272,7 @@
|
||||
"Selected": "",
|
||||
"Servings": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"Shopping_Categories": "",
|
||||
"Shopping_Category": "",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"Add": "Добави",
|
||||
@@ -14,6 +15,12 @@
|
||||
"Added_by": "Добавено от",
|
||||
"Added_on": "Добавено",
|
||||
"Advanced": "Разширено",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"App": "Приложение",
|
||||
"Apply": "",
|
||||
"Are_You_Sure": "Сигурен ли си?",
|
||||
@@ -44,6 +51,7 @@
|
||||
"Color": "Цвят",
|
||||
"Coming_Soon": "Очаквайте скоро",
|
||||
"Completed": "Завършено",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Копиране",
|
||||
"Copy_template_reference": "Копирайте препратка към шаблона",
|
||||
"CountMore": "...+{count} още",
|
||||
@@ -55,6 +63,7 @@
|
||||
"Create_New_Meal_Type": "Добавете нов тип хранене",
|
||||
"Create_New_Shopping Category": "Създайте нова категория за пазаруване",
|
||||
"Create_New_Unit": "Добавяне на нова единица",
|
||||
"Credits": "",
|
||||
"Current_Period": "Текущ период",
|
||||
"Custom Filter": "Персонализиран филтър",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -99,10 +108,13 @@
|
||||
"FoodOnHand": "Имате {храна} под ръка.",
|
||||
"Food_Alias": "Псевдоним на храната",
|
||||
"Foods": "Храни",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Групирай по",
|
||||
"Hide_Food": "Скриване на храна",
|
||||
"Hide_Keyword": "Скриване на ключови думи",
|
||||
@@ -143,6 +155,8 @@
|
||||
"Keywords": "Ключови думи",
|
||||
"Link": "Връзка",
|
||||
"Load_More": "Зареди още",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Дневник на Готвене",
|
||||
"Log_Recipe_Cooking": "Дневник на Рецепта за готвене",
|
||||
"Make_Header": "Направете заглавие",
|
||||
@@ -159,6 +173,8 @@
|
||||
"Merge_Keyword": "Обединяване на ключова дума",
|
||||
"MissingProperties": "",
|
||||
"Month": "Месец",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Премести",
|
||||
"MoveCategory": "Премести към: ",
|
||||
"Move_Down": "Премести надолу",
|
||||
@@ -249,6 +265,7 @@
|
||||
"Selected": "Избрано",
|
||||
"Servings": "Порции",
|
||||
"Settings": "Настройки",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Споделяне",
|
||||
"Shopping_Categories": "Категории за пазаруване",
|
||||
"Shopping_Category": "Категория за пазаруване",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Afegit per",
|
||||
"Added_on": "Afegit el",
|
||||
"Advanced": "Avançat",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Alineació",
|
||||
"Amount": "Quantitat",
|
||||
"App": "Aplicació",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Mostrar comentaris",
|
||||
"Completed": "Completat",
|
||||
"Conversion": "Conversió",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Copiar",
|
||||
"Copy Link": "Copiar Enllaç",
|
||||
"Copy Token": "Copiar Token",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "Afegir nova Categoria de Compres",
|
||||
"Create_New_Unit": "Afegir nova unitat",
|
||||
"Created": "Creada",
|
||||
"Credits": "",
|
||||
"Current_Period": "Període Actual",
|
||||
"Custom Filter": "Filtre Personalitzat",
|
||||
"CustomImageHelp": "Carregar una imatge per mostrar a la vista general de l’espai.",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "Àlies per l'aliment",
|
||||
"Food_Replace": "Aliment equivalent",
|
||||
"Foods": "Aliments",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Agrupat per",
|
||||
"Hide_Food": "Amagar Aliment",
|
||||
"Hide_Keyword": "Amaga les paraules clau",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "Saber-me més",
|
||||
"Link": "Enllaç",
|
||||
"Load_More": "Carregueu-ne més",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registreu el que s'ha cuinat",
|
||||
"Log_Recipe_Cooking": "Registre de receptes",
|
||||
"Logo": "Logotip",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "Missatge",
|
||||
"MissingProperties": "",
|
||||
"Month": "Mes",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Moure",
|
||||
"MoveCategory": "Moure a: ",
|
||||
"Move_Down": "Moveu avall",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "Seleccionat",
|
||||
"Servings": "Racions",
|
||||
"Settings": "Opcions",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Compartir",
|
||||
"ShoppingBackgroundSyncWarning": "Error de la connexió, esperant per sincronitzar ...",
|
||||
"Shopping_Categories": "Categoria de compres",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Přidáno uživatelem",
|
||||
"Added_on": "Přidáno v",
|
||||
"Advanced": "Rozšířené",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Zarovnání",
|
||||
"Amount": "Množství",
|
||||
"App": "Aplikace",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Zobrazit komentáře",
|
||||
"Completed": "Dokončeno",
|
||||
"Conversion": "Převod",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopírovat",
|
||||
"Copy Link": "Kopírovat odkaz",
|
||||
"Copy Token": "Kopírovat token",
|
||||
@@ -73,6 +81,7 @@
|
||||
"Create_New_Shopping Category": "Vytvořit novou nákupní kategorii",
|
||||
"Create_New_Shopping_Category": "Přidat novou nákupní kategorii",
|
||||
"Create_New_Unit": "Přidat novou jednotku",
|
||||
"Credits": "",
|
||||
"Current_Period": "Současné období",
|
||||
"Custom Filter": "Uživatelský filtr",
|
||||
"CustomImageHelp": "Nahrajte obrázek, který se zobrazí v přehledu prostoru.",
|
||||
@@ -142,10 +151,13 @@
|
||||
"Food_Alias": "Přezdívka potraviny",
|
||||
"Food_Replace": "Nahrazení v potravině",
|
||||
"Foods": "Potraviny",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Seskupit podle",
|
||||
"Hide_Food": "Skrýt potravinu",
|
||||
"Hide_Keyword": "Skrýt štítky",
|
||||
@@ -195,6 +207,8 @@
|
||||
"Learn_More": "Zjistit víc",
|
||||
"Link": "Odkaz",
|
||||
"Load_More": "Načíst další",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Zaznamenat vaření",
|
||||
"Log_Recipe_Cooking": "Záznam vaření receptu",
|
||||
"Logo": "Logo",
|
||||
@@ -214,6 +228,8 @@
|
||||
"Message": "Zpráva",
|
||||
"MissingProperties": "",
|
||||
"Month": "Měsíc",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Přesunout",
|
||||
"MoveCategory": "Přesunout do: ",
|
||||
"Move_Down": "Dolů",
|
||||
@@ -325,6 +341,7 @@
|
||||
"Selected": "Vybrané",
|
||||
"Servings": "Porce",
|
||||
"Settings": "Nastavení",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Sdílet",
|
||||
"Shopping_Categories": "Kategorie nákupního seznamu",
|
||||
"Shopping_Category": "Kategorie nákupního seznamu",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Tilføjet af",
|
||||
"Added_on": "Tilføjet den",
|
||||
"Advanced": "Avanceret",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Justering",
|
||||
"Amount": "Mængde",
|
||||
"App": "App",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Vis kommentarer",
|
||||
"Completed": "Afsluttet",
|
||||
"Conversion": "Konversion",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopier",
|
||||
"Copy Link": "Kopier link",
|
||||
"Copy Token": "Kopier token",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "Opret ny indkøbskategori",
|
||||
"Create_New_Unit": "Tilføj ny enhed",
|
||||
"Created": "Skabt",
|
||||
"Credits": "",
|
||||
"Current_Period": "Nuværende periode",
|
||||
"Custom Filter": "Tilpasset filter",
|
||||
"CustomImageHelp": "Upload et billede for at vise dets plade i område-oversigten.",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "Alternativt navn til mad",
|
||||
"Food_Replace": "Erstat ingrediens",
|
||||
"Foods": "Mad",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grupper efter",
|
||||
"Hide_Food": "Skjul mad",
|
||||
"Hide_Keyword": "Skjul nøgleord",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "Lær mere",
|
||||
"Link": "Link",
|
||||
"Load_More": "Indlæs mere",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Noter tilberedning",
|
||||
"Log_Recipe_Cooking": "Noter tilberedning af opskrift",
|
||||
"Logo": "Logo",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "Besked",
|
||||
"MissingProperties": "",
|
||||
"Month": "Måned",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Flyt",
|
||||
"MoveCategory": "Flyt til: ",
|
||||
"Move_Down": "Flyt ned",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "Valgt",
|
||||
"Servings": "Serveringer",
|
||||
"Settings": "Indstillinger",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Del",
|
||||
"ShoppingBackgroundSyncWarning": "Dårligt netværk, afventer synkronisering ...",
|
||||
"Shopping_Categories": "Indkøbskategorier",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"AI": "AI",
|
||||
"AIImportSubtitle": "Verwende AI um Fotos von Rezepten zu importieren.",
|
||||
"AISettingsHostedHelp": "AI Verfügbarkeit und Credit Limits können über die Tarifverwaltung geändert werden. ",
|
||||
"API": "API",
|
||||
"APIKey": "API Schlüssel",
|
||||
"Summary": "Zusammenfassung",
|
||||
"Structured": "Strukturiert",
|
||||
"API_Browser": "API Browser",
|
||||
"API_Documentation": "API Dokumentation",
|
||||
"AccessTokenHelp": "Zugriffsschlüssel für die REST Schnittstelle.",
|
||||
@@ -31,6 +30,12 @@
|
||||
"Admin": "Admin",
|
||||
"Advanced": "Erweitert",
|
||||
"Advanced Search Settings": "Erweiterte Sucheinstellungen",
|
||||
"AiCreditsBalance": "Credit Guthaben",
|
||||
"AiLog": "AI Protokoll",
|
||||
"AiLogHelp": "Eine Übersicht der AI Anfragen.",
|
||||
"AiModelHelp": "Die Liste enthält Modelle die offiziell Unterstützt und getestet wurden. Weitere modelle können manuell eingetragen werden.",
|
||||
"AiProvider": "AI Anbieter",
|
||||
"AiProviderHelp": "Je nach Präferenz können verschiedene AI Anbieter angelegt werden. Diese können auch Space übergreifend sein.",
|
||||
"Alignment": "Ausrichtung",
|
||||
"AllRecipes": "Alle Rezepte",
|
||||
"Amount": "Menge",
|
||||
@@ -92,6 +97,7 @@
|
||||
"Continue": "Weiter",
|
||||
"Conversion": "Umrechnung",
|
||||
"ConversionsHelp": "Mit Umrechnungen kann die Menge eines Lebensmittels in verschiedenen Einheiten ausgerechnet werden. Aktuell wird dies nur zur berechnung von Eigenschaften verwendet, später jedoch sollen auch andere Funktionen von Tandoor davon profitieren. ",
|
||||
"ConvertUsingAI": "Mithilfe von AI Umwandeln",
|
||||
"CookLog": "Kochprotokoll",
|
||||
"CookLogHelp": "Einträge im Kochprotokoll für Rezepte. ",
|
||||
"Cooked": "Gekocht",
|
||||
@@ -114,6 +120,7 @@
|
||||
"Create_New_Unit": "Neue Einheit hinzufügen",
|
||||
"Created": "Erstellt",
|
||||
"CreatedBy": "Erstellt von",
|
||||
"Credits": "Credits",
|
||||
"Ctrl+K": "Strg+K",
|
||||
"Current_Period": "Aktueller Zeitraum",
|
||||
"Custom Filter": "Benutzerdefinierter Filter",
|
||||
@@ -207,11 +214,14 @@
|
||||
"Food_Replace": "Essen Ersetzen",
|
||||
"Foods": "Lebensmittel",
|
||||
"Friday": "Freitag",
|
||||
"FromBalance": "Guthaben verwendet",
|
||||
"Fulltext": "Volltext",
|
||||
"FulltextHelp": "Felder welche im Volltext durchsucht werden sollen. Tipp: Die Suchtypen 'web', 'raw' und 'phrase' funktionieren nur mit Volltext-Feldern.",
|
||||
"Fuzzy": "Unscharf",
|
||||
"FuzzySearchHelp": "Verwende unscharfe Suche um Einträge auch bei Unterschieden in der Schreibweise zu finden.",
|
||||
"GettingStarted": "Erste Schritte",
|
||||
"Global": "Global",
|
||||
"GlobalHelp": "Globale AI Anbieter können von Nutzern aller Spaces verwendet werden. Sie können nur dich Instanz Admins (Superusers) erstellt und bearbeitet werden.",
|
||||
"GroupBy": "Gruppieren nach",
|
||||
"HeaderWarning": "Achtung: Durch ändern auf Überschrift werden Menge/Einheit/Lebensmittel gelöscht",
|
||||
"Headline": "Überschrift",
|
||||
@@ -279,6 +289,8 @@
|
||||
"Link": "Link",
|
||||
"Load": "Laden",
|
||||
"Load_More": "Weitere laden",
|
||||
"LogCredits": "Credits Protokollieren",
|
||||
"LogCreditsHelp": "Protokolliere die Credit Kosten der AI Anfragen. Ohne diese Protokollierung können Nutzer unbgerenzt viele Anfragen stellen.",
|
||||
"Log_Cooking": "Kochen protokollieren",
|
||||
"Log_Recipe_Cooking": "Kochen protokollieren",
|
||||
"Logo": "Logo",
|
||||
@@ -308,6 +320,8 @@
|
||||
"ModelSelectResultsHelp": "Für mehr Ergebnisse suchen",
|
||||
"Monday": "Montag",
|
||||
"Month": "Monat",
|
||||
"MonthlyCredits": "Monatliche Credits",
|
||||
"MonthlyCreditsUsed": "Monatliche Credits verwendet",
|
||||
"More": "Mehr",
|
||||
"Move": "Verschieben",
|
||||
"MoveCategory": "Verschieben nach: ",
|
||||
@@ -461,6 +475,7 @@
|
||||
"Servings": "Portionen",
|
||||
"ServingsText": "Portionstext",
|
||||
"Settings": "Einstellungen",
|
||||
"SettingsOnlySuperuser": "Einige Einstellungen können nur vom Server Administrator verändert werden.",
|
||||
"Share": "Teilen",
|
||||
"ShopLater": "Später kaufen",
|
||||
"ShopNow": "Jetzt kaufen",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Προστέθηκε από",
|
||||
"Added_on": "Προστέθηκε στις",
|
||||
"Advanced": "Για προχωρημένους",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Ευθυγράμμιση",
|
||||
"Amount": "Ποσότητα",
|
||||
"App": "Εφαρμογή",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Εμφάνιση σχολίων",
|
||||
"Completed": "Ολοκληρωμένο",
|
||||
"Conversion": "Μετατροπή",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Αντιγραφή",
|
||||
"Copy Link": "Αντιγραφή συνδέσμου",
|
||||
"Copy Token": "Αντιγραφή token",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "Προσθήκη νέας κατηγορίας αγορών",
|
||||
"Create_New_Unit": "Προσθήκη νέας μονάδας μέτρησης",
|
||||
"Created": "Δημιουργήθηκε",
|
||||
"Credits": "",
|
||||
"Current_Period": "Τρέχουσα περίοδος",
|
||||
"Custom Filter": "Προσαρμοσμένο φίλτρο",
|
||||
"CustomImageHelp": "Ανεβάστε μια εικόνα για να εμφανίζεται στην επισκόπηση χώρου",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "Ψευδώνυμο φαγητού",
|
||||
"Food_Replace": "Αντικατάσταση Φαγητού",
|
||||
"Foods": "Φαγητά",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Ομαδοποίηση κατά",
|
||||
"Hide_Food": "Απόκρυψη φαγητού",
|
||||
"Hide_Keyword": "Απόκρυψη λέξεων-κλειδί",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "Μάθετε περισσότερα",
|
||||
"Link": "Σύνδεσμος",
|
||||
"Load_More": "Φόρτωση περισσότερων",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Καταγραφή μαγειρέματος",
|
||||
"Log_Recipe_Cooking": "Καταγραφή εκτέλεσης συνταγής",
|
||||
"Logo": "Λογότυπο",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "Μήνυμα",
|
||||
"MissingProperties": "",
|
||||
"Month": "Μήνας",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Μετακίνηση",
|
||||
"MoveCategory": "Μετακίνηση σε: ",
|
||||
"Move_Down": "Μετακίνηση κάτω",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "Επιλεγμένο",
|
||||
"Servings": "Μερίδες",
|
||||
"Settings": "Ρυθμίσεις",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Κοινοποίηση",
|
||||
"ShoppingBackgroundSyncWarning": "Κακό δίκτυο, αναμονή συγχρονισμού...",
|
||||
"Shopping_Categories": "Κατηγορίες αγορών",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"AI": "AI",
|
||||
"AIImportSubtitle": "Use AI to import images of recipes.",
|
||||
"AISettingsHostedHelp": "You can enable AI features or change available credits by managing your subscription.",
|
||||
"API": "API",
|
||||
"APIKey": "API key",
|
||||
"API_Browser": "API Browser",
|
||||
"API_Documentation": "API Docs",
|
||||
"AccessTokenHelp": "Access keys for the REST API.",
|
||||
@@ -26,6 +28,12 @@
|
||||
"Added_on": "Added On",
|
||||
"Admin": "Admin",
|
||||
"Advanced": "Advanced",
|
||||
"AiCreditsBalance": "Credit Balance",
|
||||
"AiLog": "AI Log",
|
||||
"AiLogHelp": "Overview of your spaces AI requests. ",
|
||||
"AiModelHelp": "The list contains model that are offically tested and supported. You can add additional models if you want.",
|
||||
"AiProvider": "AI Provider",
|
||||
"AiProviderHelp": "You can configure multiple AI providers according to your preferences. They can even be configured to work across multiple spaces.",
|
||||
"Alignment": "Alignment",
|
||||
"AllRecipes": "All Recipes",
|
||||
"Amount": "Amount",
|
||||
@@ -87,6 +95,7 @@
|
||||
"Continue": "Continue",
|
||||
"Conversion": "Conversion",
|
||||
"ConversionsHelp": "With conversions you can calculate the amount of a food in different units. Currently this is only used for property calculation, later it might also be used in other parts of tandoor. ",
|
||||
"ConvertUsingAI": "Convert using AI",
|
||||
"CookLog": "Cook Log",
|
||||
"CookLogHelp": "Entries in the cook log for recipes. ",
|
||||
"Cooked": "Cooked",
|
||||
@@ -109,6 +118,7 @@
|
||||
"Create_New_Unit": "Add New Unit",
|
||||
"Created": "Created",
|
||||
"CreatedBy": "Created by",
|
||||
"Credits": "Credits",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Current Period",
|
||||
"Custom Filter": "Custom Filter",
|
||||
@@ -202,11 +212,14 @@
|
||||
"Food_Replace": "Food Replace",
|
||||
"Foods": "Foods",
|
||||
"Friday": "Friday",
|
||||
"FromBalance": "From Balance",
|
||||
"Fulltext": "Fulltext",
|
||||
"FulltextHelp": "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields.",
|
||||
"Fuzzy": "Fuzzy",
|
||||
"FuzzySearchHelp": "Use fuzzy search to find entries even when there are differences in how the word is written.",
|
||||
"GettingStarted": "Getting Started",
|
||||
"Global": "Global",
|
||||
"GlobalHelp": "Global AI Providers can be used by users of all spaces. They can only be created and edited by superusers. ",
|
||||
"GroupBy": "Group By",
|
||||
"HeaderWarning": "Warning: Changing to a Heading deletes the Amount/Unit/Food",
|
||||
"Headline": "Headline",
|
||||
@@ -274,6 +287,8 @@
|
||||
"Link": "Link",
|
||||
"Load": "Load",
|
||||
"Load_More": "Load More",
|
||||
"LogCredits": "Log Credits.",
|
||||
"LogCreditsHelp": "Log credit cost of AI requests. Without this users can perform as many AI requests as they want. ",
|
||||
"Log_Cooking": "Log Cooking",
|
||||
"Log_Recipe_Cooking": "Log Recipe Cooking",
|
||||
"Logo": "Logo",
|
||||
@@ -303,6 +318,8 @@
|
||||
"ModelSelectResultsHelp": "Search for more results",
|
||||
"Monday": "Monday",
|
||||
"Month": "Month",
|
||||
"MonthlyCredits": "Monthly Credits",
|
||||
"MonthlyCreditsUsed": "Monthly credits used",
|
||||
"More": "More",
|
||||
"Move": "Move",
|
||||
"MoveCategory": "Move To: ",
|
||||
@@ -456,6 +473,7 @@
|
||||
"Servings": "Servings",
|
||||
"ServingsText": "Servings Text",
|
||||
"Settings": "Settings",
|
||||
"SettingsOnlySuperuser": "Some Settings can only be changed by the Server Administrator.",
|
||||
"Share": "Share",
|
||||
"ShopLater": "Shop later",
|
||||
"ShopNow": "Shop now",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "IA",
|
||||
"AIImportSubtitle": "Usar IA para importar imágenes de recetas.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -26,6 +27,12 @@
|
||||
"Added_on": "Añadido el",
|
||||
"Admin": "Administrador",
|
||||
"Advanced": "Avanzado",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Alineación",
|
||||
"AllRecipes": "Todas las recetas",
|
||||
"Amount": "Cantidad",
|
||||
@@ -85,6 +92,7 @@
|
||||
"Continue": "Continuar",
|
||||
"Conversion": "Conversión",
|
||||
"ConversionsHelp": "Con las conversiones puedes calcular la cantidad de un alimento en diferentes unidades. Actualmente esto solo se usa para el cálculo de propiedades, en un futuro podría ser usado en otras partes de Tandoor. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Historial de cocina",
|
||||
"CookLogHelp": "Entradas en el historial de cocina para recetas. ",
|
||||
"Cooked": "Cocinado",
|
||||
@@ -107,6 +115,7 @@
|
||||
"Create_New_Unit": "Añadir nueva unidad",
|
||||
"Created": "Creada",
|
||||
"CreatedBy": "Creado por",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Periodo actual",
|
||||
"Custom Filter": "Filtro personalizado",
|
||||
@@ -200,7 +209,10 @@
|
||||
"Food_Replace": "Sustituir Alimento",
|
||||
"Foods": "Alimentos",
|
||||
"Friday": "Viernes",
|
||||
"FromBalance": "",
|
||||
"GettingStarted": "Primeros pasos",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Agrupar por",
|
||||
"HeaderWarning": "Advertencia: Cambiar a un encabezado eliminará la cantidad/unidad/alimento",
|
||||
"Headline": "Encabezado",
|
||||
@@ -266,6 +278,8 @@
|
||||
"Link": "Enlace",
|
||||
"Load": "Cargar",
|
||||
"Load_More": "Cargar más",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registrar cocinada",
|
||||
"Log_Recipe_Cooking": "Registro de recetas",
|
||||
"Logo": "Logotipo",
|
||||
@@ -294,6 +308,8 @@
|
||||
"ModelSelectResultsHelp": "Buscar más resultados",
|
||||
"Monday": "Lunes",
|
||||
"Month": "Mes",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Más",
|
||||
"Move": "Mover",
|
||||
"MoveCategory": "Mover a: ",
|
||||
@@ -441,6 +457,7 @@
|
||||
"Servings": "Raciones",
|
||||
"ServingsText": "Texto de la porción",
|
||||
"Settings": "Opciones",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Compartir",
|
||||
"ShopLater": "Comprar después",
|
||||
"ShopNow": "Comprar ahora",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -17,6 +18,12 @@
|
||||
"Added_on": "Lisätty",
|
||||
"Advanced": "Edistynyt",
|
||||
"Advanced Search Settings": "Tarkennetun Haun Asetukset",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Tasaus",
|
||||
"Amount": "Määrä",
|
||||
"App": "Applikaatio",
|
||||
@@ -55,6 +62,7 @@
|
||||
"Comments_setting": "Näytä Kommentit",
|
||||
"Completed": "Valmis",
|
||||
"Conversion": "Muuntaminen",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopioi",
|
||||
"Copy Link": "Kopioi Linkki",
|
||||
"Copy Token": "Kopioi Token",
|
||||
@@ -71,6 +79,7 @@
|
||||
"Create_New_Shopping_Category": "Lisää uusi ostoskategoria",
|
||||
"Create_New_Unit": "Lisää Uusi Yksikkö",
|
||||
"Created": "Luotu",
|
||||
"Credits": "",
|
||||
"Current_Period": "Nykyinen Jakso",
|
||||
"Custom Filter": "Mukautettu Suodatin",
|
||||
"CustomImageHelp": "Lataa kuva näytettäväksi tilan yleiskatsauksessa.",
|
||||
@@ -140,10 +149,13 @@
|
||||
"Food_Alias": "Ruoan nimimerkki",
|
||||
"Food_Replace": "Korvaa Ruoka",
|
||||
"Foods": "Ruuat",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Ryhmittely peruste",
|
||||
"Hide_Food": "Piilota Ruoka",
|
||||
"Hide_Keyword": "Piilota avainsana",
|
||||
@@ -191,6 +203,8 @@
|
||||
"Learn_More": "Lisätietoja",
|
||||
"Link": "Linkki",
|
||||
"Load_More": "Lataa Lisää",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Kirjaa kokkaus",
|
||||
"Log_Recipe_Cooking": "Kirjaa Reseptin valmistus",
|
||||
"Logo": "Logo",
|
||||
@@ -210,6 +224,8 @@
|
||||
"Message": "Viesti",
|
||||
"MissingProperties": "",
|
||||
"Month": "Kuukausi",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Siirry",
|
||||
"MoveCategory": "Siirrä paikkaan: ",
|
||||
"Move_Down": "Siirry alas",
|
||||
@@ -317,6 +333,7 @@
|
||||
"Selected": "Valittu",
|
||||
"Servings": "Annokset",
|
||||
"Settings": "Asetukset",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Jaa",
|
||||
"ShoppingBackgroundSyncWarning": "Huono verkkoyhteys, odotetaan synkronointia ...",
|
||||
"Shopping_Categories": "Ostoskategoriat",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "IA",
|
||||
"AIImportSubtitle": "Utiliser l'IA pour importer des images de recettes.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -27,6 +28,12 @@
|
||||
"Admin": "Admin",
|
||||
"Advanced": "Avancé",
|
||||
"Advanced Search Settings": "Paramètres de recherche avancée",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Alignement",
|
||||
"AllRecipes": "Toutes les recettes",
|
||||
"Amount": "Quantité",
|
||||
@@ -88,6 +95,7 @@
|
||||
"Continue": "Continuer",
|
||||
"Conversion": "Conversion",
|
||||
"ConversionsHelp": "Avec les conversions, vous pouvez calculer une quantité dans différentes unités. Actuellement, c'est utilisé uniquement pour le calcul des propriétés, mais ça pourrait être utilisé dans d'autres parties de Tandoor dans le futur. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Journal de cuisine",
|
||||
"CookLogHelp": "Entrées dans le journal de cuisine pour les recettes. ",
|
||||
"Cooked": "Cuit",
|
||||
@@ -110,6 +118,7 @@
|
||||
"Create_New_Unit": "Ajouter une nouvelle unité",
|
||||
"Created": "Créé",
|
||||
"CreatedBy": "Créé par",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Période actuelle",
|
||||
"Custom Filter": "Filtre personnalisé",
|
||||
@@ -203,11 +212,14 @@
|
||||
"Food_Replace": "Remplacer l'aliment",
|
||||
"Foods": "Aliments",
|
||||
"Friday": "Vendredi",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Texte intégral",
|
||||
"FulltextHelp": "Champs de recherche en texte intégral. Remarque : les méthodes de recherche \"web\", \"phrase\" et \"raw\" ne fonctionnent qu'avec des champs en texte intégral.",
|
||||
"Fuzzy": "Approximatif",
|
||||
"FuzzySearchHelp": "Utilisez la recherche approximative pour trouver des entrées même lorsqu'il existe des différences dans la façon dont le mot est écrit.",
|
||||
"GettingStarted": "Commencer",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grouper par",
|
||||
"HeaderWarning": "Attention : Changer pour un En-tête supprimera la quantité / l'unité / l'aliment",
|
||||
"Headline": "En-tête",
|
||||
@@ -275,6 +287,8 @@
|
||||
"Link": "Lien",
|
||||
"Load": "Chargement",
|
||||
"Load_More": "Charger plus",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Marquer comme cuisiné",
|
||||
"Log_Recipe_Cooking": "Marquer la recette comme cuisinée",
|
||||
"Logo": "Logo",
|
||||
@@ -301,6 +315,8 @@
|
||||
"ModelSelectResultsHelp": "Chercher plus de résultats",
|
||||
"Monday": "Lundi",
|
||||
"Month": "Mois",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Plus",
|
||||
"Move": "Déplacer",
|
||||
"MoveCategory": "Déplacer vers : ",
|
||||
@@ -454,6 +470,7 @@
|
||||
"Servings": "Portions",
|
||||
"ServingsText": "Texte des portions",
|
||||
"Settings": "Paramètres",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Partager",
|
||||
"ShopLater": "Acheter plus tard",
|
||||
"ShopNow": "Acheter maintenant",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "נוסף ע\"י",
|
||||
"Added_on": "נוסף ב",
|
||||
"Advanced": "מתקדם",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "יישור",
|
||||
"Amount": "כמות",
|
||||
"App": "אפליקציה",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "הצג תגובות",
|
||||
"Completed": "הושלם",
|
||||
"Conversion": "עברית",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "העתקה",
|
||||
"Copy Link": "העתק קישור",
|
||||
"Copy Token": "העתק טוקן",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "הוסף קטגוריות קניות חדשה",
|
||||
"Create_New_Unit": "הוסף יחידה",
|
||||
"Created": "נוצר",
|
||||
"Credits": "",
|
||||
"Current_Period": "תקופה נוכחית",
|
||||
"Custom Filter": "פילטר מותאם",
|
||||
"CustomImageHelp": "העלאת תמונה שתראה באזור הסקירה.",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "שם כינוי לאוכל",
|
||||
"Food_Replace": "החלף אוכל",
|
||||
"Foods": "מאכלים",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "אסוף לפי",
|
||||
"Hide_Food": "הסתר אוכל",
|
||||
"Hide_Keyword": "הסתר מילות מפתח",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "למד עוד",
|
||||
"Link": "קישור",
|
||||
"Load_More": "טען עוד",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "רשום הכנת מתכון",
|
||||
"Log_Recipe_Cooking": "רשום בישול מתכון",
|
||||
"Logo": "לוגו",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "הודעה",
|
||||
"MissingProperties": "",
|
||||
"Month": "חודש",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "העברה",
|
||||
"MoveCategory": "העבר אל: ",
|
||||
"Move_Down": "העברה למטה",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "נבחר",
|
||||
"Servings": "מנות",
|
||||
"Settings": "הגדרות",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "שיתוף",
|
||||
"ShoppingBackgroundSyncWarning": "בעיית תקשורת, מחכה לסנכון...",
|
||||
"Shopping_Categories": "קטגוריות קניות",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Dodao",
|
||||
"Added_on": "Dodano",
|
||||
"Advanced": "Napredno",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Poravnanje",
|
||||
"Amount": "Količina",
|
||||
"App": "Aplikacija",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Prikaži komentare",
|
||||
"Completed": "Završeno",
|
||||
"Conversion": "Konverzija",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopiraj",
|
||||
"Copy Link": "Kopiraj vezu",
|
||||
"Copy Token": "Kopiraj token",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "Dodaj novu kategoriju za kupovinu",
|
||||
"Create_New_Unit": "Dodaj novu jedinicu",
|
||||
"Created": "Stvoreno",
|
||||
"Credits": "",
|
||||
"Current_Period": "Trenutno razdoblje",
|
||||
"Custom Filter": "Prilagođeni filtar",
|
||||
"CustomImageHelp": "Učitaj sliku za prikaz u pregledu prostora.",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "Nadimci namirnice",
|
||||
"Food_Replace": "Zamjena namirnica",
|
||||
"Foods": "Namirnice",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grupiraj po",
|
||||
"Hide_Food": "Sakrij namirnicu",
|
||||
"Hide_Keyword": "Sakrij ključne riječi",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "Saznajte više",
|
||||
"Link": "Poveznica",
|
||||
"Load_More": "Učitaj više",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Zapis kuhanja",
|
||||
"Log_Recipe_Cooking": "Dnevnik recepata kuhanja",
|
||||
"Logo": "Logotip",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "Poruka",
|
||||
"MissingProperties": "",
|
||||
"Month": "Mjesec",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Premjesti",
|
||||
"MoveCategory": "Premjesti u: ",
|
||||
"Move_Down": "Premjesti dolje",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "Odabrano",
|
||||
"Servings": "Porcije",
|
||||
"Settings": "Postavke",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Podijeli",
|
||||
"ShoppingBackgroundSyncWarning": "Loša mreža, čeka se sinkronizacija...",
|
||||
"Shopping_Categories": "Kategorije Kupovine",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Hozzádta",
|
||||
"Added_on": "Hozzáadva",
|
||||
"Advanced": "Haladó",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Igazítás",
|
||||
"Amount": "Összeg",
|
||||
"App": "Applikáció",
|
||||
@@ -56,6 +63,7 @@
|
||||
"Comments_setting": "Hozzászólások megjelenítése",
|
||||
"Completed": "Kész",
|
||||
"Conversion": "Konverzió",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Másolás",
|
||||
"Copy Link": "Link másolása",
|
||||
"Copy Token": "Token másolása",
|
||||
@@ -71,6 +79,7 @@
|
||||
"Create_New_Shopping Category": "Új vásárlási kategória létrehozása",
|
||||
"Create_New_Shopping_Category": "Új vásárlási kategória hozzáadása",
|
||||
"Create_New_Unit": "Új mértékegység hozzáadása",
|
||||
"Credits": "",
|
||||
"Current_Period": "Jelenlegi periódus",
|
||||
"Custom Filter": "Egyéni szűrő",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -126,10 +135,13 @@
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "Étel cseréje",
|
||||
"Foods": "Alapanyagok",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Csoportosítva",
|
||||
"Hide_Food": "Alapanyag elrejtése",
|
||||
"Hide_Keyword": "Kulcsszavak elrejtése",
|
||||
@@ -179,6 +191,8 @@
|
||||
"Learn_More": "Tudjon meg többet",
|
||||
"Link": "Link",
|
||||
"Load_More": "Továbbiak betöltése",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Főzés naplózása",
|
||||
"Log_Recipe_Cooking": "Főzés naplózása",
|
||||
"Make_Header": "Átalakítás címsorra",
|
||||
@@ -197,6 +211,8 @@
|
||||
"Message": "Üzenet",
|
||||
"MissingProperties": "",
|
||||
"Month": "Hónap",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Mozgatás",
|
||||
"MoveCategory": "Áthelyezés ide: ",
|
||||
"Move_Down": "Lefelé mozgatás",
|
||||
@@ -301,6 +317,7 @@
|
||||
"Selected": "Kiválasztott",
|
||||
"Servings": "Adag",
|
||||
"Settings": "Beállítások",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Megosztás",
|
||||
"Shopping_Categories": "Vásárlási kategóriák",
|
||||
"Shopping_Category": "Vásárlási kategória",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"Add": "",
|
||||
@@ -8,6 +9,12 @@
|
||||
"Add_to_Plan": "Ավելացնել պլանին",
|
||||
"Add_to_Shopping": "Ավելացնել գնումներին",
|
||||
"Advanced Search Settings": "Ընդլայնված փնտրման կարգավորումներ",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Apply": "",
|
||||
"Automate": "Ավտոմատացնել",
|
||||
"BatchDeleteConfirm": "",
|
||||
@@ -22,11 +29,13 @@
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"Close": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "",
|
||||
"Create": "Ստեղծել",
|
||||
"Create_New_Food": "Ավելացնել նոր սննդամթերք",
|
||||
"Create_New_Keyword": "Ավելացնել նոր բանալի բառ",
|
||||
"Create_New_Shopping Category": "Ստեղծել գնումների նոր կատեգորիա",
|
||||
"Credits": "",
|
||||
"DELETE_ERROR": "",
|
||||
"Date": "",
|
||||
"Delete": "",
|
||||
@@ -51,10 +60,13 @@
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Food": "Սննդամթերք",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"Hide_Food": "Թաքցնել սննդամթերքը",
|
||||
"Hide_Keywords": "Թաքցնել բանալի բառը",
|
||||
"Hide_Recipes": "Թաքցնել բաղադրատոմսերը",
|
||||
@@ -69,6 +81,8 @@
|
||||
"Keywords": "",
|
||||
"Link": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Գրանցել եփելը",
|
||||
"Log_Recipe_Cooking": "Գրանցել բաղադրատոմսի օգտագործում",
|
||||
"ManageSubscription": "",
|
||||
@@ -78,6 +92,8 @@
|
||||
"MergeAutomateHelp": "",
|
||||
"Merge_Keyword": "Միացնել բանալի բառը",
|
||||
"MissingProperties": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Տեղափոխել",
|
||||
"Move_Food": "Տեղափոխել սննդամթերքը",
|
||||
"Move_Keyword": "Տեղափոխել բանալի բառը",
|
||||
@@ -124,6 +140,7 @@
|
||||
"Selected": "",
|
||||
"Servings": "",
|
||||
"Settings": "Կարգավորումներ",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"Shopping_Category": "Գնումների կատեգորիա",
|
||||
"Shopping_list": "Գնումների ցուցակ",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"App": "",
|
||||
"Apply": "",
|
||||
"Are_You_Sure": "",
|
||||
@@ -48,6 +55,7 @@
|
||||
"Coming_Soon": "",
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Salin",
|
||||
"Copy Link": "Salin Tautan",
|
||||
"Copy Token": "Salin Token",
|
||||
@@ -63,6 +71,7 @@
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Credits": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -114,10 +123,13 @@
|
||||
"FoodOnHand": "",
|
||||
"Food_Alias": "",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
@@ -164,6 +176,8 @@
|
||||
"Last_name": "",
|
||||
"Link": "Link",
|
||||
"Load_More": "Muat lebih banyak",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Log Memasak",
|
||||
"Log_Recipe_Cooking": "Log Resep Memasak",
|
||||
"Make_Header": "Buat Header",
|
||||
@@ -182,6 +196,8 @@
|
||||
"Message": "",
|
||||
"MissingProperties": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Bergerak",
|
||||
"MoveCategory": "",
|
||||
"Move_Down": "Pindahkan kebawah",
|
||||
@@ -277,6 +293,7 @@
|
||||
"Selected": "Terpilih",
|
||||
"Servings": "Porsi",
|
||||
"Settings": "Pengaturan",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Bagikan",
|
||||
"Shopping_Categories": "Kategori Belanja",
|
||||
"Shopping_Category": "Kategori Belanja",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "",
|
||||
"Amount": "",
|
||||
"App": "",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"Conversion": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Created": "",
|
||||
"Credits": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "",
|
||||
@@ -142,10 +151,13 @@
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
@@ -196,6 +208,8 @@
|
||||
"Learn_More": "",
|
||||
"Link": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"Logo": "",
|
||||
@@ -215,6 +229,8 @@
|
||||
"Message": "",
|
||||
"MissingProperties": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"Move_Down": "",
|
||||
@@ -327,6 +343,7 @@
|
||||
"Selected": "",
|
||||
"Servings": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"ShoppingBackgroundSyncWarning": "",
|
||||
"Shopping_Categories": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "IA",
|
||||
"AIImportSubtitle": "Utilizza IA per importare le immagini delle ricette.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -27,6 +28,12 @@
|
||||
"Admin": "Amministratore",
|
||||
"Advanced": "Avanzate",
|
||||
"Advanced Search Settings": "Impostazioni avanzate di ricerca",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Allineamento",
|
||||
"AllRecipes": "Tutte le ricette",
|
||||
"Amount": "Quantità",
|
||||
@@ -88,6 +95,7 @@
|
||||
"Continue": "Continua",
|
||||
"Conversion": "Conversione",
|
||||
"ConversionsHelp": "Con le conversioni è possibile calcolare la quantità di un alimento in diverse unità. Attualmente, questo metodo viene utilizzato solo per il calcolo delle proprietà, ma in futuro potrebbe essere utilizzato anche in altre parti del tandoor. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Registro di cucina",
|
||||
"CookLogHelp": "Le voci nel registro di cucina per le ricette. ",
|
||||
"Cooked": "Cucinati",
|
||||
@@ -110,6 +118,7 @@
|
||||
"Create_New_Unit": "Aggiungi nuova unità",
|
||||
"Created": "Creata",
|
||||
"CreatedBy": "Creata da",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Periodo attuale",
|
||||
"Custom Filter": "Filtro personalizzato",
|
||||
@@ -203,11 +212,14 @@
|
||||
"Food_Replace": "Sostituisci alimento",
|
||||
"Foods": "Alimenti",
|
||||
"Friday": "Venerdì",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Fulltext",
|
||||
"FulltextHelp": "Campi per la ricerca full text. Nota: i metodi di ricerca 'web', 'phrase', e 'raw' funzionano solo con i campi fulltext.",
|
||||
"Fuzzy": "Fuzzy",
|
||||
"FuzzySearchHelp": "Utilizza la ricerca fuzzy per trovare voci anche quando ci sono differenze nel modo in cui la parola è scritta.",
|
||||
"GettingStarted": "Iniziamo",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Raggruppa per",
|
||||
"HeaderWarning": "Attenzione: la modifica in un'intestazione elimina l'importo/unità/alimento",
|
||||
"Headline": "Intestazione",
|
||||
@@ -275,6 +287,8 @@
|
||||
"Link": "Collegamento",
|
||||
"Load": "Carica",
|
||||
"Load_More": "Carica altro",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registro ricette cucinate",
|
||||
"Log_Recipe_Cooking": "Aggiungi a ricette cucinate",
|
||||
"Logo": "Logo",
|
||||
@@ -303,6 +317,8 @@
|
||||
"ModelSelectResultsHelp": "Cerca altri risultati",
|
||||
"Monday": "Lunedì",
|
||||
"Month": "Mese",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Altro",
|
||||
"Move": "Sposta",
|
||||
"MoveCategory": "Sposta in: ",
|
||||
@@ -456,6 +472,7 @@
|
||||
"Servings": "Porzioni",
|
||||
"ServingsText": "Testo porzioni",
|
||||
"Settings": "Impostazioni",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Condividi",
|
||||
"ShopLater": "Compra dopo",
|
||||
"ShopNow": "Compra subito",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "",
|
||||
"Amount": "Suma",
|
||||
"App": "",
|
||||
@@ -56,6 +63,7 @@
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"Conversion": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
@@ -72,6 +80,7 @@
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Credits": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -128,10 +137,13 @@
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
@@ -181,6 +193,8 @@
|
||||
"Learn_More": "",
|
||||
"Link": "",
|
||||
"Load_More": "Įkelti daugiau",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Užregistruoti patiekalo gaminimą",
|
||||
"Log_Recipe_Cooking": "Užregistruoti recepto pagaminimą",
|
||||
"Make_Header": "Padaryti antraštę",
|
||||
@@ -199,6 +213,8 @@
|
||||
"Message": "",
|
||||
"MissingProperties": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"Move_Down": "Nuleisti žemyn",
|
||||
@@ -305,6 +321,7 @@
|
||||
"Selected": "",
|
||||
"Servings": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"Shopping_Categories": "",
|
||||
"Shopping_Category": "",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "",
|
||||
"Amount": "",
|
||||
"App": "",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"Conversion": "",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Created": "",
|
||||
"Credits": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "",
|
||||
"Link": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"Logo": "",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "",
|
||||
"MissingProperties": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"Move_Down": "",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "",
|
||||
"Servings": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"ShoppingBackgroundSyncWarning": "",
|
||||
"Shopping_Categories": "",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Lagt til av",
|
||||
"Added_on": "Lagt til",
|
||||
"Advanced": "Avansert",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Justering",
|
||||
"Amount": "Mengde",
|
||||
"App": "App",
|
||||
@@ -55,6 +62,7 @@
|
||||
"Comments_setting": "",
|
||||
"Completed": "Fullført",
|
||||
"Conversion": "Omregn enhet",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopier",
|
||||
"Copy Link": "Kopier lenke",
|
||||
"Copy Token": "Kopier Token",
|
||||
@@ -71,6 +79,7 @@
|
||||
"Create_New_Shopping Category": "Opprett ny handle kategori",
|
||||
"Create_New_Shopping_Category": "Opprett new handle kategori",
|
||||
"Create_New_Unit": "Opprett ny enhet",
|
||||
"Credits": "",
|
||||
"Current_Period": "Gjeldende periode",
|
||||
"Custom Filter": "Egendefinert Filter",
|
||||
"CustomImageHelp": "Last opp et bilde for å vise \"space\"-oversikten.",
|
||||
@@ -134,10 +143,13 @@
|
||||
"FoodOnHand": "Du har {food} på lager.",
|
||||
"Food_Alias": "Matrett Alias",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grupér",
|
||||
"Hide_Food": "Skjul Matrett",
|
||||
"Hide_Keyword": "Skjul nøkkelord",
|
||||
@@ -188,6 +200,8 @@
|
||||
"Learn_More": "Lær mer",
|
||||
"Link": "Lenke",
|
||||
"Load_More": "Last inn flere",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Loggfør tilbereding",
|
||||
"Log_Recipe_Cooking": "Logg oppskriftsbruk",
|
||||
"Make_Header": "Bruk som overskrift",
|
||||
@@ -206,6 +220,8 @@
|
||||
"Message": "Melding",
|
||||
"MissingProperties": "",
|
||||
"Month": "Måned",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Flytt",
|
||||
"MoveCategory": "Flytt til: ",
|
||||
"Move_Down": "Flytt ned",
|
||||
@@ -312,6 +328,7 @@
|
||||
"Selected": "Valgte",
|
||||
"Servings": "Porsjoner",
|
||||
"Settings": "Innstillinger",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Del",
|
||||
"ShoppingBackgroundSyncWarning": "Dårlig nettverkstilkobling, venter på synkronisering...",
|
||||
"Shopping_Categories": "Butikk Kategorier",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "AI",
|
||||
"AIImportSubtitle": "Gebruik Al om afbeeldingen van recepten te importeren.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -28,6 +29,12 @@
|
||||
"Admin": "Beheer",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Advanced Search Settings": "Geavanceerde zoekinstellingen",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Afstemming",
|
||||
"AllRecipes": "Alle recepten",
|
||||
"Amount": "Hoeveelheid",
|
||||
@@ -89,6 +96,7 @@
|
||||
"Continue": "Doorgaan",
|
||||
"Conversion": "Omrekening",
|
||||
"ConversionsHelp": "Met omrekeningen kun je de hoeveelheid van een ingrediënt in verschillende eenheden berekenen. Momenteel wordt dit alleen gebruikt voor het berekenen van eigenschappen, later kan het ook in andere onderdelen van Tandoor gebruikt worden. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Kooklogboek",
|
||||
"CookLogHelp": "Items in het kooklogboek voor recepten. ",
|
||||
"Cooked": "Gekookt",
|
||||
@@ -111,6 +119,7 @@
|
||||
"Create_New_Unit": "Voeg nieuwe eenheid toe",
|
||||
"Created": "Gemaakt",
|
||||
"CreatedBy": "Gemaakt door",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Huidige periode",
|
||||
"Custom Filter": "Aangepast filter",
|
||||
@@ -204,11 +213,14 @@
|
||||
"Food_Replace": "Voedingsmiddelen vervangen",
|
||||
"Foods": "Voedingsmiddelen",
|
||||
"Friday": "Vrijdag",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Volledige tekst",
|
||||
"FulltextHelp": "Velden voor volledige tekstzoekopdrachten. Opmerking: de zoekmethoden ‘web’, ‘zin’ en ‘ruw’ werken alleen met volledige tekstvelden.",
|
||||
"Fuzzy": "Fuzzy",
|
||||
"FuzzySearchHelp": "Gebruik fuzzy search om items te vinden, zelfs als het woord anders is gespeld.",
|
||||
"GettingStarted": "Aan de slag",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Groepeer per",
|
||||
"HeaderWarning": "Waarschuwing: Het wijzigen naar een kop verwijdert de hoeveelheid/eenheid/voedingsmiddel",
|
||||
"Headline": "Koptekst",
|
||||
@@ -276,6 +288,8 @@
|
||||
"Link": "Link",
|
||||
"Load": "Laden",
|
||||
"Load_More": "Laad meer",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registreer bereiding",
|
||||
"Log_Recipe_Cooking": "Bereiding registreren",
|
||||
"Logo": "Logo",
|
||||
@@ -304,6 +318,8 @@
|
||||
"ModelSelectResultsHelp": "Zoek naar meer resultaten",
|
||||
"Monday": "Maandag",
|
||||
"Month": "Maand",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Meer",
|
||||
"Move": "Verplaats",
|
||||
"MoveCategory": "Verplaats naar: ",
|
||||
@@ -457,6 +473,7 @@
|
||||
"Servings": "Porties",
|
||||
"ServingsText": "Portie tekst",
|
||||
"Settings": "Instellingen",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Deel",
|
||||
"ShopLater": "Later boodschappen doen",
|
||||
"ShopNow": "Nu boodschappen doen",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -25,6 +26,12 @@
|
||||
"Admin": "Administator",
|
||||
"Advanced": "Zaawansowany",
|
||||
"Advanced Search Settings": "Ustawienia zaawansowanego wyszukiwania",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Wyrównanie",
|
||||
"AllRecipes": "Wszystkie przepisy",
|
||||
"Amount": "Ilość",
|
||||
@@ -83,6 +90,7 @@
|
||||
"Confirm": "Potwierdź",
|
||||
"Continue": "Kontynuuj",
|
||||
"Conversion": "Konwersja",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopiuj",
|
||||
"Copy Link": "Skopiuj link",
|
||||
"Copy Token": "Kopiuj Token",
|
||||
@@ -100,6 +108,7 @@
|
||||
"Create_New_Shopping_Category": "Dodaj nową kategorię zakupów",
|
||||
"Create_New_Unit": "Dodaj nowa jednostkę",
|
||||
"Created": "Utworzony",
|
||||
"Credits": "",
|
||||
"Current_Period": "Bieżący okres",
|
||||
"Custom Filter": "Filtr niestandardowy",
|
||||
"CustomImageHelp": "Prześlij obraz, który będzie wyświetlany w przeglądzie przestrzeni.",
|
||||
@@ -169,10 +178,13 @@
|
||||
"Food_Alias": "Alias żywności",
|
||||
"Food_Replace": "Zastąp produkt",
|
||||
"Foods": "Żywność",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grupuj według",
|
||||
"Hide_Food": "Ukryj żywność",
|
||||
"Hide_Keyword": "Ukryj słowa kluczowe",
|
||||
@@ -223,6 +235,8 @@
|
||||
"Learn_More": "Dowiedz się więcej",
|
||||
"Link": "Link",
|
||||
"Load_More": "Załaduj więcej",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Zanotuj ugotowanie",
|
||||
"Log_Recipe_Cooking": "Zaloguj gotowanie przepisu",
|
||||
"Logo": "Logo",
|
||||
@@ -242,6 +256,8 @@
|
||||
"Message": "Wiadomość",
|
||||
"MissingProperties": "",
|
||||
"Month": "Miesiąc",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Przenieś",
|
||||
"MoveCategory": "Przenieś do: ",
|
||||
"Move_Down": "Przesunąć w dół",
|
||||
@@ -354,6 +370,7 @@
|
||||
"Selected": "Wybrane",
|
||||
"Servings": "Porcje",
|
||||
"Settings": "Ustawienia",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Udostępnij",
|
||||
"ShoppingBackgroundSyncWarning": "Słaba sieć, oczekiwanie na synchronizację...",
|
||||
"Shopping_Categories": "Kategorie zakupów",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"Add": "Adicionar",
|
||||
@@ -14,6 +15,12 @@
|
||||
"Added_by": "Adicionado por",
|
||||
"Added_on": "Adicionado a",
|
||||
"Advanced": "Avançado",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Alinhamento",
|
||||
"Amount": "Quantidade",
|
||||
"Apply": "",
|
||||
@@ -46,6 +53,7 @@
|
||||
"Coming_Soon": "",
|
||||
"Completed": "Completo",
|
||||
"Conversion": "Conversão",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Copiar",
|
||||
"Copy Link": "Copiar Ligação",
|
||||
"Copy Token": "Copiar Chave",
|
||||
@@ -60,6 +68,7 @@
|
||||
"Create_New_Shopping Category": "Criar nova categoria de Compras",
|
||||
"Create_New_Shopping_Category": "Adicionar nova categoria de compras",
|
||||
"Create_New_Unit": "Adicionar nova unidade",
|
||||
"Credits": "",
|
||||
"Current_Period": "Período atual",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "Fazer upload de uma image para mostrar na visão geral do espaço.",
|
||||
@@ -114,10 +123,13 @@
|
||||
"FoodOnHand": "Tem {food} disponível.",
|
||||
"Food_Alias": "Alcunha da comida",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Agrupar por",
|
||||
"Hide_Food": "Esconder comida",
|
||||
"Hide_Keyword": "",
|
||||
@@ -155,6 +167,8 @@
|
||||
"Learn_More": "Aprenda mais",
|
||||
"Link": "Ligação",
|
||||
"Load_More": "Carregar Mais",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registrar Culinária",
|
||||
"Log_Recipe_Cooking": "Registrar Receitas de Culinária",
|
||||
"Make_Header": "Tornar cabeçalho",
|
||||
@@ -171,6 +185,8 @@
|
||||
"Merge_Keyword": "Unir palavra-chave",
|
||||
"MissingProperties": "",
|
||||
"Month": "Mês",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Mover",
|
||||
"MoveCategory": "Mover para: ",
|
||||
"Move_Down": "Mover para baixo",
|
||||
@@ -267,6 +283,7 @@
|
||||
"Selected": "Selecionado",
|
||||
"Servings": "Doses",
|
||||
"Settings": "Definições",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Partilhar",
|
||||
"Shopping_Categories": "Categorias de Compras",
|
||||
"Shopping_Category": "Categoria de Compras",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "IA",
|
||||
"AIImportSubtitle": "Use IA para importar imagens das receitas.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -26,6 +27,12 @@
|
||||
"Added_on": "Incluído Em",
|
||||
"Admin": "Administrador",
|
||||
"Advanced": "Avançado",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Alinhamento",
|
||||
"AllRecipes": "Todas Receitas",
|
||||
"Amount": "Quantidade",
|
||||
@@ -87,6 +94,7 @@
|
||||
"Continue": "Continuar",
|
||||
"Conversion": "Conversão",
|
||||
"ConversionsHelp": "Com conversões, você pode calcular a quantidade de um alimento em diferentes unidades. Atualmente, isso é usado apenas para cálculo de propriedades, posteriormente poderá ser usado em outras partes do Tandoor. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Registro de cozimento",
|
||||
"CookLogHelp": "Entradas no registro de cozimento para receitas. ",
|
||||
"Cooked": "Cozido",
|
||||
@@ -109,6 +117,7 @@
|
||||
"Create_New_Unit": "Incluir Nova Unidade",
|
||||
"Created": "Criado",
|
||||
"CreatedBy": "Criado por",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Período Atual",
|
||||
"Custom Filter": "Filtro Customizado",
|
||||
@@ -202,11 +211,14 @@
|
||||
"Food_Replace": "Substituir Alimento",
|
||||
"Foods": "Alimentos",
|
||||
"Friday": "Sexta-feira",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Texto completo",
|
||||
"FulltextHelp": "Campos para pesquisa textual completa. Observação: os métodos de pesquisa 'web', 'phrase' e 'raw' só funcionam com campos de pesquisa textual completa.",
|
||||
"Fuzzy": "Fuzzy",
|
||||
"FuzzySearchHelp": "Use pesquisa fuzzy para encontrar registros mesmo quando existem diferenças na grafia das palavras utilizadas.",
|
||||
"GettingStarted": "Começando",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Agrupar Por",
|
||||
"HeaderWarning": "Alerta: Mudanças de Cabeçalho apagam a Quantidade/Unidade/Alimento",
|
||||
"Headline": "Título",
|
||||
@@ -274,6 +286,8 @@
|
||||
"Link": "Link",
|
||||
"Load": "Carregar",
|
||||
"Load_More": "Carregar mais",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Registro de Cozinha",
|
||||
"Log_Recipe_Cooking": "Registrar receitas feitas",
|
||||
"Logo": "Logotipo",
|
||||
@@ -296,6 +310,8 @@
|
||||
"Message": "Mensagem",
|
||||
"MissingProperties": "",
|
||||
"Month": "Mês",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Mover",
|
||||
"MoveCategory": "Mover Para: ",
|
||||
"Move_Down": "Mover para baixo",
|
||||
@@ -402,6 +418,7 @@
|
||||
"Selected": "Selecionado",
|
||||
"Servings": "Porções",
|
||||
"Settings": "Configurações",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Compartilhar",
|
||||
"ShoppingBackgroundSyncWarning": "Rede ruim, aguardando sincronização...",
|
||||
"Shopping_Categories": "Categorias de Mercado",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -17,6 +18,12 @@
|
||||
"Added_on": "Adăugat la",
|
||||
"Advanced": "Avansat",
|
||||
"Advanced Search Settings": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Amount": "Cantitate",
|
||||
"App": "Aplicație",
|
||||
"Apply": "",
|
||||
@@ -53,6 +60,7 @@
|
||||
"Coming_Soon": "În curând",
|
||||
"Comments_setting": "Afișează comentarii",
|
||||
"Completed": "Completat",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Copie",
|
||||
"Copy Link": "Copiere link",
|
||||
"Copy Token": "Copiere token",
|
||||
@@ -69,6 +77,7 @@
|
||||
"Create_New_Shopping Category": "Creați o nouă categorie de cumpărături",
|
||||
"Create_New_Shopping_Category": "Adaugă categorie de cumpărături nouă",
|
||||
"Create_New_Unit": "Adaugă unitate nouă",
|
||||
"Credits": "",
|
||||
"Current_Period": "Perioada curentă",
|
||||
"Custom Filter": "Filtru personalizat",
|
||||
"DELETE_ERROR": "",
|
||||
@@ -121,10 +130,13 @@
|
||||
"FoodOnHand": "Aveți {food} la îndemână.",
|
||||
"Food_Alias": "Pseudonim mâncare",
|
||||
"Foods": "Alimente",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Grupat de",
|
||||
"Hide_Food": "Ascunde mâncare",
|
||||
"Hide_Keyword": "Ascunde cuvintele cheie",
|
||||
@@ -173,6 +185,8 @@
|
||||
"Last_name": "Nume de familie",
|
||||
"Link": "Link",
|
||||
"Load_More": "Încărcați mai mult",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Jurnal de pregătire",
|
||||
"Log_Recipe_Cooking": "Jurnalul rețetelor de pregătire",
|
||||
"Make_Header": "Creare antet",
|
||||
@@ -191,6 +205,8 @@
|
||||
"Message": "Mesaj",
|
||||
"MissingProperties": "",
|
||||
"Month": "Lună",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Mută",
|
||||
"MoveCategory": "Mută la: ",
|
||||
"Move_Down": "Deplasați-vă în jos",
|
||||
@@ -289,6 +305,7 @@
|
||||
"Selected": "Selectat",
|
||||
"Servings": "Porții",
|
||||
"Settings": "Setări",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Împărtășire",
|
||||
"Shopping_Categories": "Categorii de cumpărături",
|
||||
"Shopping_Category": "Categorie de cumpărături",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "AI",
|
||||
"AIImportSubtitle": "Используй AI для импорта изображений рецептов.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -27,6 +28,12 @@
|
||||
"Admin": "Админ",
|
||||
"Advanced": "Расширенный",
|
||||
"Advanced Search Settings": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Выравнивание",
|
||||
"AllRecipes": "Все рецепты",
|
||||
"Amount": "Количество",
|
||||
@@ -88,6 +95,7 @@
|
||||
"Continue": "Продолжить",
|
||||
"Conversion": "Преобразование",
|
||||
"ConversionsHelp": "С помощью преобразований вы можете рассчитывать количество продукта в разных единицах измерения. В настоящее время это используется только для расчёта свойств, но в будущем может применяться и в других частях Tandoor. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Журнал приготовления",
|
||||
"CookLogHelp": "История приготовлений по рецептам. ",
|
||||
"Cooked": "Приготовлено",
|
||||
@@ -110,6 +118,7 @@
|
||||
"Create_New_Unit": "Добавить единицу измерения",
|
||||
"Created": "Создано",
|
||||
"CreatedBy": "Создано пользователем",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Текущий период",
|
||||
"Custom Filter": "Пользовательский фильтр",
|
||||
@@ -203,11 +212,14 @@
|
||||
"Food_Replace": "Замена продукта",
|
||||
"Foods": "Продукты",
|
||||
"Friday": "Пятница",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Полнотекстовый",
|
||||
"FulltextHelp": "Поля, используемые в полнотекстовом поиске. Важно: методы поиска web, phrase и raw применимы только к полнотекстовым полям.",
|
||||
"Fuzzy": "Нечёткий",
|
||||
"FuzzySearchHelp": "Нечёткий поиск позволяет находить записи, даже если в написании есть ошибки или отличия.",
|
||||
"GettingStarted": "Начало работы",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Сгруппировать по",
|
||||
"HeaderWarning": "Внимание: при преобразовании в заголовок удаляются данные о количестве, единице/измерения/продукте.",
|
||||
"Headline": "Заголовок",
|
||||
@@ -275,6 +287,8 @@
|
||||
"Link": "Гиперссылка",
|
||||
"Load": "Загрузить",
|
||||
"Load_More": "Загрузить еще",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Журнал приготовления",
|
||||
"Log_Recipe_Cooking": "Журнал приготовления",
|
||||
"Logo": "Логотип",
|
||||
@@ -302,6 +316,8 @@
|
||||
"ModelSelectResultsHelp": "Показать больше результатов",
|
||||
"Monday": "Понедельник",
|
||||
"Month": "Месяц",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Ещё",
|
||||
"Move": "Переместить",
|
||||
"MoveCategory": "Переместить в: ",
|
||||
@@ -454,6 +470,7 @@
|
||||
"Servings": "Порции",
|
||||
"ServingsText": "Описание порций",
|
||||
"Settings": "Настройки",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Поделиться",
|
||||
"ShopLater": "Купить позже",
|
||||
"ShopNow": "Купить сейчас",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "Umetna inteligenca",
|
||||
"AIImportSubtitle": "Uporabite umetno inteligenco za uvoz slik receptov.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -27,6 +28,12 @@
|
||||
"Admin": "Skrbnik",
|
||||
"Advanced": "Napredno",
|
||||
"Advanced Search Settings": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Poravnava",
|
||||
"AllRecipes": "Vsi recepti",
|
||||
"Amount": "Količina",
|
||||
@@ -88,6 +95,7 @@
|
||||
"Continue": "Nadaljuj",
|
||||
"Conversion": "Pogovor",
|
||||
"ConversionsHelp": "S pretvorbami lahko izračunate količino živila v različnih enotah. Trenutno se to uporablja le za izračun lastnosti, kasneje pa se lahko uporabi tudi v drugih delih Tandoorja. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Kuharski dnevnik",
|
||||
"CookLogHelp": "Vnosi v dnevnik kuhanja za recepte. ",
|
||||
"Cooked": "Kuhano",
|
||||
@@ -110,6 +118,7 @@
|
||||
"Create_New_Unit": "Dodaj novo enoto",
|
||||
"Created": "Ustvarjeno",
|
||||
"CreatedBy": "Ustvaril/a",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Trenutno obdobje",
|
||||
"Custom Filter": "Filter po meri",
|
||||
@@ -203,11 +212,14 @@
|
||||
"Food_Replace": "Zamenjava živila",
|
||||
"Foods": "Živila",
|
||||
"Friday": "Petek",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "Celotno besedilo",
|
||||
"FulltextHelp": "Polja za iskanje po celotnem besedilu. Opomba: metode iskanja »splet«, »fraza« in »surovo« delujejo samo s polji po celotnem besedilu.",
|
||||
"Fuzzy": "Nejasno",
|
||||
"FuzzySearchHelp": "Uporabite mehko iskanje za iskanje vnosov, tudi če obstajajo razlike v načinu pisanja besede.",
|
||||
"GettingStarted": "Začetek",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Združi po",
|
||||
"HeaderWarning": "Opozorilo: Sprememba naslova izbriše količino/enoto/hrano",
|
||||
"Headline": "Glavni naslov",
|
||||
@@ -275,6 +287,8 @@
|
||||
"Link": "Hiperpovezava",
|
||||
"Load": "Naloži",
|
||||
"Load_More": "Naloži več",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Zgodovina kuhanja",
|
||||
"Log_Recipe_Cooking": "Beleži kuharski recept",
|
||||
"Logo": "Logotip",
|
||||
@@ -303,6 +317,8 @@
|
||||
"ModelSelectResultsHelp": "Išči več rezultatov",
|
||||
"Monday": "Ponedeljek",
|
||||
"Month": "Mesec",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "Več",
|
||||
"Move": "Premakni",
|
||||
"MoveCategory": "Premakni v: ",
|
||||
@@ -456,6 +472,7 @@
|
||||
"Servings": "Porcije",
|
||||
"ServingsText": "Besedilo o porcijah",
|
||||
"Settings": "Nastavitve",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Deli",
|
||||
"ShopLater": "Nakupujte pozneje",
|
||||
"ShopNow": "Nakupujte zdaj",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"AIImportSubtitle": "Använd AI för att importera bilder av recept.",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -26,6 +27,12 @@
|
||||
"Added_on": "Tillagd på",
|
||||
"Admin": "Administratör",
|
||||
"Advanced": "Avancerat",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Orientering",
|
||||
"AllRecipes": "Alla recept",
|
||||
"Amount": "Mängd",
|
||||
@@ -87,6 +94,7 @@
|
||||
"Continue": "Fortsätt",
|
||||
"Conversion": "Omvandling",
|
||||
"ConversionsHelp": "Med omvandlingar kan du beräkna mängden av ett livsmedel i olika enheter. För närvarande används detta endast för egenskapsberäkning, senare kan det även användas i andra delar av Tandoor. ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "Tillagningslogg",
|
||||
"CookLogHelp": "Poster i tillagningsloggen för recept. ",
|
||||
"Cooked": "Tillagad",
|
||||
@@ -109,6 +117,7 @@
|
||||
"Create_New_Unit": "Lägg till enhet",
|
||||
"Created": "Skapad",
|
||||
"CreatedBy": "Skapad av",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "Nuvarande period",
|
||||
"Custom Filter": "Anpassat filter",
|
||||
@@ -180,10 +189,13 @@
|
||||
"Food_Alias": "Alias för livsmedel",
|
||||
"Food_Replace": "Ersätt ingrediens",
|
||||
"Foods": "Livsmedel",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Gruppera enligt",
|
||||
"Hide_Food": "Dölj livsmedel",
|
||||
"Hide_Keyword": "Dölj nyckelord",
|
||||
@@ -234,6 +246,8 @@
|
||||
"Learn_More": "Läs mer",
|
||||
"Link": "Länk",
|
||||
"Load_More": "Ladda mer",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Logga tillagning",
|
||||
"Log_Recipe_Cooking": "Logga tillagningen av receptet",
|
||||
"Logo": "Logga",
|
||||
@@ -253,6 +267,8 @@
|
||||
"Message": "Meddelande",
|
||||
"MissingProperties": "",
|
||||
"Month": "Månad",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Flytta",
|
||||
"MoveCategory": "Flytta till: ",
|
||||
"Move_Down": "Flytta ned",
|
||||
@@ -365,6 +381,7 @@
|
||||
"Selected": "Vald",
|
||||
"Servings": "Portioner",
|
||||
"Settings": "Inställningar",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Dela",
|
||||
"ShoppingBackgroundSyncWarning": "Dålig uppkoppling, inväntar synkronisering...",
|
||||
"Shopping_Categories": "Shopping kategorier",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "Ekleyen",
|
||||
"Added_on": "Eklenme Zamanı",
|
||||
"Advanced": "Gelişmiş",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Hizalama",
|
||||
"Amount": "Miktar",
|
||||
"App": "Uygulama",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "Yorumları Göster",
|
||||
"Completed": "Tamamlandı",
|
||||
"Conversion": "Dönüşüm",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Kopyala",
|
||||
"Copy Link": "Bağlantıyı Kopyala",
|
||||
"Copy Token": "Anahtarı Kopyala",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "Yeni Alışveriş Kategorisi Ekle",
|
||||
"Create_New_Unit": "Yeni Birim Ekle",
|
||||
"Created": "Oluşturuldu",
|
||||
"Credits": "",
|
||||
"Current_Period": "Mevcut Dönem",
|
||||
"Custom Filter": "Özel Filtre",
|
||||
"CustomImageHelp": "Alan genel bakışında gösterilecek bir resim yükleyin.",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "Yiyecek Takma Adı",
|
||||
"Food_Replace": "Yiyecek Değiştir",
|
||||
"Foods": "Yiyecekler",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "Gruplandırma Ölçütü",
|
||||
"Hide_Food": "Yiyeceği Gizle",
|
||||
"Hide_Keyword": "Anahtar kelimeleri gizle",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "Daha Fazla",
|
||||
"Link": "Bağlantı",
|
||||
"Load_More": "Daha Fazla Yükle",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Günlük Pişirme",
|
||||
"Log_Recipe_Cooking": "Günlük Tarif Pişirme",
|
||||
"Logo": "Logo",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "Mesaj",
|
||||
"MissingProperties": "",
|
||||
"Month": "Ay",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Taşı",
|
||||
"MoveCategory": "Taşı: ",
|
||||
"Move_Down": "Aşağıya Taşı",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "Seçilen",
|
||||
"Servings": "Servis Sayısı",
|
||||
"Settings": "Ayarlar",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Paylaş",
|
||||
"ShoppingBackgroundSyncWarning": "Kötü bağlantı, senkronizasyon bekleniyor...",
|
||||
"Shopping_Categories": "Alışveriş Kategorileri",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"Add": "Додати",
|
||||
@@ -14,6 +15,12 @@
|
||||
"Added_by": "Додано",
|
||||
"Added_on": "Додано На",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "Вирівнювання",
|
||||
"Amount": "Кількість",
|
||||
"App": "",
|
||||
@@ -50,6 +57,7 @@
|
||||
"Coming_Soon": "",
|
||||
"Completed": "Виконано",
|
||||
"Conversion": "Конвертування",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "Копіювати",
|
||||
"Copy Link": "Скопіювати Посилання",
|
||||
"Copy Token": "Скопіювати Токен",
|
||||
@@ -64,6 +72,7 @@
|
||||
"Create_New_Shopping Category": "Створити Нову Категорію Покупок",
|
||||
"Create_New_Shopping_Category": "Додати Нову Категорію Покупок",
|
||||
"Create_New_Unit": "Додати Нову Одиницю",
|
||||
"Credits": "",
|
||||
"Current_Period": "Теперішній Період",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "Завантажте зображення що буде показуватись у огляді простору.",
|
||||
@@ -124,10 +133,13 @@
|
||||
"FoodOnHand": "Ви маєте {food} на руках.",
|
||||
"Food_Alias": "Найменування Їжі",
|
||||
"Foods": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "По Групі",
|
||||
"Hide_Food": "Сховати Їжу",
|
||||
"Hide_Keyword": "",
|
||||
@@ -172,6 +184,8 @@
|
||||
"Learn_More": "Дізнатися Більше",
|
||||
"Link": "Посилання",
|
||||
"Load_More": "Завантажити більше",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "Занотувати приготування",
|
||||
"Log_Recipe_Cooking": "Журнал приготування",
|
||||
"Make_Header": "Створити Заголовок",
|
||||
@@ -188,6 +202,8 @@
|
||||
"Merge_Keyword": "Об'єднати Ключове слово",
|
||||
"MissingProperties": "",
|
||||
"Month": "Місяць",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "Перемістити",
|
||||
"MoveCategory": "Перемістити До: ",
|
||||
"Move_Down": "Перемістити вниз",
|
||||
@@ -290,6 +306,7 @@
|
||||
"Selected": "Вибрано",
|
||||
"Servings": "Порції",
|
||||
"Settings": "Налаштування",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "Поділитися",
|
||||
"Shopping_Categories": "Категорії Покупок",
|
||||
"Shopping_Category": "Категорія Покупок",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -16,6 +17,12 @@
|
||||
"Added_by": "添加者",
|
||||
"Added_on": "添加到",
|
||||
"Advanced": "高级",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "校准",
|
||||
"Amount": "数量",
|
||||
"App": "应用",
|
||||
@@ -57,6 +64,7 @@
|
||||
"Comments_setting": "显示评论",
|
||||
"Completed": "完成",
|
||||
"Conversion": "转换",
|
||||
"ConvertUsingAI": "",
|
||||
"Copy": "复制",
|
||||
"Copy Link": "复制链接",
|
||||
"Copy Token": "复制令牌",
|
||||
@@ -74,6 +82,7 @@
|
||||
"Create_New_Shopping_Category": "添加新的购物类别",
|
||||
"Create_New_Unit": "添加新的单位",
|
||||
"Created": "已创建",
|
||||
"Credits": "",
|
||||
"Current_Period": "本期",
|
||||
"Custom Filter": "自定义筛选器",
|
||||
"CustomImageHelp": "上传图片以在空间概览中显示。",
|
||||
@@ -143,10 +152,13 @@
|
||||
"Food_Alias": "食物别名",
|
||||
"Food_Replace": "食物替换",
|
||||
"Foods": "食物",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "分组",
|
||||
"Hide_Food": "隐藏食物",
|
||||
"Hide_Keyword": "隐藏关键词",
|
||||
@@ -197,6 +209,8 @@
|
||||
"Learn_More": "了解更多",
|
||||
"Link": "链接",
|
||||
"Load_More": "加载更多",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "烹饪记录",
|
||||
"Log_Recipe_Cooking": "食谱烹饪记录",
|
||||
"Logo": "徽标",
|
||||
@@ -216,6 +230,8 @@
|
||||
"Message": "信息",
|
||||
"MissingProperties": "",
|
||||
"Month": "月份",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"Move": "移动",
|
||||
"MoveCategory": "移动到: ",
|
||||
"Move_Down": "下移",
|
||||
@@ -328,6 +344,7 @@
|
||||
"Selected": "选定",
|
||||
"Servings": "份量",
|
||||
"Settings": "设置",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "分享",
|
||||
"ShoppingBackgroundSyncWarning": "网络状况不佳,正在等待进行同步……",
|
||||
"Shopping_Categories": "购物类别",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AI": "人工智慧",
|
||||
"AIImportSubtitle": "以人工智慧匯入食譜圖片。",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "API",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
@@ -26,6 +27,12 @@
|
||||
"Added_on": "添加於",
|
||||
"Admin": "管理者",
|
||||
"Advanced": "高級",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "對齊",
|
||||
"AllRecipes": "所有食譜",
|
||||
"Amount": "數量",
|
||||
@@ -87,6 +94,7 @@
|
||||
"Continue": "繼續",
|
||||
"Conversion": "轉換",
|
||||
"ConversionsHelp": "透過轉換功能,您可以計算食物在不同單位下的數量。目前這僅用於屬性計算,未來也可能用於 Tandoor 的其他部分。 ",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "烹飪記錄",
|
||||
"CookLogHelp": "食譜的烹飪記錄條目。 ",
|
||||
"Cooked": "已烹飪",
|
||||
@@ -109,6 +117,7 @@
|
||||
"Create_New_Unit": "建立新單位",
|
||||
"Created": "建立",
|
||||
"CreatedBy": "建立者",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "Ctrl+K",
|
||||
"Current_Period": "當前期間",
|
||||
"Custom Filter": "自定義篩選器",
|
||||
@@ -202,11 +211,14 @@
|
||||
"Food_Replace": "食物替換",
|
||||
"Foods": "食物",
|
||||
"Friday": "星期五",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "全文",
|
||||
"FulltextHelp": "全文搜索的字段。注意:'web'、'phrase' 和 'raw' 搜索方法僅對全文欄位有效。",
|
||||
"Fuzzy": "模糊",
|
||||
"FuzzySearchHelp": "使用模糊搜索來查找條目,即使單詞的寫法存在差異。",
|
||||
"GettingStarted": "開始使用",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"GroupBy": "分組依據",
|
||||
"HeaderWarning": "警告:變更為標題會刪除數量/單位/食物",
|
||||
"Headline": "標題",
|
||||
@@ -274,6 +286,8 @@
|
||||
"Link": "連結",
|
||||
"Load": "載入",
|
||||
"Load_More": "載入更多",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "記錄烹飪",
|
||||
"Log_Recipe_Cooking": "記錄食譜烹飪",
|
||||
"Logo": "標誌",
|
||||
@@ -302,6 +316,8 @@
|
||||
"ModelSelectResultsHelp": "搜尋更多結果",
|
||||
"Monday": "星期一",
|
||||
"Month": "月",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "更多",
|
||||
"Move": "移動",
|
||||
"MoveCategory": "移動至: ",
|
||||
@@ -455,6 +471,7 @@
|
||||
"Servings": "份量",
|
||||
"ServingsText": "份量文字",
|
||||
"Settings": "設定",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "分享",
|
||||
"ShopLater": "稍後購物",
|
||||
"ShopNow": "立即購物",
|
||||
|
||||
@@ -3,6 +3,8 @@ apis/ApiTokenAuthApi.ts
|
||||
apis/index.ts
|
||||
index.ts
|
||||
models/AccessToken.ts
|
||||
models/AiLog.ts
|
||||
models/AiProvider.ts
|
||||
models/AlignmentEnum.ts
|
||||
models/AuthToken.ts
|
||||
models/AutoMealPlan.ts
|
||||
@@ -27,6 +29,7 @@ models/ExportRequest.ts
|
||||
models/FdcQuery.ts
|
||||
models/FdcQueryFoods.ts
|
||||
models/Food.ts
|
||||
models/FoodBatchUpdate.ts
|
||||
models/FoodInheritField.ts
|
||||
models/FoodShoppingUpdate.ts
|
||||
models/FoodSimple.ts
|
||||
@@ -57,6 +60,8 @@ models/OpenDataStoreCategory.ts
|
||||
models/OpenDataUnit.ts
|
||||
models/OpenDataUnitTypeEnum.ts
|
||||
models/OpenDataVersion.ts
|
||||
models/PaginatedAiLogList.ts
|
||||
models/PaginatedAiProviderList.ts
|
||||
models/PaginatedAutomationList.ts
|
||||
models/PaginatedBookmarkletImportListList.ts
|
||||
models/PaginatedConnectorConfigList.ts
|
||||
@@ -103,6 +108,7 @@ models/PaginatedUserSpaceList.ts
|
||||
models/PaginatedViewLogList.ts
|
||||
models/ParsedIngredient.ts
|
||||
models/PatchedAccessToken.ts
|
||||
models/PatchedAiProvider.ts
|
||||
models/PatchedAutomation.ts
|
||||
models/PatchedBookmarkletImport.ts
|
||||
models/PatchedConnectorConfig.ts
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import * as runtime from '../runtime';
|
||||
import type {
|
||||
AccessToken,
|
||||
AiLog,
|
||||
AiProvider,
|
||||
AutoMealPlan,
|
||||
Automation,
|
||||
BookmarkletImport,
|
||||
@@ -28,6 +30,7 @@ import type {
|
||||
ExportRequest,
|
||||
FdcQuery,
|
||||
Food,
|
||||
FoodBatchUpdate,
|
||||
FoodInheritField,
|
||||
FoodShoppingUpdate,
|
||||
Group,
|
||||
@@ -49,6 +52,8 @@ import type {
|
||||
OpenDataStore,
|
||||
OpenDataUnit,
|
||||
OpenDataVersion,
|
||||
PaginatedAiLogList,
|
||||
PaginatedAiProviderList,
|
||||
PaginatedAutomationList,
|
||||
PaginatedBookmarkletImportListList,
|
||||
PaginatedConnectorConfigList,
|
||||
@@ -95,6 +100,7 @@ import type {
|
||||
PaginatedViewLogList,
|
||||
ParsedIngredient,
|
||||
PatchedAccessToken,
|
||||
PatchedAiProvider,
|
||||
PatchedAutomation,
|
||||
PatchedBookmarkletImport,
|
||||
PatchedConnectorConfig,
|
||||
@@ -179,6 +185,10 @@ import type {
|
||||
import {
|
||||
AccessTokenFromJSON,
|
||||
AccessTokenToJSON,
|
||||
AiLogFromJSON,
|
||||
AiLogToJSON,
|
||||
AiProviderFromJSON,
|
||||
AiProviderToJSON,
|
||||
AutoMealPlanFromJSON,
|
||||
AutoMealPlanToJSON,
|
||||
AutomationFromJSON,
|
||||
@@ -203,6 +213,8 @@ import {
|
||||
FdcQueryToJSON,
|
||||
FoodFromJSON,
|
||||
FoodToJSON,
|
||||
FoodBatchUpdateFromJSON,
|
||||
FoodBatchUpdateToJSON,
|
||||
FoodInheritFieldFromJSON,
|
||||
FoodInheritFieldToJSON,
|
||||
FoodShoppingUpdateFromJSON,
|
||||
@@ -245,6 +257,10 @@ import {
|
||||
OpenDataUnitToJSON,
|
||||
OpenDataVersionFromJSON,
|
||||
OpenDataVersionToJSON,
|
||||
PaginatedAiLogListFromJSON,
|
||||
PaginatedAiLogListToJSON,
|
||||
PaginatedAiProviderListFromJSON,
|
||||
PaginatedAiProviderListToJSON,
|
||||
PaginatedAutomationListFromJSON,
|
||||
PaginatedAutomationListToJSON,
|
||||
PaginatedBookmarkletImportListListFromJSON,
|
||||
@@ -337,6 +353,8 @@ import {
|
||||
ParsedIngredientToJSON,
|
||||
PatchedAccessTokenFromJSON,
|
||||
PatchedAccessTokenToJSON,
|
||||
PatchedAiProviderFromJSON,
|
||||
PatchedAiProviderToJSON,
|
||||
PatchedAutomationFromJSON,
|
||||
PatchedAutomationToJSON,
|
||||
PatchedBookmarkletImportFromJSON,
|
||||
@@ -522,11 +540,48 @@ export interface ApiAccessTokenUpdateRequest {
|
||||
}
|
||||
|
||||
export interface ApiAiImportCreateRequest {
|
||||
aiProviderId: number;
|
||||
file: string | null;
|
||||
text: string | null;
|
||||
recipeId: string | null;
|
||||
}
|
||||
|
||||
export interface ApiAiLogListRequest {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface ApiAiLogRetrieveRequest {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderCreateRequest {
|
||||
aiProvider: Omit<AiProvider, 'createdAt'|'updatedAt'>;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderDestroyRequest {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderListRequest {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderPartialUpdateRequest {
|
||||
id: number;
|
||||
patchedAiProvider?: Omit<PatchedAiProvider, 'createdAt'|'updatedAt'>;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderRetrieveRequest {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ApiAiProviderUpdateRequest {
|
||||
id: number;
|
||||
aiProvider: Omit<AiProvider, 'createdAt'|'updatedAt'>;
|
||||
}
|
||||
|
||||
export interface ApiAutoPlanCreateRequest {
|
||||
autoMealPlan: AutoMealPlan;
|
||||
}
|
||||
@@ -910,6 +965,10 @@ export interface ApiFdcSearchRetrieveRequest {
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export interface ApiFoodBatchUpdateUpdateRequest {
|
||||
foodBatchUpdate: FoodBatchUpdate;
|
||||
}
|
||||
|
||||
export interface ApiFoodCreateRequest {
|
||||
food: Omit<Food, 'shopping'|'parent'|'numchild'|'fullName'|'substituteOnhand'>;
|
||||
}
|
||||
@@ -983,6 +1042,7 @@ export interface ApiGroupRetrieveRequest {
|
||||
}
|
||||
|
||||
export interface ApiImportCreateRequest {
|
||||
aiProviderId: number;
|
||||
file: string | null;
|
||||
text: string | null;
|
||||
recipeId: string | null;
|
||||
@@ -1732,7 +1792,7 @@ export interface ApiSpaceListRequest {
|
||||
|
||||
export interface ApiSpacePartialUpdateRequest {
|
||||
id: number;
|
||||
patchedSpace?: Omit<PatchedSpace, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'>;
|
||||
patchedSpace?: Omit<PatchedSpace, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'>;
|
||||
}
|
||||
|
||||
export interface ApiSpaceRetrieveRequest {
|
||||
@@ -2369,6 +2429,13 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
* given an image or PDF file convert its content to a structured recipe using AI and the scraping system
|
||||
*/
|
||||
async apiAiImportCreateRaw(requestParameters: ApiAiImportCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<RecipeFromSourceResponse>> {
|
||||
if (requestParameters['aiProviderId'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'aiProviderId',
|
||||
'Required parameter "aiProviderId" was null or undefined when calling apiAiImportCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters['file'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'file',
|
||||
@@ -2412,6 +2479,10 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
formParams = new URLSearchParams();
|
||||
}
|
||||
|
||||
if (requestParameters['aiProviderId'] != null) {
|
||||
formParams.append('ai_provider_id', requestParameters['aiProviderId'] as any);
|
||||
}
|
||||
|
||||
if (requestParameters['file'] != null) {
|
||||
formParams.append('file', requestParameters['file'] as any);
|
||||
}
|
||||
@@ -2443,6 +2514,319 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiLogListRaw(requestParameters: ApiAiLogListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedAiLogList>> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters['page'] != null) {
|
||||
queryParameters['page'] = requestParameters['page'];
|
||||
}
|
||||
|
||||
if (requestParameters['pageSize'] != null) {
|
||||
queryParameters['page_size'] = requestParameters['pageSize'];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-log/`,
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedAiLogListFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiLogList(requestParameters: ApiAiLogListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedAiLogList> {
|
||||
const response = await this.apiAiLogListRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiLogRetrieveRaw(requestParameters: ApiAiLogRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AiLog>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiAiLogRetrieve().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-log/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => AiLogFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiLogRetrieve(requestParameters: ApiAiLogRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AiLog> {
|
||||
const response = await this.apiAiLogRetrieveRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderCreateRaw(requestParameters: ApiAiProviderCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AiProvider>> {
|
||||
if (requestParameters['aiProvider'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'aiProvider',
|
||||
'Required parameter "aiProvider" was null or undefined when calling apiAiProviderCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: AiProviderToJSON(requestParameters['aiProvider']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => AiProviderFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderCreate(requestParameters: ApiAiProviderCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AiProvider> {
|
||||
const response = await this.apiAiProviderCreateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderDestroyRaw(requestParameters: ApiAiProviderDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiAiProviderDestroy().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'DELETE',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderDestroy(requestParameters: ApiAiProviderDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.apiAiProviderDestroyRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderListRaw(requestParameters: ApiAiProviderListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedAiProviderList>> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters['page'] != null) {
|
||||
queryParameters['page'] = requestParameters['page'];
|
||||
}
|
||||
|
||||
if (requestParameters['pageSize'] != null) {
|
||||
queryParameters['page_size'] = requestParameters['pageSize'];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/`,
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedAiProviderListFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderList(requestParameters: ApiAiProviderListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedAiProviderList> {
|
||||
const response = await this.apiAiProviderListRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderPartialUpdateRaw(requestParameters: ApiAiProviderPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AiProvider>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiAiProviderPartialUpdate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'PATCH',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: PatchedAiProviderToJSON(requestParameters['patchedAiProvider']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => AiProviderFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderPartialUpdate(requestParameters: ApiAiProviderPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AiProvider> {
|
||||
const response = await this.apiAiProviderPartialUpdateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderRetrieveRaw(requestParameters: ApiAiProviderRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AiProvider>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiAiProviderRetrieve().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => AiProviderFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderRetrieve(requestParameters: ApiAiProviderRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AiProvider> {
|
||||
const response = await this.apiAiProviderRetrieveRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderUpdateRaw(requestParameters: ApiAiProviderUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AiProvider>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiAiProviderUpdate().'
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters['aiProvider'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'aiProvider',
|
||||
'Required parameter "aiProvider" was null or undefined when calling apiAiProviderUpdate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/ai-provider/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'PUT',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: AiProviderToJSON(requestParameters['aiProvider']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => AiProviderFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiAiProviderUpdate(requestParameters: ApiAiProviderUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AiProvider> {
|
||||
const response = await this.apiAiProviderUpdateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
@@ -5579,6 +5963,46 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiFoodBatchUpdateUpdateRaw(requestParameters: ApiFoodBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<FoodBatchUpdate>> {
|
||||
if (requestParameters['foodBatchUpdate'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'foodBatchUpdate',
|
||||
'Required parameter "foodBatchUpdate" was null or undefined when calling apiFoodBatchUpdateUpdate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/food/batch_update/`,
|
||||
method: 'PUT',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: FoodBatchUpdateToJSON(requestParameters['foodBatchUpdate']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => FoodBatchUpdateFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiFoodBatchUpdateUpdate(requestParameters: ApiFoodBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<FoodBatchUpdate> {
|
||||
const response = await this.apiFoodBatchUpdateUpdateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
@@ -6252,6 +6676,13 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
/**
|
||||
*/
|
||||
async apiImportCreateRaw(requestParameters: ApiImportCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<RecipeFromSourceResponse>> {
|
||||
if (requestParameters['aiProviderId'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'aiProviderId',
|
||||
'Required parameter "aiProviderId" was null or undefined when calling apiImportCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters['file'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'file',
|
||||
@@ -6295,6 +6726,10 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
formParams = new URLSearchParams();
|
||||
}
|
||||
|
||||
if (requestParameters['aiProviderId'] != null) {
|
||||
formParams.append('ai_provider_id', requestParameters['aiProviderId'] as any);
|
||||
}
|
||||
|
||||
if (requestParameters['file'] != null) {
|
||||
formParams.append('file', requestParameters['file'] as any);
|
||||
}
|
||||
|
||||
157
vue3/src/openapi/models/AiLog.ts
Normal file
157
vue3/src/openapi/models/AiLog.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
import type { AiProvider } from './AiProvider';
|
||||
import {
|
||||
AiProviderFromJSON,
|
||||
AiProviderFromJSONTyped,
|
||||
AiProviderToJSON,
|
||||
} from './AiProvider';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AiLog
|
||||
*/
|
||||
export interface AiLog {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
*
|
||||
* @type {AiProvider}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
readonly aiProvider: AiProvider;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
_function: string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
creditCost: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
creditsFromBalance?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
inputTokens?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
outputTokens?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
startTime?: Date;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
endTime?: Date;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
createdBy?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
readonly createdAt: Date;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiLog
|
||||
*/
|
||||
readonly updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the AiLog interface.
|
||||
*/
|
||||
export function instanceOfAiLog(value: object): value is AiLog {
|
||||
if (!('aiProvider' in value) || value['aiProvider'] === undefined) return false;
|
||||
if (!('_function' in value) || value['_function'] === undefined) return false;
|
||||
if (!('creditCost' in value) || value['creditCost'] === undefined) return false;
|
||||
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
|
||||
if (!('updatedAt' in value) || value['updatedAt'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function AiLogFromJSON(json: any): AiLog {
|
||||
return AiLogFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function AiLogFromJSONTyped(json: any, ignoreDiscriminator: boolean): AiLog {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': json['id'] == null ? undefined : json['id'],
|
||||
'aiProvider': AiProviderFromJSON(json['ai_provider']),
|
||||
'_function': json['function'],
|
||||
'creditCost': json['credit_cost'],
|
||||
'creditsFromBalance': json['credits_from_balance'] == null ? undefined : json['credits_from_balance'],
|
||||
'inputTokens': json['input_tokens'] == null ? undefined : json['input_tokens'],
|
||||
'outputTokens': json['output_tokens'] == null ? undefined : json['output_tokens'],
|
||||
'startTime': json['start_time'] == null ? undefined : (new Date(json['start_time'])),
|
||||
'endTime': json['end_time'] == null ? undefined : (new Date(json['end_time'])),
|
||||
'createdBy': json['created_by'] == null ? undefined : json['created_by'],
|
||||
'createdAt': (new Date(json['created_at'])),
|
||||
'updatedAt': (new Date(json['updated_at'])),
|
||||
};
|
||||
}
|
||||
|
||||
export function AiLogToJSON(value?: Omit<AiLog, 'aiProvider'|'createdAt'|'updatedAt'> | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': value['id'],
|
||||
'function': value['_function'],
|
||||
'credit_cost': value['creditCost'],
|
||||
'credits_from_balance': value['creditsFromBalance'],
|
||||
'input_tokens': value['inputTokens'],
|
||||
'output_tokens': value['outputTokens'],
|
||||
'start_time': value['startTime'] == null ? undefined : ((value['startTime'] as any).toISOString()),
|
||||
'end_time': value['endTime'] == null ? undefined : ((value['endTime'] as any).toISOString()),
|
||||
'created_by': value['createdBy'],
|
||||
};
|
||||
}
|
||||
|
||||
134
vue3/src/openapi/models/AiProvider.ts
Normal file
134
vue3/src/openapi/models/AiProvider.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AiProvider
|
||||
*/
|
||||
export interface AiProvider {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
apiKey?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
modelName: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
logCreditCost?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
space?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
readonly createdAt: Date;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof AiProvider
|
||||
*/
|
||||
readonly updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the AiProvider interface.
|
||||
*/
|
||||
export function instanceOfAiProvider(value: object): value is AiProvider {
|
||||
if (!('name' in value) || value['name'] === undefined) return false;
|
||||
if (!('modelName' in value) || value['modelName'] === undefined) return false;
|
||||
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
|
||||
if (!('updatedAt' in value) || value['updatedAt'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function AiProviderFromJSON(json: any): AiProvider {
|
||||
return AiProviderFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function AiProviderFromJSONTyped(json: any, ignoreDiscriminator: boolean): AiProvider {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': json['id'] == null ? undefined : json['id'],
|
||||
'name': json['name'],
|
||||
'description': json['description'] == null ? undefined : json['description'],
|
||||
'apiKey': json['api_key'] == null ? undefined : json['api_key'],
|
||||
'modelName': json['model_name'],
|
||||
'url': json['url'] == null ? undefined : json['url'],
|
||||
'logCreditCost': json['log_credit_cost'] == null ? undefined : json['log_credit_cost'],
|
||||
'space': json['space'] == null ? undefined : json['space'],
|
||||
'createdAt': (new Date(json['created_at'])),
|
||||
'updatedAt': (new Date(json['updated_at'])),
|
||||
};
|
||||
}
|
||||
|
||||
export function AiProviderToJSON(value?: Omit<AiProvider, 'createdAt'|'updatedAt'> | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': value['id'],
|
||||
'name': value['name'],
|
||||
'description': value['description'],
|
||||
'api_key': value['apiKey'],
|
||||
'model_name': value['modelName'],
|
||||
'url': value['url'],
|
||||
'log_credit_cost': value['logCreditCost'],
|
||||
'space': value['space'],
|
||||
};
|
||||
}
|
||||
|
||||
222
vue3/src/openapi/models/FoodBatchUpdate.ts
Normal file
222
vue3/src/openapi/models/FoodBatchUpdate.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface FoodBatchUpdate
|
||||
*/
|
||||
export interface FoodBatchUpdate {
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
foods: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
category?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteAdd: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteRemove: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteSet: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteRemoveAll?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
inheritFieldsAdd: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
inheritFieldsRemove: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
inheritFieldsSet: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
inheritFieldsRemoveAll?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
childInheritFieldsAdd: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
childInheritFieldsRemove: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
childInheritFieldsSet: Array<number>;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
childInheritFieldsRemoveAll?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteChildren?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
substituteSiblings?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
ignoreShopping?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
onHand?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
parentRemove?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof FoodBatchUpdate
|
||||
*/
|
||||
parentSet?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the FoodBatchUpdate interface.
|
||||
*/
|
||||
export function instanceOfFoodBatchUpdate(value: object): value is FoodBatchUpdate {
|
||||
if (!('foods' in value) || value['foods'] === undefined) return false;
|
||||
if (!('substituteAdd' in value) || value['substituteAdd'] === undefined) return false;
|
||||
if (!('substituteRemove' in value) || value['substituteRemove'] === undefined) return false;
|
||||
if (!('substituteSet' in value) || value['substituteSet'] === undefined) return false;
|
||||
if (!('inheritFieldsAdd' in value) || value['inheritFieldsAdd'] === undefined) return false;
|
||||
if (!('inheritFieldsRemove' in value) || value['inheritFieldsRemove'] === undefined) return false;
|
||||
if (!('inheritFieldsSet' in value) || value['inheritFieldsSet'] === undefined) return false;
|
||||
if (!('childInheritFieldsAdd' in value) || value['childInheritFieldsAdd'] === undefined) return false;
|
||||
if (!('childInheritFieldsRemove' in value) || value['childInheritFieldsRemove'] === undefined) return false;
|
||||
if (!('childInheritFieldsSet' in value) || value['childInheritFieldsSet'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function FoodBatchUpdateFromJSON(json: any): FoodBatchUpdate {
|
||||
return FoodBatchUpdateFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function FoodBatchUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): FoodBatchUpdate {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'foods': json['foods'],
|
||||
'category': json['category'] == null ? undefined : json['category'],
|
||||
'substituteAdd': json['substitute_add'],
|
||||
'substituteRemove': json['substitute_remove'],
|
||||
'substituteSet': json['substitute_set'],
|
||||
'substituteRemoveAll': json['substitute_remove_all'] == null ? undefined : json['substitute_remove_all'],
|
||||
'inheritFieldsAdd': json['inherit_fields_add'],
|
||||
'inheritFieldsRemove': json['inherit_fields_remove'],
|
||||
'inheritFieldsSet': json['inherit_fields_set'],
|
||||
'inheritFieldsRemoveAll': json['inherit_fields_remove_all'] == null ? undefined : json['inherit_fields_remove_all'],
|
||||
'childInheritFieldsAdd': json['child_inherit_fields_add'],
|
||||
'childInheritFieldsRemove': json['child_inherit_fields_remove'],
|
||||
'childInheritFieldsSet': json['child_inherit_fields_set'],
|
||||
'childInheritFieldsRemoveAll': json['child_inherit_fields_remove_all'] == null ? undefined : json['child_inherit_fields_remove_all'],
|
||||
'substituteChildren': json['substitute_children'] == null ? undefined : json['substitute_children'],
|
||||
'substituteSiblings': json['substitute_siblings'] == null ? undefined : json['substitute_siblings'],
|
||||
'ignoreShopping': json['ignore_shopping'] == null ? undefined : json['ignore_shopping'],
|
||||
'onHand': json['on_hand'] == null ? undefined : json['on_hand'],
|
||||
'parentRemove': json['parent_remove'] == null ? undefined : json['parent_remove'],
|
||||
'parentSet': json['parent_set'] == null ? undefined : json['parent_set'],
|
||||
};
|
||||
}
|
||||
|
||||
export function FoodBatchUpdateToJSON(value?: FoodBatchUpdate | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'foods': value['foods'],
|
||||
'category': value['category'],
|
||||
'substitute_add': value['substituteAdd'],
|
||||
'substitute_remove': value['substituteRemove'],
|
||||
'substitute_set': value['substituteSet'],
|
||||
'substitute_remove_all': value['substituteRemoveAll'],
|
||||
'inherit_fields_add': value['inheritFieldsAdd'],
|
||||
'inherit_fields_remove': value['inheritFieldsRemove'],
|
||||
'inherit_fields_set': value['inheritFieldsSet'],
|
||||
'inherit_fields_remove_all': value['inheritFieldsRemoveAll'],
|
||||
'child_inherit_fields_add': value['childInheritFieldsAdd'],
|
||||
'child_inherit_fields_remove': value['childInheritFieldsRemove'],
|
||||
'child_inherit_fields_set': value['childInheritFieldsSet'],
|
||||
'child_inherit_fields_remove_all': value['childInheritFieldsRemoveAll'],
|
||||
'substitute_children': value['substituteChildren'],
|
||||
'substitute_siblings': value['substituteSiblings'],
|
||||
'ignore_shopping': value['ignoreShopping'],
|
||||
'on_hand': value['onHand'],
|
||||
'parent_remove': value['parentRemove'],
|
||||
'parent_set': value['parentSet'],
|
||||
};
|
||||
}
|
||||
|
||||
101
vue3/src/openapi/models/PaginatedAiLogList.ts
Normal file
101
vue3/src/openapi/models/PaginatedAiLogList.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
import type { AiLog } from './AiLog';
|
||||
import {
|
||||
AiLogFromJSON,
|
||||
AiLogFromJSONTyped,
|
||||
AiLogToJSON,
|
||||
} from './AiLog';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PaginatedAiLogList
|
||||
*/
|
||||
export interface PaginatedAiLogList {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PaginatedAiLogList
|
||||
*/
|
||||
count: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PaginatedAiLogList
|
||||
*/
|
||||
next?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PaginatedAiLogList
|
||||
*/
|
||||
previous?: string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<AiLog>}
|
||||
* @memberof PaginatedAiLogList
|
||||
*/
|
||||
results: Array<AiLog>;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof PaginatedAiLogList
|
||||
*/
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the PaginatedAiLogList interface.
|
||||
*/
|
||||
export function instanceOfPaginatedAiLogList(value: object): value is PaginatedAiLogList {
|
||||
if (!('count' in value) || value['count'] === undefined) return false;
|
||||
if (!('results' in value) || value['results'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function PaginatedAiLogListFromJSON(json: any): PaginatedAiLogList {
|
||||
return PaginatedAiLogListFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PaginatedAiLogListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedAiLogList {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'count': json['count'],
|
||||
'next': json['next'] == null ? undefined : json['next'],
|
||||
'previous': json['previous'] == null ? undefined : json['previous'],
|
||||
'results': ((json['results'] as Array<any>).map(AiLogFromJSON)),
|
||||
'timestamp': json['timestamp'] == null ? undefined : (new Date(json['timestamp'])),
|
||||
};
|
||||
}
|
||||
|
||||
export function PaginatedAiLogListToJSON(value?: PaginatedAiLogList | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'count': value['count'],
|
||||
'next': value['next'],
|
||||
'previous': value['previous'],
|
||||
'results': ((value['results'] as Array<any>).map(AiLogToJSON)),
|
||||
'timestamp': value['timestamp'] == null ? undefined : ((value['timestamp']).toISOString()),
|
||||
};
|
||||
}
|
||||
|
||||
101
vue3/src/openapi/models/PaginatedAiProviderList.ts
Normal file
101
vue3/src/openapi/models/PaginatedAiProviderList.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
import type { AiProvider } from './AiProvider';
|
||||
import {
|
||||
AiProviderFromJSON,
|
||||
AiProviderFromJSONTyped,
|
||||
AiProviderToJSON,
|
||||
} from './AiProvider';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PaginatedAiProviderList
|
||||
*/
|
||||
export interface PaginatedAiProviderList {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PaginatedAiProviderList
|
||||
*/
|
||||
count: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PaginatedAiProviderList
|
||||
*/
|
||||
next?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PaginatedAiProviderList
|
||||
*/
|
||||
previous?: string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<AiProvider>}
|
||||
* @memberof PaginatedAiProviderList
|
||||
*/
|
||||
results: Array<AiProvider>;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof PaginatedAiProviderList
|
||||
*/
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the PaginatedAiProviderList interface.
|
||||
*/
|
||||
export function instanceOfPaginatedAiProviderList(value: object): value is PaginatedAiProviderList {
|
||||
if (!('count' in value) || value['count'] === undefined) return false;
|
||||
if (!('results' in value) || value['results'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function PaginatedAiProviderListFromJSON(json: any): PaginatedAiProviderList {
|
||||
return PaginatedAiProviderListFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PaginatedAiProviderListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedAiProviderList {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'count': json['count'],
|
||||
'next': json['next'] == null ? undefined : json['next'],
|
||||
'previous': json['previous'] == null ? undefined : json['previous'],
|
||||
'results': ((json['results'] as Array<any>).map(AiProviderFromJSON)),
|
||||
'timestamp': json['timestamp'] == null ? undefined : (new Date(json['timestamp'])),
|
||||
};
|
||||
}
|
||||
|
||||
export function PaginatedAiProviderListToJSON(value?: PaginatedAiProviderList | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'count': value['count'],
|
||||
'next': value['next'],
|
||||
'previous': value['previous'],
|
||||
'results': ((value['results'] as Array<any>).map(AiProviderToJSON)),
|
||||
'timestamp': value['timestamp'] == null ? undefined : ((value['timestamp']).toISOString()),
|
||||
};
|
||||
}
|
||||
|
||||
130
vue3/src/openapi/models/PatchedAiProvider.ts
Normal file
130
vue3/src/openapi/models/PatchedAiProvider.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { mapValues } from '../runtime';
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PatchedAiProvider
|
||||
*/
|
||||
export interface PatchedAiProvider {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
apiKey?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
modelName?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
logCreditCost?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
space?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
readonly createdAt?: Date;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof PatchedAiProvider
|
||||
*/
|
||||
readonly updatedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the PatchedAiProvider interface.
|
||||
*/
|
||||
export function instanceOfPatchedAiProvider(value: object): value is PatchedAiProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function PatchedAiProviderFromJSON(json: any): PatchedAiProvider {
|
||||
return PatchedAiProviderFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PatchedAiProviderFromJSONTyped(json: any, ignoreDiscriminator: boolean): PatchedAiProvider {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': json['id'] == null ? undefined : json['id'],
|
||||
'name': json['name'] == null ? undefined : json['name'],
|
||||
'description': json['description'] == null ? undefined : json['description'],
|
||||
'apiKey': json['api_key'] == null ? undefined : json['api_key'],
|
||||
'modelName': json['model_name'] == null ? undefined : json['model_name'],
|
||||
'url': json['url'] == null ? undefined : json['url'],
|
||||
'logCreditCost': json['log_credit_cost'] == null ? undefined : json['log_credit_cost'],
|
||||
'space': json['space'] == null ? undefined : json['space'],
|
||||
'createdAt': json['created_at'] == null ? undefined : (new Date(json['created_at'])),
|
||||
'updatedAt': json['updated_at'] == null ? undefined : (new Date(json['updated_at'])),
|
||||
};
|
||||
}
|
||||
|
||||
export function PatchedAiProviderToJSON(value?: Omit<PatchedAiProvider, 'createdAt'|'updatedAt'> | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': value['id'],
|
||||
'name': value['name'],
|
||||
'description': value['description'],
|
||||
'api_key': value['apiKey'],
|
||||
'model_name': value['modelName'],
|
||||
'url': value['url'],
|
||||
'log_credit_cost': value['logCreditCost'],
|
||||
'space': value['space'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ import {
|
||||
SpaceNavTextColorEnumFromJSONTyped,
|
||||
SpaceNavTextColorEnumToJSON,
|
||||
} from './SpaceNavTextColorEnum';
|
||||
import type { AiProvider } from './AiProvider';
|
||||
import {
|
||||
AiProviderFromJSON,
|
||||
AiProviderFromJSONTyped,
|
||||
AiProviderToJSON,
|
||||
} from './AiProvider';
|
||||
import type { FoodInheritField } from './FoodInheritField';
|
||||
import {
|
||||
FoodInheritFieldFromJSON,
|
||||
@@ -212,6 +218,36 @@ export interface PatchedSpace {
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
logoColorSvg?: UserFileView;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
aiCreditsMonthly?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
aiCreditsBalance?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
readonly aiMonthlyCreditsUsed?: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
aiEnabled?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {AiProvider}
|
||||
* @memberof PatchedSpace
|
||||
*/
|
||||
aiDefaultProvider?: AiProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,10 +294,15 @@ export function PatchedSpaceFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
'logoColor192': json['logo_color_192'] == null ? undefined : UserFileViewFromJSON(json['logo_color_192']),
|
||||
'logoColor512': json['logo_color_512'] == null ? undefined : UserFileViewFromJSON(json['logo_color_512']),
|
||||
'logoColorSvg': json['logo_color_svg'] == null ? undefined : UserFileViewFromJSON(json['logo_color_svg']),
|
||||
'aiCreditsMonthly': json['ai_credits_monthly'] == null ? undefined : json['ai_credits_monthly'],
|
||||
'aiCreditsBalance': json['ai_credits_balance'] == null ? undefined : json['ai_credits_balance'],
|
||||
'aiMonthlyCreditsUsed': json['ai_monthly_credits_used'] == null ? undefined : json['ai_monthly_credits_used'],
|
||||
'aiEnabled': json['ai_enabled'] == null ? undefined : json['ai_enabled'],
|
||||
'aiDefaultProvider': json['ai_default_provider'] == null ? undefined : AiProviderFromJSON(json['ai_default_provider']),
|
||||
};
|
||||
}
|
||||
|
||||
export function PatchedSpaceToJSON(value?: Omit<PatchedSpace, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'> | null): any {
|
||||
export function PatchedSpaceToJSON(value?: Omit<PatchedSpace, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'> | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
@@ -284,6 +325,10 @@ export function PatchedSpaceToJSON(value?: Omit<PatchedSpace, 'createdBy'|'creat
|
||||
'logo_color_192': UserFileViewToJSON(value['logoColor192']),
|
||||
'logo_color_512': UserFileViewToJSON(value['logoColor512']),
|
||||
'logo_color_svg': UserFileViewToJSON(value['logoColorSvg']),
|
||||
'ai_credits_monthly': value['aiCreditsMonthly'],
|
||||
'ai_credits_balance': value['aiCreditsBalance'],
|
||||
'ai_enabled': value['aiEnabled'],
|
||||
'ai_default_provider': AiProviderToJSON(value['aiDefaultProvider']),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,6 @@ export interface ServerSettings {
|
||||
* @memberof ServerSettings
|
||||
*/
|
||||
enablePdfExport: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ServerSettings
|
||||
*/
|
||||
enableAiImport: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
@@ -159,7 +153,6 @@ export interface ServerSettings {
|
||||
export function instanceOfServerSettings(value: object): value is ServerSettings {
|
||||
if (!('shoppingMinAutosyncInterval' in value) || value['shoppingMinAutosyncInterval'] === undefined) return false;
|
||||
if (!('enablePdfExport' in value) || value['enablePdfExport'] === undefined) return false;
|
||||
if (!('enableAiImport' in value) || value['enableAiImport'] === undefined) return false;
|
||||
if (!('disableExternalConnectors' in value) || value['disableExternalConnectors'] === undefined) return false;
|
||||
if (!('termsUrl' in value) || value['termsUrl'] === undefined) return false;
|
||||
if (!('privacyUrl' in value) || value['privacyUrl'] === undefined) return false;
|
||||
@@ -184,7 +177,6 @@ export function ServerSettingsFromJSONTyped(json: any, ignoreDiscriminator: bool
|
||||
|
||||
'shoppingMinAutosyncInterval': json['shopping_min_autosync_interval'],
|
||||
'enablePdfExport': json['enable_pdf_export'],
|
||||
'enableAiImport': json['enable_ai_import'],
|
||||
'disableExternalConnectors': json['disable_external_connectors'],
|
||||
'termsUrl': json['terms_url'],
|
||||
'privacyUrl': json['privacy_url'],
|
||||
@@ -215,7 +207,6 @@ export function ServerSettingsToJSON(value?: ServerSettings | null): any {
|
||||
|
||||
'shopping_min_autosync_interval': value['shoppingMinAutosyncInterval'],
|
||||
'enable_pdf_export': value['enablePdfExport'],
|
||||
'enable_ai_import': value['enableAiImport'],
|
||||
'disable_external_connectors': value['disableExternalConnectors'],
|
||||
'terms_url': value['termsUrl'],
|
||||
'privacy_url': value['privacyUrl'],
|
||||
|
||||
@@ -31,6 +31,12 @@ import {
|
||||
SpaceNavTextColorEnumFromJSONTyped,
|
||||
SpaceNavTextColorEnumToJSON,
|
||||
} from './SpaceNavTextColorEnum';
|
||||
import type { AiProvider } from './AiProvider';
|
||||
import {
|
||||
AiProviderFromJSON,
|
||||
AiProviderFromJSONTyped,
|
||||
AiProviderToJSON,
|
||||
} from './AiProvider';
|
||||
import type { FoodInheritField } from './FoodInheritField';
|
||||
import {
|
||||
FoodInheritFieldFromJSON,
|
||||
@@ -212,6 +218,36 @@ export interface Space {
|
||||
* @memberof Space
|
||||
*/
|
||||
logoColorSvg?: UserFileView;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Space
|
||||
*/
|
||||
aiCreditsMonthly?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Space
|
||||
*/
|
||||
aiCreditsBalance?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Space
|
||||
*/
|
||||
readonly aiMonthlyCreditsUsed: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof Space
|
||||
*/
|
||||
aiEnabled?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {AiProvider}
|
||||
* @memberof Space
|
||||
*/
|
||||
aiDefaultProvider?: AiProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,6 +265,7 @@ export function instanceOfSpace(value: object): value is Space {
|
||||
if (!('userCount' in value) || value['userCount'] === undefined) return false;
|
||||
if (!('recipeCount' in value) || value['recipeCount'] === undefined) return false;
|
||||
if (!('fileSizeMb' in value) || value['fileSizeMb'] === undefined) return false;
|
||||
if (!('aiMonthlyCreditsUsed' in value) || value['aiMonthlyCreditsUsed'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -269,10 +306,15 @@ export function SpaceFromJSONTyped(json: any, ignoreDiscriminator: boolean): Spa
|
||||
'logoColor192': json['logo_color_192'] == null ? undefined : UserFileViewFromJSON(json['logo_color_192']),
|
||||
'logoColor512': json['logo_color_512'] == null ? undefined : UserFileViewFromJSON(json['logo_color_512']),
|
||||
'logoColorSvg': json['logo_color_svg'] == null ? undefined : UserFileViewFromJSON(json['logo_color_svg']),
|
||||
'aiCreditsMonthly': json['ai_credits_monthly'] == null ? undefined : json['ai_credits_monthly'],
|
||||
'aiCreditsBalance': json['ai_credits_balance'] == null ? undefined : json['ai_credits_balance'],
|
||||
'aiMonthlyCreditsUsed': json['ai_monthly_credits_used'],
|
||||
'aiEnabled': json['ai_enabled'] == null ? undefined : json['ai_enabled'],
|
||||
'aiDefaultProvider': json['ai_default_provider'] == null ? undefined : AiProviderFromJSON(json['ai_default_provider']),
|
||||
};
|
||||
}
|
||||
|
||||
export function SpaceToJSON(value?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'> | null): any {
|
||||
export function SpaceToJSON(value?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'> | null): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
@@ -295,6 +337,10 @@ export function SpaceToJSON(value?: Omit<Space, 'createdBy'|'createdAt'|'maxReci
|
||||
'logo_color_192': UserFileViewToJSON(value['logoColor192']),
|
||||
'logo_color_512': UserFileViewToJSON(value['logoColor512']),
|
||||
'logo_color_svg': UserFileViewToJSON(value['logoColorSvg']),
|
||||
'ai_credits_monthly': value['aiCreditsMonthly'],
|
||||
'ai_credits_balance': value['aiCreditsBalance'],
|
||||
'ai_enabled': value['aiEnabled'],
|
||||
'ai_default_provider': AiProviderToJSON(value['aiDefaultProvider']),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './AccessToken';
|
||||
export * from './AiLog';
|
||||
export * from './AiProvider';
|
||||
export * from './AlignmentEnum';
|
||||
export * from './AuthToken';
|
||||
export * from './AutoMealPlan';
|
||||
@@ -25,6 +27,7 @@ export * from './ExportRequest';
|
||||
export * from './FdcQuery';
|
||||
export * from './FdcQueryFoods';
|
||||
export * from './Food';
|
||||
export * from './FoodBatchUpdate';
|
||||
export * from './FoodInheritField';
|
||||
export * from './FoodShoppingUpdate';
|
||||
export * from './FoodSimple';
|
||||
@@ -55,6 +58,8 @@ export * from './OpenDataStoreCategory';
|
||||
export * from './OpenDataUnit';
|
||||
export * from './OpenDataUnitTypeEnum';
|
||||
export * from './OpenDataVersion';
|
||||
export * from './PaginatedAiLogList';
|
||||
export * from './PaginatedAiProviderList';
|
||||
export * from './PaginatedAutomationList';
|
||||
export * from './PaginatedBookmarkletImportListList';
|
||||
export * from './PaginatedConnectorConfigList';
|
||||
@@ -101,6 +106,7 @@ export * from './PaginatedUserSpaceList';
|
||||
export * from './PaginatedViewLogList';
|
||||
export * from './ParsedIngredient';
|
||||
export * from './PatchedAccessToken';
|
||||
export * from './PatchedAiProvider';
|
||||
export * from './PatchedAutomation';
|
||||
export * from './PatchedBookmarkletImport';
|
||||
export * from './PatchedConnectorConfig';
|
||||
|
||||
@@ -35,6 +35,18 @@
|
||||
<database-model-col model="MealType"></database-model-col>
|
||||
</v-row>
|
||||
|
||||
<template v-if="useUserPreferenceStore().activeSpace.aiEnabled">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<h2>{{ $t('Ai') }}</h2>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<database-model-col model="AiProvider"></database-model-col>
|
||||
<database-model-col model="AiLog"></database-model-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<template v-for="p in TANDOOR_PLUGINS" :key="p.name">
|
||||
<component :is="p.databasePageComponent" v-if="p.databasePageComponent"></component>
|
||||
</template>
|
||||
@@ -82,6 +94,7 @@
|
||||
import DatabaseModelCol from "@/components/display/DatabaseModelCol.vue";
|
||||
import DatabaseLinkCol from "@/components/display/DatabaseLinkCol.vue";
|
||||
import {TANDOOR_PLUGINS} from "@/types/Plugins.ts";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -27,9 +27,17 @@
|
||||
@delete="loadItems({page: page})"></model-edit-dialog>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- TODO build customizable model component system -->
|
||||
<v-card-actions v-if="genericModel.model.name == 'RecipeImport'">
|
||||
<v-btn prepend-icon="fa-solid fa-rotate" color="success" @click="importAllRecipes()">{{ $t('ImportAll') }}</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<v-card-text v-if="genericModel.model.name == 'AiLog'">
|
||||
{{ $t('MonthlyCreditsUsed') }} ({{ useUserPreferenceStore().activeSpace.aiMonthlyCreditsUsed }} / {{ useUserPreferenceStore().activeSpace.aiCreditsMonthly }})
|
||||
{{ $t('AiCreditsBalance') }} : {{ useUserPreferenceStore().activeSpace.aiCreditsBalance }}
|
||||
<v-progress-linear :model-value="useUserPreferenceStore().activeSpace.aiMonthlyCreditsUsed" :max="useUserPreferenceStore().activeSpace.aiCreditsMonthly"></v-progress-linear>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -57,6 +65,9 @@
|
||||
<v-icon icon="fa-solid fa-ellipsis-v"></v-icon>
|
||||
<v-menu activator="parent" close-on-content-click>
|
||||
<v-list density="compact" class="pt-1 pb-1" activatable>
|
||||
<v-list-item prepend-icon="fa-solid fa-list-check" @click="batchEditDialog = true" v-if="genericModel.model.name == 'Food'">
|
||||
{{ $t('BatchEdit') }}
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="fa-solid fa-arrows-to-dot" @click="batchMergeDialog = true" v-if="genericModel.model.isMerge">
|
||||
{{ $t('Merge') }}
|
||||
</v-list-item>
|
||||
@@ -67,6 +78,10 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:item.space="{ item }" v-if="genericModel.model.name == 'AiProvider'">
|
||||
<v-chip label v-if="item.space == null" color="success">{{ $t('Global') }}</v-chip>
|
||||
<v-chip label v-else color="info">{{ $t('Space') }}</v-chip>
|
||||
</template>
|
||||
<template v-slot:item.action="{ item }">
|
||||
<v-btn class="float-right" icon="$menu" variant="plain">
|
||||
<v-icon icon="$menu"></v-icon>
|
||||
@@ -105,10 +120,13 @@
|
||||
</v-row>
|
||||
|
||||
<batch-delete-dialog :items="selectedItems" :model="props.model" v-model="batchDeleteDialog" activator="model"
|
||||
@change="loadItems({page: page, itemsPerPage: pageSize, search: query})"></batch-delete-dialog>
|
||||
@change="loadItems({page: page, itemsPerPage: pageSize, search: query})"></batch-delete-dialog>
|
||||
|
||||
<model-merge-dialog :model="model" :source="selectedItems" v-model="batchMergeDialog" activator="model"
|
||||
@change="loadItems({page: page, itemsPerPage: pageSize, search: query})"></model-merge-dialog>
|
||||
<model-merge-dialog :model="model" :source="selectedItems" v-model="batchMergeDialog" activator="model"
|
||||
@change="loadItems({page: page, itemsPerPage: pageSize, search: query})"></model-merge-dialog>
|
||||
|
||||
<batch-edit-food-dialog :items="selectedItems" v-model="batchEditDialog" v-if="model == 'Food'" activator="model"
|
||||
@change="loadItems({page: page, itemsPerPage: pageSize, search: query})"></batch-edit-food-dialog>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
@@ -132,6 +150,7 @@ import RecipeShareDialog from "@/components/dialogs/RecipeShareDialog.vue";
|
||||
import AddToShoppingDialog from "@/components/dialogs/AddToShoppingDialog.vue";
|
||||
import BatchDeleteDialog from "@/components/dialogs/BatchDeleteDialog.vue";
|
||||
import {useRouteQuery} from "@vueuse/router";
|
||||
import BatchEditFoodDialog from "@/components/dialogs/BatchEditFoodDialog.vue";
|
||||
|
||||
const {t} = useI18n()
|
||||
const router = useRouter()
|
||||
@@ -160,6 +179,7 @@ const selectedItems = ref([] as EditorSupportedTypes[])
|
||||
|
||||
const batchDeleteDialog = ref(false)
|
||||
const batchMergeDialog = ref(false)
|
||||
const batchEditDialog = ref(false)
|
||||
|
||||
// data
|
||||
const loading = ref(false);
|
||||
@@ -203,7 +223,7 @@ function loadItems(options: VDataTableUpdateOptions) {
|
||||
page.value = options.page
|
||||
pageSize.value = options.itemsPerPage
|
||||
|
||||
genericModel.value.list({ query: query.value, page: options.page, pageSize: pageSize.value }).then((r: any) => {
|
||||
genericModel.value.list({query: query.value, page: options.page, pageSize: pageSize.value}).then((r: any) => {
|
||||
items.value = r.results
|
||||
itemCount.value = r.count
|
||||
}).catch((err: any) => {
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6" v-if="useUserPreferenceStore().serverSettings.enableAiImport">
|
||||
<v-col cols="12" md="6" v-if="useUserPreferenceStore().activeSpace.aiEnabled">
|
||||
<v-card
|
||||
:title="$t('AI')"
|
||||
:subtitle="$t('AIImportSubtitle')"
|
||||
@@ -69,7 +69,7 @@
|
||||
:color="(importType == 'ai') ? 'primary' : ''"
|
||||
elevation="1"
|
||||
@click="importType = 'ai'"
|
||||
:disabled="!useUserPreferenceStore().serverSettings.enableAiImport">
|
||||
:disabled="!useUserPreferenceStore().activeSpace.aiEnabled">
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
@@ -140,10 +140,22 @@
|
||||
@keydown.enter="loadRecipeFromUrl({url: importUrl})"></v-text-field>
|
||||
|
||||
<div v-if="importType == 'ai'">
|
||||
<v-btn-toggle v-model="aiMode">
|
||||
<v-btn value="file">{{ $t('File') }}</v-btn>
|
||||
<v-btn value="text">{{ $t('Text') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-row>
|
||||
<v-col md="6">
|
||||
<ModelSelect model="AiProvider" v-model="selectedAiProvider">
|
||||
<template #append>
|
||||
<v-btn icon="$settings" :to="{name:'ModelListPage', params: {model: 'AiProvider'}}" color="success"></v-btn>
|
||||
</template>
|
||||
</ModelSelect>
|
||||
</v-col>
|
||||
<v-col md="6">
|
||||
<v-btn-toggle class="mb-2" border divided v-model="aiMode">
|
||||
<v-btn value="file">{{ $t('File') }}</v-btn>
|
||||
<v-btn value="text">{{ $t('Text') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
<v-file-upload v-model="image" v-if="aiMode == 'file'" :loading="loading" clearable>
|
||||
<template #icon>
|
||||
@@ -540,6 +552,7 @@ import {useI18n} from "vue-i18n";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {
|
||||
AccessToken,
|
||||
AiProvider,
|
||||
ApiApi,
|
||||
ImportLog,
|
||||
Recipe,
|
||||
@@ -648,6 +661,7 @@ const appImportDuplicates = ref(false)
|
||||
const appImportLog = ref<null | ImportLog>(null)
|
||||
const image = ref<null | File>(null)
|
||||
const aiMode = ref<'file' | 'text'>('file')
|
||||
const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().activeSpace.aiDefaultProvider)
|
||||
const editAfterImport = ref(false)
|
||||
|
||||
const bookmarkletToken = ref("")
|
||||
@@ -724,12 +738,15 @@ function loadRecipeFromUrl(recipeFromSourceRequest: RecipeFromSource) {
|
||||
*/
|
||||
function loadRecipeFromAiImport() {
|
||||
let request = null
|
||||
|
||||
if (selectedAiProvider.value == undefined) {
|
||||
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, "No AI Provider selected")
|
||||
}
|
||||
|
||||
if (image.value != null && aiMode.value == 'file') {
|
||||
console.log('file import')
|
||||
request = doAiImport(image.value)
|
||||
request = doAiImport(selectedAiProvider.value.id!, image.value)
|
||||
} else if (sourceImportText.value != '' && aiMode.value == 'text') {
|
||||
console.log('text import')
|
||||
request = doAiImport(null, sourceImportText.value)
|
||||
request = doAiImport(selectedAiProvider.value.id!, null, sourceImportText.value)
|
||||
}
|
||||
|
||||
if (request != null) {
|
||||
@@ -811,13 +828,13 @@ function deleteStep(step: SourceImportStep) {
|
||||
}
|
||||
|
||||
function handleMergeAllSteps(): void {
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps){
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps) {
|
||||
mergeAllSteps(importResponse.value.recipe.steps)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSplitAllSteps(): void {
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps){
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps) {
|
||||
splitAllSteps(importResponse.value.recipe.steps, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,36 +66,37 @@ export const useMealPlanStore = defineStore(_STORE_ID, () => {
|
||||
|
||||
currently_updating.value = [from_date, to_date] // certainly no perfect check but better than nothing
|
||||
loading.value = true
|
||||
const api = new ApiApi()
|
||||
return api.apiMealPlanList({
|
||||
fromDate: DateTime.fromJSDate(from_date).toISODate() as string,
|
||||
toDate: DateTime.fromJSDate(to_date).toISODate() as string,
|
||||
pageSize: 100
|
||||
}).then(r => {
|
||||
let foundIds: number[] = []
|
||||
r.results.forEach((p) => {
|
||||
plans.value.set(p.id!, p)
|
||||
foundIds.push(p.id!)
|
||||
})
|
||||
|
||||
// delete entries that no longer exist
|
||||
plans.value.forEach(p => {
|
||||
if (!foundIds.includes(p.id!)) {
|
||||
plans.value.delete(p.id!)
|
||||
}
|
||||
})
|
||||
|
||||
currently_updating.value = [new Date(0), new Date(0)]
|
||||
}).catch((err) => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
plans.value = new Map<number, MealPlan>()
|
||||
return recLoadMealPlans(from_date, to_date)
|
||||
}
|
||||
return new Promise(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function recLoadMealPlans(from_date: Date, to_date: Date, page: number = 1): Promise<any> {
|
||||
const api = new ApiApi()
|
||||
return api.apiMealPlanList({
|
||||
fromDate: DateTime.fromJSDate(from_date).toISODate() as string,
|
||||
toDate: DateTime.fromJSDate(to_date).toISODate() as string,
|
||||
pageSize: 100,
|
||||
page: page
|
||||
}).then(r => {
|
||||
r.results.forEach((p) => {
|
||||
plans.value.set(p.id!, p)
|
||||
})
|
||||
|
||||
if (r.next) {
|
||||
return recLoadMealPlans(from_date, to_date, page + 1)
|
||||
} else {
|
||||
loading.value = false
|
||||
currently_updating.value = [new Date(0), new Date(0)]
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
})
|
||||
}
|
||||
|
||||
function createOrUpdate(object: MealPlan) {
|
||||
if (object.id == undefined) {
|
||||
return createObject(object)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
AccessToken,
|
||||
AccessToken, AiLog, AiProvider,
|
||||
ApiApi, ApiKeywordMoveUpdateRequest, Automation, type AutomationTypeEnum, ConnectorConfig, CookLog, CustomFilter,
|
||||
Food,
|
||||
Ingredient,
|
||||
@@ -146,6 +146,8 @@ export type EditorSupportedModels =
|
||||
| 'ViewLog'
|
||||
| 'ConnectorConfig'
|
||||
| 'SearchFields'
|
||||
| 'AiProvider'
|
||||
| 'AiLog'
|
||||
|
||||
// used to type methods/parameters in conjunction with configuration type
|
||||
export type EditorSupportedTypes =
|
||||
@@ -180,6 +182,8 @@ export type EditorSupportedTypes =
|
||||
| ViewLog
|
||||
| ConnectorConfig
|
||||
| SearchFields
|
||||
| AiProvider
|
||||
| AiLog
|
||||
|
||||
export const TFood = {
|
||||
name: 'Food',
|
||||
@@ -788,6 +792,55 @@ export const TConnectorConfig = {
|
||||
} as Model
|
||||
registerModel(TConnectorConfig)
|
||||
|
||||
export const TAiProvider = {
|
||||
name: 'AiProvider',
|
||||
localizationKey: 'AiProvider',
|
||||
localizationKeyDescription: 'AiProviderHelp',
|
||||
icon: 'fa-solid fa-wand-magic-sparkles',
|
||||
|
||||
editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/AiProviderEditor.vue`)),
|
||||
|
||||
disableListView: false,
|
||||
toStringKeys: ['name'],
|
||||
isPaginated: true,
|
||||
|
||||
disableCreate: false,
|
||||
disableDelete: false,
|
||||
disableUpdate: false,
|
||||
|
||||
tableHeaders: [
|
||||
{title: 'Name', key: 'name'},
|
||||
{title: 'Global', key: 'space'},
|
||||
{title: 'Actions', key: 'action', align: 'end'},
|
||||
]
|
||||
} as Model
|
||||
registerModel(TAiProvider)
|
||||
|
||||
export const TAiLog = {
|
||||
name: 'AiLog',
|
||||
localizationKey: 'AiLog',
|
||||
localizationKeyDescription: 'AiLogHelp',
|
||||
icon: 'fa-solid fa-wand-magic-sparkles',
|
||||
|
||||
disableListView: false,
|
||||
toStringKeys: ['aiProvider.name', 'function', 'created_at'],
|
||||
isPaginated: true,
|
||||
|
||||
disableCreate: true,
|
||||
disableDelete: true,
|
||||
disableUpdate: true,
|
||||
|
||||
tableHeaders: [
|
||||
{title: 'Type', key: '_function'},
|
||||
{title: 'AiProvider', key: 'aiProvider.name',},
|
||||
{title: 'Credits', key: 'creditCost',},
|
||||
{title: 'FromBalance', key: 'creditsFromBalance',},
|
||||
{title: 'CreatedAt', key: 'createdAt'},
|
||||
{title: 'Actions', key: 'action', align: 'end'},
|
||||
]
|
||||
} as Model
|
||||
registerModel(TAiLog)
|
||||
|
||||
export const TFoodInheritField = {
|
||||
name: 'FoodInheritField',
|
||||
localizationKey: 'FoodInherit',
|
||||
|
||||
Reference in New Issue
Block a user