This commit is contained in:
liqiang-fit2cloud 2024-12-17 16:37:21 +08:00
commit c6a4a6a8f5
20 changed files with 174 additions and 82 deletions

View File

@ -0,0 +1,64 @@
# coding=utf-8
import base64
import os
from typing import Dict
from langchain_core.messages import HumanMessage
from common import forms
from common.exception.app_exception import AppApiException
from common.forms import BaseForm, TooltipLabel
from setting.models_provider.base_model_provider import BaseModelCredential, ValidCode
class GeminiImageModelParams(BaseForm):
temperature = forms.SliderField(TooltipLabel('温度', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'),
required=True, default_value=0.7,
_min=0.1,
_max=1.0,
_step=0.01,
precision=2)
max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800,
_min=1,
_max=100000,
_step=1,
precision=0)
class GeminiImageModelCredential(BaseForm, BaseModelCredential):
api_key = forms.PasswordInputField('API Key', required=True)
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], provider,
raise_exception=False):
model_type_list = provider.get_model_type_list()
if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):
raise AppApiException(ValidCode.valid_error.value, f'{model_type} 模型类型不支持')
for key in ['api_key']:
if key not in model_credential:
if raise_exception:
raise AppApiException(ValidCode.valid_error.value, f'{key} 字段为必填字段')
else:
return False
try:
model = provider.get_model(model_type, model_name, model_credential)
res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
for chunk in res:
print(chunk)
except Exception as e:
if isinstance(e, AppApiException):
raise e
if raise_exception:
raise AppApiException(ValidCode.valid_error.value, f'校验失败,请检查参数是否正确: {str(e)}')
else:
return False
return True
def encryption_dict(self, model: Dict[str, object]):
return {**model, 'api_key': super().encryption(model.get('api_key', ''))}
def get_model_params_setting_form(self, model_name):
return GeminiImageModelParams()

View File

@ -11,24 +11,47 @@ import os
from common.util.file_util import get_file_content
from setting.models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \
ModelInfoManage
from setting.models_provider.impl.gemini_model_provider.credential.image import GeminiImageModelCredential
from setting.models_provider.impl.gemini_model_provider.credential.llm import GeminiLLMModelCredential
from setting.models_provider.impl.gemini_model_provider.model.image import GeminiImage
from setting.models_provider.impl.gemini_model_provider.model.llm import GeminiChatModel
from smartdoc.conf import PROJECT_DIR
gemini_llm_model_credential = GeminiLLMModelCredential()
gemini_image_model_credential = GeminiImageModelCredential()
gemini_1_pro = ModelInfo('gemini-1.0-pro', '最新的Gemini 1.0 Pro模型随Google更新而更新',
ModelTypeConst.LLM,
gemini_llm_model_credential,
GeminiChatModel)
model_info_list = [
ModelInfo('gemini-1.0-pro', '最新的Gemini 1.0 Pro模型随Google更新而更新',
ModelTypeConst.LLM,
gemini_llm_model_credential,
GeminiChatModel),
ModelInfo('gemini-1.0-pro-vision', '最新的Gemini 1.0 Pro Vision模型随Google更新而更新',
ModelTypeConst.LLM,
gemini_llm_model_credential,
GeminiChatModel),
]
gemini_1_pro_vision = ModelInfo('gemini-1.0-pro-vision', '最新的Gemini 1.0 Pro Vision模型随Google更新而更新',
ModelTypeConst.LLM,
gemini_llm_model_credential,
GeminiChatModel)
model_image_info_list = [
ModelInfo('gemini-1.5-flash', '最新的Gemini 1.5 Flash模型随Google更新而更新',
ModelTypeConst.IMAGE,
gemini_image_model_credential,
GeminiImage),
ModelInfo('gemini-1.5-pro', '最新的Gemini 1.5 Flash模型随Google更新而更新',
ModelTypeConst.IMAGE,
gemini_image_model_credential,
GeminiImage),
]
model_info_manage = ModelInfoManage.builder().append_model_info(gemini_1_pro).append_model_info(
gemini_1_pro_vision).append_default_model_info(gemini_1_pro).build()
model_info_manage = (
ModelInfoManage.builder()
.append_model_info_list(model_info_list)
.append_model_info_list(model_image_info_list)
.append_default_model_info(model_info_list[0])
.append_default_model_info(model_image_info_list[0])
.build()
)
class GeminiModelProvider(IModelProvider):

View File

@ -0,0 +1,24 @@
from typing import Dict
from langchain_google_genai import ChatGoogleGenerativeAI
from common.config.tokenizer_manage_config import TokenizerManage
from setting.models_provider.base_model_provider import MaxKBBaseModel
def custom_get_token_ids(text: str):
tokenizer = TokenizerManage.get_tokenizer()
return tokenizer.encode(text)
class GeminiImage(MaxKBBaseModel, ChatGoogleGenerativeAI):
@staticmethod
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)
return GeminiImage(
model=model_name,
google_api_key=model_credential.get('api_key'),
streaming=True,
**optional_params,
)

