From 58572f2b00dffa0a45fe78c5bdd65aff02ed4823 Mon Sep 17 00:00:00 2001 From: gray Date: Wed, 5 Feb 2025 14:43:30 +0800 Subject: [PATCH 01/35] model version input to select component --- .../_components/Models/EditModelModal.tsx | 56 +++++++++++++++---- src/FE/pages/admin/models/index.tsx | 23 ++++++-- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/FE/pages/admin/_components/Models/EditModelModal.tsx b/src/FE/pages/admin/_components/Models/EditModelModal.tsx index 81c8a6fb..543811cf 100644 --- a/src/FE/pages/admin/_components/Models/EditModelModal.tsx +++ b/src/FE/pages/admin/_components/Models/EditModelModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; @@ -7,6 +7,7 @@ import useTranslation from '@/hooks/useTranslation'; import { AdminModelDto, GetModelKeysResult, + SimpleModelReferenceDto, UpdateModelDto, } from '@/types/adminApis'; @@ -28,7 +29,11 @@ import { PopoverTrigger, } from '@/components/ui/popover'; -import { deleteModels, putModels } from '@/apis/adminApis'; +import { + deleteModels, + getModelProviderModels, + putModels, +} from '@/apis/adminApis'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -36,6 +41,7 @@ interface IProps { isOpen: boolean; selected: AdminModelDto; modelKeys: GetModelKeysResult[]; + modelVersionList?: SimpleModelReferenceDto[]; onClose: () => void; onSuccessful: () => void; saveLoading?: boolean; @@ -43,10 +49,21 @@ interface IProps { const EditModelModal = (props: IProps) => { const { t } = useTranslation(); - const { isOpen, onClose, selected, onSuccessful, modelKeys } = props; + const { + isOpen, + onClose, + selected, + onSuccessful, + modelKeys, + modelVersionList, + } = props; + + const [modelVersions, setModelVersions] = useState( + modelVersionList || [], + ); const formSchema = z.object({ - modelReferenceName: z.string(), + modelReferenceId: z.string(), name: z .string() .min(1, `${t('This field is require')}`) @@ -62,7 +79,7 @@ const EditModelModal = (props: IProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - modelReferenceName: '', + modelReferenceId: '', name: '', modelId: '', enabled: true, @@ -81,7 +98,7 @@ const EditModelModal = (props: IProps) => { inputTokenPrice1M: values.inputPrice1M, outputTokenPrice1M: values.outputPrice1M, modelKeyId: parseInt(values.modelKeyId!), - modelReferenceId: selected.modelReferenceId, + modelReferenceId: parseInt(values.modelReferenceId), name: values.name!, }; putModels(values.modelId!, dto).then(() => { @@ -116,7 +133,7 @@ const EditModelModal = (props: IProps) => { const { name, modelId, - modelReferenceName, + modelReferenceId, enabled, modelKeyId, deploymentName, @@ -130,10 +147,23 @@ const EditModelModal = (props: IProps) => { form.setValue('deploymentName', deploymentName || ''); form.setValue('inputPrice1M', inputTokenPrice1M); form.setValue('outputPrice1M', outputTokenPrice1M); - form.setValue('modelReferenceName', modelReferenceName); + form.setValue('modelReferenceId', modelReferenceId.toString()); } }, [isOpen]); + useEffect(() => { + const subscription = form.watch(async (value, { name, type }) => { + if (name === 'modelKeyId' && type === 'change') { + const modelKeyId = value.modelKeyId; + const modelProviderId = modelKeys.find((x) => x.id === +modelKeyId!) + ?.modelProviderId!; + const possibleModels = await getModelProviderModels(modelProviderId); + setModelVersions(possibleModels); + } + }); + return () => subscription?.unsubscribe(); + }, [form.watch]); + return ( @@ -205,14 +235,18 @@ const EditModelModal = (props: IProps) => {
{ return ( - ({ + name: key.name, + value: key.id.toString(), + }))} /> ); }} diff --git a/src/FE/pages/admin/models/index.tsx b/src/FE/pages/admin/models/index.tsx index d358f2c6..40fe5fb6 100644 --- a/src/FE/pages/admin/models/index.tsx +++ b/src/FE/pages/admin/models/index.tsx @@ -4,7 +4,11 @@ import useTranslation from '@/hooks/useTranslation'; import { formatNumberAsMoney } from '@/utils/common'; -import { AdminModelDto, GetModelKeysResult } from '@/types/adminApis'; +import { + AdminModelDto, + GetModelKeysResult, + SimpleModelReferenceDto, +} from '@/types/adminApis'; import { feModelProviders } from '@/types/model'; import { Button } from '@/components/ui/button'; @@ -21,7 +25,11 @@ import { import AddModelModal from '../_components/Models/AddModelModal'; import EditModelModal from '../_components/Models/EditModelModal'; -import { getModelKeys, getModels } from '@/apis/adminApis'; +import { + getModelKeys, + getModelProviderModels, + getModels, +} from '@/apis/adminApis'; export default function Models() { const { t } = useTranslation(); @@ -30,6 +38,9 @@ export default function Models() { const [models, setModels] = useState([]); const [loading, setLoading] = useState(true); const [modelKeys, setModelKeys] = useState([]); + const [modelVersions, setModelVersions] = useState( + [], + ); useEffect(() => { init(); @@ -48,8 +59,11 @@ export default function Models() { }; const showEditDialog = (item: AdminModelDto) => { - setSelectedModel(item); - setIsOpen({ ...isOpen, edit: true }); + getModelProviderModels(item.modelProviderId).then((possibleModels) => { + setModelVersions(possibleModels); + setSelectedModel(item); + setIsOpen({ ...isOpen, edit: true }); + }); }; const showCreateDialog = () => { @@ -123,6 +137,7 @@ export default function Models() { Date: Wed, 5 Feb 2025 14:59:57 +0800 Subject: [PATCH 02/35] modelVersion, modelPrice linkage --- .../admin/_components/Models/EditModelModal.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/FE/pages/admin/_components/Models/EditModelModal.tsx b/src/FE/pages/admin/_components/Models/EditModelModal.tsx index 543811cf..0b9dfb19 100644 --- a/src/FE/pages/admin/_components/Models/EditModelModal.tsx +++ b/src/FE/pages/admin/_components/Models/EditModelModal.tsx @@ -32,6 +32,7 @@ import { import { deleteModels, getModelProviderModels, + getModelReference, putModels, } from '@/apis/adminApis'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -151,6 +152,13 @@ const EditModelModal = (props: IProps) => { } }, [isOpen]); + const onModelReferenceChanged = async (modelReferenceId: number) => { + getModelReference(modelReferenceId).then((data) => { + form.setValue('inputPrice1M', data.promptTokenPrice1M); + form.setValue('outputPrice1M', data.responseTokenPrice1M); + }); + }; + useEffect(() => { const subscription = form.watch(async (value, { name, type }) => { if (name === 'modelKeyId' && type === 'change') { @@ -160,6 +168,11 @@ const EditModelModal = (props: IProps) => { const possibleModels = await getModelProviderModels(modelProviderId); setModelVersions(possibleModels); } + + if (name === 'modelReferenceId' && type === 'change') { + const modelReferenceId = +value.modelReferenceId!; + onModelReferenceChanged(modelReferenceId); + } }); return () => subscription?.unsubscribe(); }, [form.watch]); From b29488ea39f21fba185fbf44ccf2e23b0da0b491 Mon Sep 17 00:00:00 2001 From: gray Date: Wed, 5 Feb 2025 15:00:53 +0800 Subject: [PATCH 03/35] remove useless code --- src/FE/pages/admin/_components/Models/AddModelModal.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FE/pages/admin/_components/Models/AddModelModal.tsx b/src/FE/pages/admin/_components/Models/AddModelModal.tsx index c4d2d36c..bf470e47 100644 --- a/src/FE/pages/admin/_components/Models/AddModelModal.tsx +++ b/src/FE/pages/admin/_components/Models/AddModelModal.tsx @@ -50,7 +50,6 @@ const AddModelModal = (props: IProps) => { const [modelVersions, setModelVersions] = useState( [], ); - const [modelReference, setModelReference] = useState(); const { isOpen, onClose, onSuccessful, modelKeys } = props; const formSchema = z.object({ @@ -108,7 +107,6 @@ const AddModelModal = (props: IProps) => { const onModelReferenceChanged = async (modelReferenceId: number) => { getModelReference(modelReferenceId).then((data) => { - setModelReference(data); form.setValue('inputPrice1M', data.promptTokenPrice1M); form.setValue('outputPrice1M', data.responseTokenPrice1M); }); From 84b71174fec064f9c2eb5cb036f0c0713ce380e1 Mon Sep 17 00:00:00 2001 From: sdcb Date: Thu, 6 Feb 2025 18:50:12 +0800 Subject: [PATCH 04/35] update gemini model --- .../Admin/ModelKeys/ModelKeysController.cs | 2 +- src/BE/DB/Init/BasicData.cs | 13 ++-- .../db-migration/2025/20250206-gemini.sql | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/scripts/db-migration/2025/20250206-gemini.sql diff --git a/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs b/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs index 4e2f7581..0312f0db 100644 --- a/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs +++ b/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs @@ -196,7 +196,7 @@ public async Task> ListModelKeyPossibleModels(s DeploymentName = x.Models.FirstOrDefault(m => m.ModelKeyId == modelKeyId)!.DeploymentName, ReferenceId = x.Id, ReferenceName = x.Name, - IsLegacy = x.PublishDate == null || x.PublishDate < new DateOnly(2024, 7, 1), + IsLegacy = x.PublishDate != null && x.PublishDate < new DateOnly(2024, 7, 1), IsExists = x.Models.Any(m => m.ModelKeyId == modelKeyId), }) .OrderBy(x => (x.IsLegacy ? 1 : 0) + (x.IsExists ? 2 : 0)) diff --git a/src/BE/DB/Init/BasicData.cs b/src/BE/DB/Init/BasicData.cs index 9f2c9ccb..f7fd60c1 100644 --- a/src/BE/DB/Init/BasicData.cs +++ b/src/BE/DB/Init/BasicData.cs @@ -107,7 +107,7 @@ private static void InsertTransactionTypes(ChatsDB db) private static void InsertModelReferences(ChatsDB db) { - // Generated from data, hash: 9b1222897eb278b83b41f6d04be20a1ca5436973e4ed678753a7e4d2af1deb35 + // Generated from data, hash: 660d39eff899b27d8aca5709d71b3d3584d692a99cf36b88fe6f6ed325a614d4 db.ModelReferences.AddRange( [ new(){ Id=0, ProviderId=0, Name="Test", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=2048, MaxResponseTokens=2048, TokenizerId=1, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, @@ -216,8 +216,8 @@ private static void InsertModelReferences(ChatsDB db) new(){ Id=803, ProviderId=8, Name="generalv3.5", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=8192, MaxResponseTokens=8192, TokenizerId=null, InputTokenPrice1M=30.00000M, OutputTokenPrice1M=30.00000M, CurrencyCode="RMB", }, new(){ Id=804, ProviderId=8, Name="max-32k", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=32768, MaxResponseTokens=8192, TokenizerId=null, InputTokenPrice1M=32.00000M, OutputTokenPrice1M=32.00000M, CurrencyCode="RMB", }, new(){ Id=805, ProviderId=8, Name="4.0Ultra", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=8192, MaxResponseTokens=8192, TokenizerId=null, InputTokenPrice1M=70.00000M, OutputTokenPrice1M=70.00000M, CurrencyCode="RMB", }, - new(){ Id=900, ProviderId=9, Name="glm-4-plus", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=50.00000M, OutputTokenPrice1M=50.00000M, CurrencyCode="RMB", }, - new(){ Id=901, ProviderId=9, Name="glm-4-0520", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=100.00000M, OutputTokenPrice1M=100.00000M, CurrencyCode="RMB", }, + new(){ Id=900, ProviderId=9, Name="glm-4-plus", DisplayName=null, PublishDate=new DateOnly(2025, 1, 11), MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=50.00000M, OutputTokenPrice1M=50.00000M, CurrencyCode="RMB", }, + new(){ Id=901, ProviderId=9, Name="glm-4-0520", DisplayName=null, PublishDate=new DateOnly(2024, 5, 20), MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=100.00000M, OutputTokenPrice1M=100.00000M, CurrencyCode="RMB", }, new(){ Id=902, ProviderId=9, Name="glm-4-air", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=1.00000M, OutputTokenPrice1M=1.00000M, CurrencyCode="RMB", }, new(){ Id=903, ProviderId=9, Name="glm-4-airx", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=8192, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=10.00000M, OutputTokenPrice1M=10.00000M, CurrencyCode="RMB", }, new(){ Id=904, ProviderId=9, Name="glm-4-long", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=1.00M, AllowSearch=true, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=1.00000M, OutputTokenPrice1M=1.00000M, CurrencyCode="RMB", }, @@ -254,9 +254,10 @@ private static void InsertModelReferences(ChatsDB db) new(){ Id=1220, ProviderId=12, Name="o1-2024-12-17", DisplayName="o1", PublishDate=new DateOnly(2024, 12, 17), MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=200000, MaxResponseTokens=100000, TokenizerId=2, InputTokenPrice1M=15.00000M, OutputTokenPrice1M=60.00000M, CurrencyCode="USD", }, new(){ Id=1221, ProviderId=12, Name="o3-mini-2025-01-31", DisplayName="o3-mini", PublishDate=new DateOnly(2025, 2, 1), MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=200000, MaxResponseTokens=100000, TokenizerId=2, InputTokenPrice1M=1.10000M, OutputTokenPrice1M=4.40000M, CurrencyCode="USD", }, new(){ Id=1222, ProviderId=12, Name="deepseek-r1", DisplayName="deepseek", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=128000, MaxResponseTokens=4000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, - new(){ Id=1300, ProviderId=13, Name="gemini-2.0-flash-thinking-exp", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=40000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, - new(){ Id=1301, ProviderId=13, Name="gemini-2.0-flash-exp", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, - new(){ Id=1302, ProviderId=13, Name="gemini-exp-1206", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=2097152, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1300, ProviderId=13, Name="gemini-2.0-flash-thinking-exp", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1301, ProviderId=13, Name="gemini-2.0-flash", DisplayName="gemini", PublishDate=new DateOnly(2025, 2, 6), MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=8192, TokenizerId=null, InputTokenPrice1M=0.10000M, OutputTokenPrice1M=0.40000M, CurrencyCode="USD", }, + new(){ Id=1302, ProviderId=13, Name="gemini-2.0-pro-exp", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=2097152, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1303, ProviderId=13, Name="gemini-2.0-flash-lite-preview", DisplayName="gemini", PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, new(){ Id=1400, ProviderId=14, Name="general", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=128000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, new(){ Id=1401, ProviderId=14, Name="general-vision", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=128000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, new(){ Id=1500, ProviderId=15, Name="MiniMax-Text-01", DisplayName=null, PublishDate=null, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1000000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=1.00000M, OutputTokenPrice1M=8.00000M, CurrencyCode="RMB", } diff --git a/src/scripts/db-migration/2025/20250206-gemini.sql b/src/scripts/db-migration/2025/20250206-gemini.sql new file mode 100644 index 00000000..b2ef1170 --- /dev/null +++ b/src/scripts/db-migration/2025/20250206-gemini.sql @@ -0,0 +1,71 @@ +-- Region Parameters +DECLARE @p0 SmallInt = 1301 +DECLARE @p1 NVarChar(1000) = 'gemini-2.0-flash' +DECLARE @p2 Date = '2025-02-06' +DECLARE @p3 Int = 8192 +DECLARE @p4 Decimal(8,5) = 0.1 +DECLARE @p5 Decimal(8,5) = 0.4 +-- EndRegion +UPDATE [ModelReference] +SET [Name] = @p1, [PublishDate] = @p2, [MaxResponseTokens] = @p3, [InputTokenPrice1M] = @p4, [OutputTokenPrice1M] = @p5 +WHERE [Id] = @p0 +GO + +-- Region Parameters +DECLARE @p0 SmallInt = 1302 +DECLARE @p1 NVarChar(1000) = 'gemini-2.0-pro-exp' +-- EndRegion +UPDATE [ModelReference] +SET [Name] = @p1 +WHERE [Id] = @p0 +GO + +-- Region Parameters +DECLARE @p0 SmallInt = 1300 +DECLARE @p1 Int = 1048576 +-- EndRegion +UPDATE [ModelReference] +SET [ContextWindow] = @p1 +WHERE [Id] = @p0 +GO + +-- Region Parameters +DECLARE @p0 SmallInt = 1303 +DECLARE @p1 SmallInt = 13 +DECLARE @p2 NVarChar(1000) = 'gemini-2.0-flash-lite-preview' +DECLARE @p3 NVarChar(1000) = 'gemini' +DECLARE @p4 Date = null +DECLARE @p5 Decimal(3,2) = 0 +DECLARE @p6 Decimal(3,2) = 2 +DECLARE @p7 Bit = 0 +DECLARE @p8 Bit = 1 +DECLARE @p9 Bit = 1 +DECLARE @p10 Bit = 1 +DECLARE @p11 Int = 1048576 +DECLARE @p12 Int = 8000 +DECLARE @p13 SmallInt = null +DECLARE @p14 Decimal(6,5) = 0 +DECLARE @p15 Decimal(6,5) = 0 +DECLARE @p16 Char(3) = 'USD' +-- EndRegion +INSERT INTO [ModelReference]([Id], [ProviderId], [Name], [DisplayName], [PublishDate], [MinTemperature], [MaxTemperature], [AllowSearch], [AllowVision], [AllowSystemPrompt], [AllowStreaming], [ContextWindow], [MaxResponseTokens], [TokenizerId], [InputTokenPrice1M], [OutputTokenPrice1M], [CurrencyCode]) +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16) +GO + +-- Region Parameters +DECLARE @p0 SmallInt = 901 +DECLARE @p1 Date = '2024-05-20' +-- EndRegion +UPDATE [ModelReference] +SET [PublishDate] = @p1 +WHERE [Id] = @p0 +GO + +-- Region Parameters +DECLARE @p0 SmallInt = 900 +DECLARE @p1 Date = '2025-01-11' +-- EndRegion +UPDATE [ModelReference] +SET [PublishDate] = @p1 +WHERE [Id] = @p0 +GO \ No newline at end of file From 1253e601db135c6cb0313c43384c25ce4d498aa1 Mon Sep 17 00:00:00 2001 From: gray Date: Fri, 7 Feb 2025 09:44:23 +0800 Subject: [PATCH 05/35] add siliconflow logo --- src/FE/public/logos/siliconflow.svg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/FE/public/logos/siliconflow.svg diff --git a/src/FE/public/logos/siliconflow.svg b/src/FE/public/logos/siliconflow.svg new file mode 100644 index 00000000..90fe2faf --- /dev/null +++ b/src/FE/public/logos/siliconflow.svg @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From d0db52f59f193a4c62abe724f4405f3ac49fc51b Mon Sep 17 00:00:00 2001 From: sdcb Date: Fri, 7 Feb 2025 19:10:31 +0800 Subject: [PATCH 06/35] initial support for think --- src/BE/DB/Enums/DBMessageContentType.cs | 10 +++++++--- src/scripts/db-migration/2025/20250207-think.sql | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/scripts/db-migration/2025/20250207-think.sql diff --git a/src/BE/DB/Enums/DBMessageContentType.cs b/src/BE/DB/Enums/DBMessageContentType.cs index 6cd3cd78..936a2eaa 100644 --- a/src/BE/DB/Enums/DBMessageContentType.cs +++ b/src/BE/DB/Enums/DBMessageContentType.cs @@ -6,17 +6,21 @@ public enum DBMessageContentType : byte { /// - /// Error content type encoded in UTF-8. + /// Error content type, stored in MessageContentText table /// Error = 0, /// - /// Text content type encoded in Unicode. + /// Text content type, stored in MessageContentText table /// Text = 1, /// - /// 32bit little-endian integer file ID content type. + /// File ID content type, stored in MessageContentFile table /// FileId = 2, + + /// + /// Reasoning content type, stored in MessageContentText table + Think = 3, } diff --git a/src/scripts/db-migration/2025/20250207-think.sql b/src/scripts/db-migration/2025/20250207-think.sql new file mode 100644 index 00000000..4c134fa4 --- /dev/null +++ b/src/scripts/db-migration/2025/20250207-think.sql @@ -0,0 +1 @@ +INSERT INTO [MessageContentType] VALUES(3, 'think'); From c92e9592fe27d544abf49a69776213cf13e16ecb Mon Sep 17 00:00:00 2001 From: sdcb Date: Fri, 7 Feb 2025 19:20:59 +0800 Subject: [PATCH 07/35] add think segment --- src/BE/Controllers/Chats/Chats/Dtos/SseResponseKind.cs | 1 + src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseKind.cs b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseKind.cs index 8731a282..f58999b2 100644 --- a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseKind.cs +++ b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseKind.cs @@ -11,4 +11,5 @@ public enum SseResponseKind TitleSegment = 5, ResponseMessage = 6, ChatLeafMessageId = 7, + ThinkSegment = 8, } \ No newline at end of file diff --git a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs index 6b3edee3..4df2353e 100644 --- a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs +++ b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs @@ -28,6 +28,16 @@ public static SseResponseLine Segment(byte spanId, string segment) }; } + public static SseResponseLine ThinkSegment(byte spanId, string segment) + { + return new SseResponseLine + { + SpanId = spanId, + Result = segment, + Kind = SseResponseKind.ThinkSegment, + }; + } + public static SseResponseLine Error(byte spanId, string error) { return new SseResponseLine From 8f2f33534daa1d1e5d9fc6143b4abe44f0d2e1d2 Mon Sep 17 00:00:00 2001 From: sdcb Date: Fri, 7 Feb 2025 19:26:39 +0800 Subject: [PATCH 08/35] in the road --- .../Controllers/Chats/Chats/ChatController.cs | 6 ++--- .../Dtos/ChatCompletionChunk.cs | 5 ++++- .../Dtos/FullChatCompletion.cs | 5 ++++- .../OpenAICompatibleController.cs | 2 +- src/BE/Services/Models/ChatService.cs | 4 ++-- .../Services/Models/ChatServiceExtensions.cs | 4 ++-- .../DashScope/DashScopeChatService.cs | 4 ++-- .../Hunyuan/HunyuanChatService.cs | 2 +- .../ChatServices/OpenAI/OpenAIChatService.cs | 22 +++++++++++++++++-- .../QianFan/QianFanChatService.cs | 2 +- .../ChatServices/Test/TestChatService.cs | 2 +- src/BE/Services/Models/Dtos/ChatSegment.cs | 10 ++++++--- .../Models/Dtos/InternalChatSegment.cs | 12 ++++++---- src/BE/Services/Models/InChatContext.cs | 4 ++-- 14 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/BE/Controllers/Chats/Chats/ChatController.cs b/src/BE/Controllers/Chats/Chats/ChatController.cs index 279a318d..f092844b 100644 --- a/src/BE/Controllers/Chats/Chats/ChatController.cs +++ b/src/BE/Controllers/Chats/Chats/ChatController.cs @@ -402,8 +402,8 @@ private static async Task ProcessChatSpan( using ChatService s = chatFactory.CreateChatService(userModel.Model); await foreach (InternalChatSegment seg in icc.Run(userBalance.Balance, userModel, s.ChatStreamedFEProcessed(messageToSend, cco, extraDetails, cancellationToken))) { - if (seg.TextSegment == string.Empty) continue; - await writer.WriteAsync(SseResponseLine.Segment(span.Id, seg.TextSegment), cancellationToken); + if (seg.Segment == string.Empty) continue; + await writer.WriteAsync(SseResponseLine.Segment(span.Id, seg.Segment), cancellationToken); if (cancellationToken.IsCancellationRequested) { @@ -448,7 +448,7 @@ private static async Task ProcessChatSpan( ChatRoleId = (byte)DBChatRole.Assistant, MessageContents = [ - MessageContent.FromText(icc.FullResponse.TextSegment), + MessageContent.FromText(icc.FullResponse.Segment), ], SpanId = span.Id, CreatedAt = DateTime.UtcNow, diff --git a/src/BE/Controllers/OpenAICompatible/Dtos/ChatCompletionChunk.cs b/src/BE/Controllers/OpenAICompatible/Dtos/ChatCompletionChunk.cs index 1546b628..b5c07877 100644 --- a/src/BE/Controllers/OpenAICompatible/Dtos/ChatCompletionChunk.cs +++ b/src/BE/Controllers/OpenAICompatible/Dtos/ChatCompletionChunk.cs @@ -5,7 +5,10 @@ namespace Chats.BE.Controllers.OpenAICompatible.Dtos; public record Delta { [JsonPropertyName("content")] - public required string Content { get; init; } + public required string? Content { get; init; } + + [JsonPropertyName("reasoning_content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public required string? ReasoningContent { get; init; } } public record DeltaChoice diff --git a/src/BE/Controllers/OpenAICompatible/Dtos/FullChatCompletion.cs b/src/BE/Controllers/OpenAICompatible/Dtos/FullChatCompletion.cs index a97d3812..37197751 100644 --- a/src/BE/Controllers/OpenAICompatible/Dtos/FullChatCompletion.cs +++ b/src/BE/Controllers/OpenAICompatible/Dtos/FullChatCompletion.cs @@ -47,7 +47,10 @@ public record ResponseMessage public required string Role { get; init; } [JsonPropertyName("content")] - public required string Content { get; init; } + public required string? Content { get; init; } + + [JsonPropertyName("reasoning_content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public required string? ReasoningContent { get; init; } [JsonPropertyName("refusal")] public object? Refusal { get; init; } diff --git a/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs b/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs index 4e46e035..2e9b14a5 100644 --- a/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs +++ b/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs @@ -48,7 +48,7 @@ public async Task ChatCompletion([FromBody] JsonObject json, [From { await foreach (InternalChatSegment seg in icc.Run(userBalance.Balance, userModel, s.ChatStreamedSimulated(cco.Stream, [.. cco.Messages], cco.ToCleanCco(), cancellationToken))) { - if (seg.TextSegment == string.Empty) continue; + if (seg.Segment == string.Empty) continue; if (cco.Stream) { diff --git a/src/BE/Services/Models/ChatService.cs b/src/BE/Services/Models/ChatService.cs index be87d92d..0b147aa4 100644 --- a/src/BE/Services/Models/ChatService.cs +++ b/src/BE/Services/Models/ChatService.cs @@ -38,14 +38,14 @@ public virtual async Task Chat(IReadOnlyList messages, await foreach (ChatSegment seg in ChatStreamed(messages, options, cancellationToken)) { lastSegment = seg; - result.Append(seg.TextSegment); + result.Append(seg.Segment); } return new ChatSegment() { Usage = lastSegment?.Usage, FinishReason = lastSegment?.FinishReason, - TextSegment = result.ToString(), + Segment = result.ToString(), }; } diff --git a/src/BE/Services/Models/ChatServiceExtensions.cs b/src/BE/Services/Models/ChatServiceExtensions.cs index 95228582..4935aa0a 100644 --- a/src/BE/Services/Models/ChatServiceExtensions.cs +++ b/src/BE/Services/Models/ChatServiceExtensions.cs @@ -31,7 +31,7 @@ public async IAsyncEnumerable ChatStreamedSimulated(bool su yield return seg.ToInternal(() => new Dtos.ChatTokenUsage { InputTokens = inputTokens, - OutputTokens = outputTokens += Tokenizer.CountTokens(seg.TextSegment), + OutputTokens = outputTokens += Tokenizer.CountTokens(seg.Segment), ReasoningTokens = 0 }); } @@ -42,7 +42,7 @@ public async IAsyncEnumerable ChatStreamedSimulated(bool su yield return seg.ToInternal(() => new Dtos.ChatTokenUsage() { InputTokens = inputTokens, - OutputTokens = outputTokens += Tokenizer.CountTokens(seg.TextSegment), + OutputTokens = outputTokens += Tokenizer.CountTokens(seg.Segment), ReasoningTokens = 0 }); } diff --git a/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs b/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs index 17c4bc51..7ba88cec 100644 --- a/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs +++ b/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs @@ -46,7 +46,7 @@ public override async IAsyncEnumerable ChatStreamed(IReadOnlyList ChatStreamed(IReadOnlyList ChatStreamed(IReadOnlyList? sad = choices.Cast().FirstOrDefault()?.Uncapsulate().Delta.SerializedAdditionalRawData; + //if (sad != null && sad.Any() && sad.TryGetValue("reasoning_content", out BinaryData? rc)) + //{ + // string? think = rc.ToObjectFromJson(); + // if (think != null) + // { + // Console.Write(Util.WithStyle(rc.ToObjectFromJson(), "color: green")); + // } + //} + } + public override async IAsyncEnumerable ChatStreamed(IReadOnlyList messages, ChatCompletionOptions options, [EnumeratorCancellation] CancellationToken cancellationToken) { await foreach (StreamingChatCompletionUpdate delta in _chatClient.CompleteChatStreamingAsync(messages, options, cancellationToken)) { if (delta.ContentUpdate.Count == 0 && delta.Usage == null) continue; + string? segment = delta.ContentUpdate.FirstOrDefault()?.Text; + string? reasoningSegment = + yield return new ChatSegment { - TextSegment = delta.ContentUpdate.FirstOrDefault()?.Text ?? "", + Segment = segment, FinishReason = delta.FinishReason, Usage = delta.Usage != null ? new Dtos.ChatTokenUsage() { @@ -65,7 +83,7 @@ public override async Task Chat(IReadOnlyList messages ChatCompletion delta = cc.Value; return new ChatSegment { - TextSegment = delta.Content[0].Text, + Segment = delta.Content[0].Text, FinishReason = delta.FinishReason, Usage = delta.Usage != null ? GetUsage(delta.Usage) : null, }; diff --git a/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs b/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs index 378a96c8..63c11987 100644 --- a/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs +++ b/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs @@ -45,7 +45,7 @@ public override async IAsyncEnumerable ChatStreamed(IReadOnlyList ChatStreamed( int outputTokens = Tokenizer.CountTokens(outputed.ToString()); yield return new ChatSegment() { - TextSegment = combined.ToString(), + Segment = combined.ToString(), Usage = new Dtos.ChatTokenUsage() { InputTokens = inputTokens, diff --git a/src/BE/Services/Models/Dtos/ChatSegment.cs b/src/BE/Services/Models/Dtos/ChatSegment.cs index 65607f8f..442c02eb 100644 --- a/src/BE/Services/Models/Dtos/ChatSegment.cs +++ b/src/BE/Services/Models/Dtos/ChatSegment.cs @@ -6,7 +6,9 @@ public record ChatSegment { public required ChatFinishReason? FinishReason { get; init; } - public required string TextSegment { get; init; } + public required string? Segment { get; init; } + + public required string? ReasoningSegment { get; init; } public required ChatTokenUsage? Usage { get; init; } @@ -18,7 +20,8 @@ public InternalChatSegment ToInternal(Func usageCalculator) { Usage = Usage, FinishReason = FinishReason, - TextSegment = TextSegment, + Segment = Segment, + ReasoningSegment = ReasoningSegment, IsUsageReliable = true, IsFromUpstream = true, }; @@ -29,7 +32,8 @@ public InternalChatSegment ToInternal(Func usageCalculator) { Usage = Usage ?? usageCalculator(), FinishReason = FinishReason, - TextSegment = TextSegment, + Segment = Segment, + ReasoningSegment = ReasoningSegment, IsUsageReliable = false, IsFromUpstream = true, }; diff --git a/src/BE/Services/Models/Dtos/InternalChatSegment.cs b/src/BE/Services/Models/Dtos/InternalChatSegment.cs index 9493973f..9d595f29 100644 --- a/src/BE/Services/Models/Dtos/InternalChatSegment.cs +++ b/src/BE/Services/Models/Dtos/InternalChatSegment.cs @@ -7,7 +7,9 @@ public record InternalChatSegment { public required ChatFinishReason? FinishReason { get; init; } - public required string TextSegment { get; init; } + public required string? Segment { get; init; } + + public required string? ReasoningSegment { get; init; } public required ChatTokenUsage Usage { get; init; } @@ -19,7 +21,8 @@ public record InternalChatSegment { Usage = ChatTokenUsage.Zero, FinishReason = null, - TextSegment = string.Empty, + Segment = null, + ReasoningSegment = null, IsUsageReliable = false, IsFromUpstream = false, }; @@ -84,7 +87,7 @@ internal ChatCompletionChunk ToOpenAIChunk(string modelName, string traceId) [ new DeltaChoice { - Delta = new Delta { Content = TextSegment }, + Delta = new Delta { Content = Segment, ReasoningContent = ReasoningSegment }, FinishReason = GetFinishReasonText(), Index = 0, Logprobs = null, @@ -110,7 +113,8 @@ internal FullChatCompletion ToOpenAIFullChat(string modelName, string traceId) Message = new ResponseMessage { Role = "system", - Content = TextSegment, + Content = Segment, + ReasoningContent = ReasoningSegment, Refusal = null, } } diff --git a/src/BE/Services/Models/InChatContext.cs b/src/BE/Services/Models/InChatContext.cs index a56442d9..bf30b9d5 100644 --- a/src/BE/Services/Models/InChatContext.cs +++ b/src/BE/Services/Models/InChatContext.cs @@ -51,7 +51,7 @@ public async IAsyncEnumerable Run(decimal userBalance, User if (_firstResponseTick == 0) _firstResponseTick = Stopwatch.GetTimestamp(); } _lastSegment = seg; - _fullResult.Append(seg.TextSegment); + _fullResult.Append(seg.Segment); UserModelBalanceCost currentCost = calculator.GetNewBalance(seg.Usage.InputTokens, seg.Usage.OutputTokens, priceConfig); if (!currentCost.IsSufficient) @@ -71,7 +71,7 @@ public async IAsyncEnumerable Run(decimal userBalance, User } } - public InternalChatSegment FullResponse => _lastSegment with { TextSegment = _fullResult.ToString() }; + public InternalChatSegment FullResponse => _lastSegment with { Segment = _fullResult.ToString() }; public UserModelUsage ToUserModelUsage(int userId, ClientInfo clientInfo, bool isApi) { From 41347052cf8b5b1b77fcca278642ab93f05f24e7 Mon Sep 17 00:00:00 2001 From: gray Date: Sat, 8 Feb 2025 10:39:28 +0800 Subject: [PATCH 09/35] model/model key add query --- src/FE/components/ui/select.tsx | 58 +++++++--- src/FE/locales/zh-CN.json | 2 + src/FE/pages/admin/model-keys/index.tsx | 101 +++++++++++++++++- src/FE/pages/admin/models/index.tsx | 134 ++++++++++++++++++++++-- src/FE/types/chat.ts | 2 +- 5 files changed, 267 insertions(+), 30 deletions(-) diff --git a/src/FE/components/ui/select.tsx b/src/FE/components/ui/select.tsx index aeb2d577..5fcf8607 100644 --- a/src/FE/components/ui/select.tsx +++ b/src/FE/components/ui/select.tsx @@ -1,5 +1,5 @@ import * as SelectPrimitive from '@radix-ui/react-select'; -import { Check, ChevronDown, ChevronUp } from 'lucide-react'; +import { Check, ChevronDown, ChevronUp, CircleXIcon } from 'lucide-react'; import * as React from 'react'; import { cn } from '@/lib/utils'; @@ -12,22 +12,46 @@ const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - span]:line-clamp-1', - className, - )} - {...props} - > - {children} - - - - -)); + React.ComponentPropsWithoutRef & { + onReset?: () => void; + } +>(({ className, children, onReset, ...props }, ref) => { + return ( +
span]:line-clamp-1 focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0', + className, + )} + > + + + {children} + + + + {props.value ? ( + + ) : ( + + )} + +
+ ); +}); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectScrollUpButton = React.forwardRef< diff --git a/src/FE/locales/zh-CN.json b/src/FE/locales/zh-CN.json index 07af06e1..37543d39 100644 --- a/src/FE/locales/zh-CN.json +++ b/src/FE/locales/zh-CN.json @@ -276,6 +276,8 @@ "File Configs": "文件配置", "Select an Model": "选择一个模型", "Select an Role": "选择一个角色", + "Select an Model Provider": "选择一个模型提供商", + "Select an Model Key": "选择一个模型密钥", "Enabled": "已启用", "Disabled": "已禁用", "Eg: ": "例如: ", diff --git a/src/FE/pages/admin/model-keys/index.tsx b/src/FE/pages/admin/model-keys/index.tsx index 4596373d..552ac9e0 100644 --- a/src/FE/pages/admin/model-keys/index.tsx +++ b/src/FE/pages/admin/model-keys/index.tsx @@ -6,8 +6,16 @@ import useTranslation from '@/hooks/useTranslation'; import { GetModelKeysResult } from '@/types/adminApis'; import { feModelProviders } from '@/types/model'; +import ChatIcon from '@/components/ChatIcon/ChatIcon'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { Table, TableBody, @@ -22,6 +30,11 @@ import ModelKeysModal from '../_components/ModelKeys/ModelKeysModal'; import { getModelKeys } from '@/apis/adminApis'; +interface IQuery { + modelProviderId: string; + modelKeyId: string; +} + export default function ModelKeys() { const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); @@ -29,8 +42,16 @@ export default function ModelKeys() { const [modelKeyId, setModelKeyId] = useState(); const [selected, setSelected] = useState(null); const [services, setServices] = useState([]); + const [filteredServices, setFilteredServices] = useState< + GetModelKeysResult[] + >([]); const [loading, setLoading] = useState(true); + const [query, setQuery] = useState({ + modelProviderId: '', + modelKeyId: '', + }); + useEffect(() => { init(); }, []); @@ -54,9 +75,72 @@ export default function ModelKeys() { setSelected(null); }; + const handleQuery = (params: IQuery) => { + const { modelProviderId, modelKeyId } = params; + let serviceList = [...services]; + if (modelProviderId) { + serviceList = serviceList.filter( + (x) => x.modelProviderId.toString() === modelProviderId, + ); + } + if (modelKeyId) { + serviceList = serviceList.filter((x) => x.id.toString() === modelKeyId); + } + setQuery(params); + setFilteredServices(serviceList); + }; + return ( <> -
+
+
+ + +
+ { + return ; + }} + > Date: Sat, 8 Feb 2025 10:59:03 +0800 Subject: [PATCH 11/35] fix apiKey cannot delete/cancel --- src/FE/pages/home/_components/Popover/DeletePopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FE/pages/home/_components/Popover/DeletePopover.tsx b/src/FE/pages/home/_components/Popover/DeletePopover.tsx index 0ea3cf95..12aefbdf 100644 --- a/src/FE/pages/home/_components/Popover/DeletePopover.tsx +++ b/src/FE/pages/home/_components/Popover/DeletePopover.tsx @@ -40,7 +40,7 @@ export default function DeletePopover(props: Props) { {t('Delete')} - +
{t('Are you sure you want to delete it?')}