View File

@ -28,8 +28,8 @@ class VolcanicEngineTTIModelGeneralParams(BaseForm):
class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
access_key = forms.PasswordInputField('Access Key', required=True)
secret_key = forms.PasswordInputField('Secret Key', required=True)
access_key = forms.PasswordInputField('Access Key ID', required=True)
secret_key = forms.PasswordInputField('Secret Access Key', required=True)
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], provider,
raise_exception=False):

View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.3335 3.33333C5.3335 2.59695 5.93045 2 6.66683 2H19.724C19.9008 2 20.0704 2.07024 20.1954 2.19526L26.4716 8.47141C26.5966 8.59643 26.6668 8.766 26.6668 8.94281V28.6667C26.6668 29.403 26.0699 30 25.3335 30H6.66683C5.93045 30 5.3335 29.403 5.3335 28.6667V3.33333Z" fill="#3370FF"/>
<path d="M20 2.05994C20.072 2.0927 20.1383 2.13831 20.1953 2.19532L26.4714 8.47146C26.5284 8.52847 26.574 8.59473 26.6068 8.66672H21.3333C20.597 8.66672 20 8.06977 20 7.33339V2.05994Z" fill="#2B5FD9"/>
<path d="M19.976 12.8794C20.254 12.9447 20.4595 13.1796 20.4872 13.4638L20.6106 14.7297C20.6546 15.1805 20.2459 15.5429 19.8037 15.4455L16.7344 14.7691C16.5577 14.7302 16.3944 14.8747 16.4116 15.0548L17.1297 22.5884C17.1297 22.7046 17.1151 22.7812 17.0891 22.8249C17.0254 24.3752 15.7484 25.6124 14.1825 25.6124C12.5759 25.6124 11.2734 24.31 11.2734 22.7033C11.2734 21.0967 12.5759 19.7942 14.1825 19.7942C14.8467 19.7942 15.4589 20.0168 15.9485 20.3914L15.274 12.6749C15.2345 12.2228 15.6485 11.8642 16.0904 11.9678L19.976 12.8794Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,6 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 19.8889H13.2222C12.9768 19.8889 12.7778 19.6899 12.7778 19.4445V18.5556C12.7778 18.3101 12.9768 18.1111 13.2222 18.1111H18.1111V13.2222C18.1111 12.9768 18.3101 12.7778 18.5556 12.7778H19.4444C19.6899 12.7778 19.8889 12.9768 19.8889 13.2222V19C19.8889 19.4909 19.4909 19.8889 19 19.8889Z" fill="white"/>
<path d="M9.66672 2.11101H3.00006C2.50914 2.11101 2.11117 2.50898 2.11117 2.9999V8.77767C2.11117 9.02313 2.31015 9.22212 2.55561 9.22212H3.4445C3.68996 9.22212 3.88895 9.02313 3.88895 8.77767V3.88879H9.66672C9.91218 3.88879 10.1112 3.6898 10.1112 3.44434V2.55545C10.1112 2.30999 9.91218 2.11101 9.66672 2.11101Z" fill="white"/>
<path d="M16.6889 2.11111V3.17778H19.8889V4.77778L18.9217 4.77829C18.447 5.83102 17.8102 6.79899 17.0398 7.6598C17.8689 8.35035 18.8195 8.92658 19.863 9.36344L19.1304 10.7872C17.9346 10.2731 16.8423 9.59559 15.8889 8.78252C14.9359 9.59534 13.8434 10.273 12.6474 10.7872L11.9148 9.36344C12.9583 8.92658 13.9089 8.35035 14.7391 7.65984C13.9676 6.79899 13.3308 5.83102 12.8561 4.77829L11.8889 4.77778V3.17778H15.0889V2.11111H16.6889ZM17.1301 4.77841H14.6477C14.9932 5.40835 15.4096 5.99905 15.8881 6.5426C16.3682 5.99905 16.7846 5.40835 17.1301 4.77841Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.11111 19.4445C2.11111 19.6899 2.3101 19.8889 2.55556 19.8889H10.5556C10.801 19.8889 11 19.6899 11 19.4445V11.4445C11 11.199 10.801 11 10.5556 11H2.55556C2.3101 11 2.11111 11.199 2.11111 11.4445V19.4445ZM4.1389 12.7778H4.97223C5.1103 12.7778 5.22223 12.8897 5.22223 13.0278V13.8611C5.22223 13.9992 5.1103 14.1111 4.97223 14.1111H4.1389C4.00083 14.1111 3.8889 13.9992 3.8889 13.8611V13.0278C3.8889 12.8897 4.00083 12.7778 4.1389 12.7778ZM5.72934 18.1059L3.8292 18.1014C3.75556 18.1012 3.69601 18.0414 3.69618 17.9677C3.69627 17.9325 3.71031 17.8987 3.73524 17.8738L5.57204 16.037C5.64146 15.9675 5.75403 15.9675 5.82345 16.037L6.67104 16.8845L8.91875 14.6368C8.98817 14.5674 9.10074 14.5674 9.17016 14.6368C9.2035 14.6702 9.22223 14.7154 9.22223 14.7625V17.9778C9.22223 18.0514 9.16254 18.1111 9.0889 18.1111H5.76635C5.75351 18.1111 5.74109 18.1093 5.72934 18.1059Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -526,41 +526,6 @@
</template>
<!-- 图片生成 -->
<template v-if="item.type == WorkflowType.ImageGenerateNode">
<div
class="card-never border-r-4 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">历史记录</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.history_message?.length > 0">
<p
class="mt-4 mb-4"
v-for="(history, historyIndex) in item.history_message"
:key="historyIndex"
>
<span class="color-secondary mr-4">{{ history.role }}:</span>
<span v-if="Array.isArray(history.content)">
<template v-for="(h, i) in history.content" :key="i">
<el-image
v-if="h.type === 'image_url'"
:src="h.image_url.url"
alt=""
fit="cover"
style="width: 40px; height: 40px; display: inline-block"
class="border-r-4 mr-8"
/>
<span v-else>{{ h.text }}<br /></span>
</template>
</span>
<span v-else>{{ history.content }}</span>
</p>
</template>
<template v-else> - </template>
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">本次对话</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">

View File

@ -20,7 +20,7 @@
<el-input v-model="detail.padding_problem_text" disabled />
</el-form-item>
<el-form-item label="引用分段">
<div v-if="detail.paragraph_list.length > 0">
<div v-if="detail.paragraph_list.length > 0" class="w-full">
<template v-for="(item, index) in detail.paragraph_list" :key="index">
<ParagraphCard :data="item" :index="index" />
</template>

View File

@ -25,7 +25,7 @@ defineProps({
cursor: pointer;
min-height: var(--card-min-height);
border: 1px dashed var(--el-border-color);
background: #eff0f1;
background: var(--el-disabled-bg-color);
border-radius: 8px;
box-sizing: border-box;

View File

@ -129,7 +129,7 @@ defineExpose({
cursor: pointer;
min-height: var(--card-min-height);
border: 1px dashed var(--el-color-primary);
background: #eff0f1;
background: var(--el-disabled-bg-color);;
padding-bottom: 20px;
.add-icon {

View File

@ -5,6 +5,9 @@
--el-border-color: #dee0e3;
--el-text-color-regular: #1f2329;
--el-color-info: #8f959e !important;
--el-disabled-bg-color: #eff0f1;
--el-disabled-border-color: #bbbfc4;
--el-text-color-primary: #1f2329;
}
.el-button {
@ -251,6 +254,9 @@
padding: 1px 12px !important;
}
.el-input.is-disabled .el-input__wrapper {
}
.el-input--large {
.el-input__inner {
font-size: 16px;

View File

@ -477,12 +477,7 @@ defineExpose({ open })
top: 25px;
border-radius: 8px;
border: 1px solid #ffffff;
background: linear-gradient(
188deg,
rgba(235, 241, 255, 0.2) 39.6%,
rgba(231, 249, 255, 0.2) 94.3%
),
#eff0f1;
background: var(--dialog-bg-gradient-color);
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
overflow: hidden;
width: 330px;

View File

@ -405,12 +405,7 @@ onBeforeUnmount(() => {
position: relative;
border-radius: 8px;
border: 1px solid #ffffff;
background: linear-gradient(
188deg,
rgba(235, 241, 255, 0.2) 39.6%,
rgba(231, 249, 255, 0.2) 94.3%
),
#eff0f1;
background: var(--dialog-bg-gradient-color);
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
position: fixed;
bottom: 16px;

View File

@ -162,8 +162,8 @@
<el-input-number
v-model="days"
controls-position="right"
min="1"
max="100000"
:min="1"
:max="100000"
:value-on-clear="0"
step-strictly
style="width: 110px; margin-left: 8px; margin-right: 8px"

View File

@ -324,8 +324,10 @@ onMounted(() => {
border-bottom: none !important;
padding-left: 16px;
font-size: 14px;
height: 40px;
&:hover {
background: var(--app-text-color-light-1);
border-radius: 4px;
}
}
:deep(.el-collapse-item) {
@ -340,7 +342,7 @@ onMounted(() => {
border-bottom: none !important;
}
:deep(.el-collapse-item__content) {
padding-bottom: 0 !important;;
padding-bottom: 0 !important;
}
}
}

View File

@ -84,7 +84,7 @@ const fileURL = computed(() => {
}
.header {
background: #eff0f1;
background: var(--el-disabled-bg-color);
height: 38px;
border-radius: 4px 4px 0 0;
position: relative;

View File

@ -270,7 +270,7 @@ export const speechToTextNode = {
}
export const textToSpeechNode = {
type: WorkflowType.TextToSpeechNode,
text: '将文本通过语音合成模型转换为音频文件',
text: '将文本通过语音合成模型转换为音频',
label: '文本转语音',
height: 252,
properties: {
@ -287,17 +287,17 @@ export const textToSpeechNode = {
}
export const menuNodes = [
aiChatNode,
imageUnderstandNode,
imageGenerateNode,
searchDatasetNode,
questionNode,
rerankerNode,
conditionNode,
replyNode,
rerankerNode,
documentExtractNode,
imageUnderstandNode,
formNode,
questionNode,
documentExtractNode,
speechToTextNode,
textToSpeechNode,
imageGenerateNode
textToSpeechNode
]
/**
@ -390,7 +390,7 @@ export const nodeDict: any = {
[WorkflowType.ImageUnderstandNode]: imageUnderstandNode,
[WorkflowType.TextToSpeechNode]: textToSpeechNode,
[WorkflowType.SpeechToTextNode]: speechToTextNode,
[WorkflowType.ImageGenerateNode]: imageGenerateNode
[WorkflowType.ImageGenerateNode]: imageGenerateNode
}
export function isWorkFlow(type: string | undefined) {
return type === 'WORK_FLOW'

View File

@ -1,6 +1,6 @@
<template>
<AppAvatar shape="square" style="background: #14C0FF;">
<img src="@/assets/icon_image.svg" style="width: 65%" alt="" />
<AppAvatar shape="square" style="background: #FF8800;">
<img src="@/assets/icon_text-image.svg" style="width: 65%" alt="" />
</AppAvatar>
</template>
<script setup lang="ts"></script>

View File

@ -39,6 +39,7 @@
class="card-checkbox cursor w-full mb-8"
:class="form_data.document ? 'active' : ''"
style="--el-card-padding: 8px 16px"
@click.stop="form_data.document = !form_data.document"
>
<div class="flex-between">
<div class="flex align-center">
@ -48,7 +49,10 @@
<el-text class="color-secondary">需要使用文档内容提取节点解析文档内容</el-text>
</div>
</div>
<el-checkbox v-model="form_data.document" />
<el-checkbox
v-model="form_data.document"
@change="form_data.document = !form_data.document"
/>
</div>
</el-card>
<el-card
@ -56,6 +60,7 @@
class="card-checkbox cursor w-full mb-8"
:class="form_data.image ? 'active' : ''"
style="--el-card-padding: 8px 16px"
@click.stop="form_data.image = !form_data.image"
>
<div class="flex-between">
<div class="flex align-center">
@ -65,24 +70,26 @@
<el-text class="color-secondary">需要使用图片理解节点解析图片内容</el-text>
</div>
</div>
<el-checkbox v-model="form_data.image" />
<el-checkbox v-model="form_data.image" @change="form_data.image = !form_data.image" />
</div>
</el-card>
<el-card
shadow="hover"
class="card-checkbox cursor w-full mb-8"
:class="form_data.audio ? 'active' : ''"
style="--el-card-padding: 8px 16px"
@click.stop="form_data.audio = !form_data.audio"
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" src="@/assets/icon_file-image.svg" alt="" />
<img class="mr-12" src="@/assets/icon_file-audio.svg" alt="" />
<div>
<p class="line-height-22 mt-4">音频MP3</p>
<el-text class="color-secondary">所选模型支持接收音频或与语音转文本节点配合使用</el-text>
<el-text class="color-secondary">需要使用语音转文本节点解析音频内容</el-text>
</div>
</div>
<el-checkbox v-model="form_data.audio" />
<el-checkbox v-model="form_data.audio" @change="form_data.audio = !form_data.audio" />
</div>
</el-card>
</el-form-item>

View File

@ -69,7 +69,7 @@ const refreshFileUploadConfig = () => {
.map((v: any) => cloneDeep(v.properties.node_data.file_upload_setting))
.filter((v: any) => v)
fields = fields.filter((item: any) => item.value !== 'image' && item.value !== 'document')
fields = fields.filter((item: any) => item.value !== 'image' && item.value !== 'document' && item.value !== 'audio' && item.value !== 'video')
if (form_data.length === 0) {
set(props.nodeModel.properties.config, 'fields', fields)