Commit 22d39ec7 authored by crazywoola's avatar crazywoola

Merge branch 'fix/2559-upload-powered-by-brand-image-not-showing-up' into deploy/dev

parents c4352960 0f53fc86
......@@ -124,19 +124,13 @@ class AppListApi(Resource):
available_models_names = [f'{model.provider.provider}.{model.model}' for model in available_models]
provider_model = f"{model_config_dict['model']['provider']}.{model_config_dict['model']['name']}"
if provider_model not in available_models_names:
model_manager = ModelManager()
model_instance = model_manager.get_default_model_instance(
tenant_id=current_user.current_tenant_id,
model_type=ModelType.LLM
)
if not model_instance:
if not default_model_entity:
raise ProviderNotInitializeError(
"No Default System Reasoning Model available. Please configure "
"in the Settings -> Model Provider.")
else:
model_config_dict["model"]["provider"] = model_instance.provider
model_config_dict["model"]["name"] = model_instance.model
model_config_dict["model"]["provider"] = default_model_entity.provider
model_config_dict["model"]["name"] = default_model_entity.model
model_configuration = AppModelConfigService.validate_configuration(
tenant_id=current_user.current_tenant_id,
......
......@@ -178,7 +178,8 @@ class DataSourceNotionApi(Resource):
notion_workspace_id=workspace_id,
notion_obj_id=page_id,
notion_page_type=page_type,
notion_access_token=data_source_binding.access_token
notion_access_token=data_source_binding.access_token,
tenant_id=current_user.current_tenant_id
)
text_docs = extractor.extract()
......@@ -208,7 +209,8 @@ class DataSourceNotionApi(Resource):
notion_info={
"notion_workspace_id": workspace_id,
"notion_obj_id": page['page_id'],
"notion_page_type": page['type']
"notion_page_type": page['type'],
"tenant_id": current_user.current_tenant_id
},
document_model=args['doc_form']
)
......
......@@ -298,7 +298,8 @@ class DatasetIndexingEstimateApi(Resource):
notion_info={
"notion_workspace_id": workspace_id,
"notion_obj_id": page['page_id'],
"notion_page_type": page['type']
"notion_page_type": page['type'],
"tenant_id": current_user.current_tenant_id
},
document_model=args['doc_form']
)
......
......@@ -455,7 +455,8 @@ class DocumentBatchIndexingEstimateApi(DocumentResource):
notion_info={
"notion_workspace_id": data_source_info['notion_workspace_id'],
"notion_obj_id": data_source_info['notion_page_id'],
"notion_page_type": data_source_info['type']
"notion_page_type": data_source_info['type'],
"tenant_id": current_user.current_tenant_id
},
document_model=document.doc_form
)
......
......@@ -3,12 +3,12 @@ import logging
from typing import Optional, cast
import numpy as np
from langchain.embeddings.base import Embeddings
from sqlalchemy.exc import IntegrityError
from core.model_manager import ModelInstance
from core.model_runtime.entities.model_entities import ModelPropertyKey
from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel
from core.rag.datasource.entity.embedding import Embeddings
from extensions.ext_database import db
from extensions.ext_redis import redis_client
from libs import helper
......
......@@ -59,7 +59,7 @@ class AnnotationReplyFeature:
documents = vector.search_by_vector(
query=query,
k=1,
top_k=1,
score_threshold=score_threshold,
filter={
'group_id': [dataset.id]
......
......@@ -606,36 +606,42 @@ class BaseAssistantApplicationRunner(AppRunner):
for message in messages:
result.append(UserPromptMessage(content=message.query))
agent_thoughts: list[MessageAgentThought] = message.agent_thoughts
for agent_thought in agent_thoughts:
tools = agent_thought.tool
if tools:
tools = tools.split(';')
tool_calls: list[AssistantPromptMessage.ToolCall] = []
tool_call_response: list[ToolPromptMessage] = []
tool_inputs = json.loads(agent_thought.tool_input)
for tool in tools:
# generate a uuid for tool call
tool_call_id = str(uuid.uuid4())
tool_calls.append(AssistantPromptMessage.ToolCall(
id=tool_call_id,
type='function',
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
if agent_thoughts:
for agent_thought in agent_thoughts:
tools = agent_thought.tool
if tools:
tools = tools.split(';')
tool_calls: list[AssistantPromptMessage.ToolCall] = []
tool_call_response: list[ToolPromptMessage] = []
tool_inputs = json.loads(agent_thought.tool_input)
for tool in tools:
# generate a uuid for tool call
tool_call_id = str(uuid.uuid4())
tool_calls.append(AssistantPromptMessage.ToolCall(
id=tool_call_id,
type='function',
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
name=tool,
arguments=json.dumps(tool_inputs.get(tool, {})),
)
))
tool_call_response.append(ToolPromptMessage(
content=agent_thought.observation,
name=tool,
arguments=json.dumps(tool_inputs.get(tool, {})),
)
))
tool_call_response.append(ToolPromptMessage(
content=agent_thought.observation,
name=tool,
tool_call_id=tool_call_id,
))
result.extend([
AssistantPromptMessage(
content=agent_thought.thought,
tool_calls=tool_calls,
),
*tool_call_response
])
tool_call_id=tool_call_id,
))
result.extend([
AssistantPromptMessage(
content=agent_thought.thought,
tool_calls=tool_calls,
),
*tool_call_response
])
if not tools:
result.append(AssistantPromptMessage(content=agent_thought.thought))
else:
if message.answer:
result.append(AssistantPromptMessage(content=message.answer))
return result
\ No newline at end of file
......@@ -154,7 +154,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
thought='',
action_str='',
observation='',
action=None
action=None,
)
# publish agent thought if it's first iteration
......@@ -469,7 +469,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
thought=message.content,
action_str='',
action=None,
observation=None
observation=None,
)
if message.tool_calls:
try:
......@@ -484,7 +484,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
elif isinstance(message, ToolPromptMessage):
if current_scratchpad:
current_scratchpad.observation = message.content
return agent_scratchpad
def _check_cot_prompt_messages(self, mode: Literal["completion", "chat"],
......@@ -607,6 +607,13 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
prompt_message.content = system_message
overridden = True
break
# convert tool prompt messages to user prompt messages
for idx, prompt_message in enumerate(prompt_messages):
if isinstance(prompt_message, ToolPromptMessage):
prompt_messages[idx] = UserPromptMessage(
content=prompt_message.content
)
if not overridden:
prompt_messages.insert(0, SystemPromptMessage(
......
......@@ -366,7 +366,8 @@ class IndexingRunner:
"notion_workspace_id": data_source_info['notion_workspace_id'],
"notion_obj_id": data_source_info['notion_page_id'],
"notion_page_type": data_source_info['type'],
"document": dataset_document
"document": dataset_document,
"tenant_id": dataset_document.tenant_id
},
document_model=dataset_document.doc_form
)
......
......@@ -6,6 +6,7 @@
- bedrock
- togetherai
- ollama
- mistralai
- replicate
- huggingface_hub
- zhipuai
......
- open-mistral-7b
- open-mixtral-8x7b
- mistral-small-latest
- mistral-medium-latest
- mistral-large-latest
from collections.abc import Generator
from typing import Optional, Union
from core.model_runtime.entities.llm_entities import LLMResult
from core.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool
from core.model_runtime.model_providers.openai_api_compatible.llm.llm import OAIAPICompatLargeLanguageModel
class MistralAILargeLanguageModel(OAIAPICompatLargeLanguageModel):
def _invoke(self, model: str, credentials: dict,
prompt_messages: list[PromptMessage], model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
stream: bool = True, user: Optional[str] = None) \
-> Union[LLMResult, Generator]:
self._add_custom_parameters(credentials)
# mistral dose not support user/stop arguments
stop = []
user = None
return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
def validate_credentials(self, model: str, credentials: dict) -> None:
self._add_custom_parameters(credentials)
super().validate_credentials(model, credentials)
@staticmethod
def _add_custom_parameters(credentials: dict) -> None:
credentials['mode'] = 'chat'
credentials['endpoint_url'] = 'https://api.mistral.ai/v1'
model: mistral-large-latest
label:
zh_Hans: mistral-large-latest
en_US: mistral-large-latest
model_type: llm
features:
- agent-thought
model_properties:
context_size: 32000
parameter_rules:
- name: temperature
use_template: temperature
default: 0.7
min: 0
max: 1
- name: top_p
use_template: top_p
default: 1
min: 0
max: 1
- name: max_tokens
use_template: max_tokens
default: 1024
min: 1
max: 8000
- name: safe_prompt
defulat: false
type: boolean
help:
en_US: Whether to inject a safety prompt before all conversations.
zh_Hans: 是否开启提示词审查
label:
en_US: SafePrompt
zh_Hans: 提示词审查
- name: random_seed
type: int
help:
en_US: The seed to use for random sampling. If set, different calls will generate deterministic results.
zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定
label:
en_US: RandomSeed
zh_Hans: 随机数种子
default: 0
min: 0
max: 2147483647
pricing:
input: '0.008'
output: '0.024'
unit: '0.001'
currency: USD
model: mistral-medium-latest
label:
zh_Hans: mistral-medium-latest
en_US: mistral-medium-latest
model_type: llm
features:
- agent-thought
model_properties:
context_size: 32000
parameter_rules:
- name: temperature
use_template: temperature
default: 0.7
min: 0
max: 1
- name: top_p
use_template: top_p
default: 1
min: 0
max: 1
- name: max_tokens
use_template: max_tokens
default: 1024
min: 1
max: 8000
- name: safe_prompt
defulat: false
type: boolean
help:
en_US: Whether to inject a safety prompt before all conversations.
zh_Hans: 是否开启提示词审查
label:
en_US: SafePrompt
zh_Hans: 提示词审查
- name: random_seed
type: int
help:
en_US: The seed to use for random sampling. If set, different calls will generate deterministic results.
zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定
label:
en_US: RandomSeed
zh_Hans: 随机数种子
default: 0
min: 0
max: 2147483647
pricing:
input: '0.0027'
output: '0.0081'
unit: '0.001'
currency: USD
model: mistral-small-latest
label:
zh_Hans: mistral-small-latest
en_US: mistral-small-latest
model_type: llm
features:
- agent-thought
model_properties:
context_size: 32000
parameter_rules:
- name: temperature
use_template: temperature
default: 0.7
min: 0
max: 1
- name: top_p
use_template: top_p
default: 1
min: 0
max: 1
- name: max_tokens
use_template: max_tokens
default: 1024
min: 1
max: 8000
- name: safe_prompt
defulat: false
type: boolean
help:
en_US: Whether to inject a safety prompt before all conversations.
zh_Hans: 是否开启提示词审查
label:
en_US: SafePrompt
zh_Hans: 提示词审查
- name: random_seed
type: int
help:
en_US: The seed to use for random sampling. If set, different calls will generate deterministic results.
zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定
label:
en_US: RandomSeed
zh_Hans: 随机数种子
default: 0
min: 0
max: 2147483647
pricing:
input: '0.002'
output: '0.006'
unit: '0.001'
currency: USD
model: open-mistral-7b
label:
zh_Hans: open-mistral-7b
en_US: open-mistral-7b
model_type: llm
features:
- agent-thought
model_properties:
context_size: 8000
parameter_rules:
- name: temperature
use_template: temperature
default: 0.7
min: 0
max: 1
- name: top_p
use_template: top_p
default: 1
min: 0
max: 1
- name: max_tokens
use_template: max_tokens
default: 1024
min: 1
max: 2048
- name: safe_prompt
defulat: false
type: boolean
help:
en_US: Whether to inject a safety prompt before all conversations.
zh_Hans: 是否开启提示词审查
label:
en_US: SafePrompt
zh_Hans: 提示词审查
- name: random_seed
type: int
help:
en_US: The seed to use for random sampling. If set, different calls will generate deterministic results.
zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定
label:
en_US: RandomSeed
zh_Hans: 随机数种子
default: 0
min: 0
max: 2147483647
pricing:
input: '0.00025'
output: '0.00025'
unit: '0.001'
currency: USD
model: open-mixtral-8x7b
label:
zh_Hans: open-mixtral-8x7b
en_US: open-mixtral-8x7b
model_type: llm
features:
- agent-thought
model_properties:
context_size: 32000
parameter_rules:
- name: temperature
use_template: temperature
default: 0.7
min: 0
max: 1
- name: top_p
use_template: top_p
default: 1
min: 0
max: 1
- name: max_tokens
use_template: max_tokens
default: 1024
min: 1
max: 8000
- name: safe_prompt
defulat: false
type: boolean
help:
en_US: Whether to inject a safety prompt before all conversations.
zh_Hans: 是否开启提示词审查
label:
en_US: SafePrompt
zh_Hans: 提示词审查
- name: random_seed
type: int
help:
en_US: The seed to use for random sampling. If set, different calls will generate deterministic results.
zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定
label:
en_US: RandomSeed
zh_Hans: 随机数种子
default: 0
min: 0
max: 2147483647
pricing:
input: '0.0007'
output: '0.0007'
unit: '0.001'
currency: USD
import logging
from core.model_runtime.entities.model_entities import ModelType
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.__base.model_provider import ModelProvider
logger = logging.getLogger(__name__)
class MistralAIProvider(ModelProvider):
def validate_provider_credentials(self, credentials: dict) -> None:
"""
Validate provider credentials
if validate failed, raise exception
:param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
"""
try:
model_instance = self.get_model_instance(ModelType.LLM)
model_instance.validate_credentials(
model='open-mistral-7b',
credentials=credentials
)
except CredentialsValidateFailedError as ex:
raise ex
except Exception as ex:
logger.exception(f'{self.get_provider_schema().provider} credentials validate failed')
raise ex
provider: mistralai
label:
en_US: MistralAI
description:
en_US: Models provided by MistralAI, such as open-mistral-7b and mistral-large-latest.
zh_Hans: MistralAI 提供的模型,例如 open-mistral-7b 和 mistral-large-latest。
icon_small:
en_US: icon_s_en.png
icon_large:
en_US: icon_l_en.png
background: "#FFFFFF"
help:
title:
en_US: Get your API Key from MistralAI
zh_Hans: 从 MistralAI 获取 API Key
url:
en_US: https://console.mistral.ai/api-keys/
supported_model_types:
- llm
configurate_methods:
- predefined-model
provider_credential_schema:
credential_form_schemas:
- variable: api_key
label:
en_US: API Key
type: secret-input
required: true
placeholder:
zh_Hans: 在此输入您的 API Key
en_US: Enter your API Key
......@@ -13,6 +13,7 @@ class MoonshotLargeLanguageModel(OAIAPICompatLargeLanguageModel):
stream: bool = True, user: Optional[str] = None) \
-> Union[LLMResult, Generator]:
self._add_custom_parameters(credentials)
user = user[:32] if user else None
return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
def validate_credentials(self, model: str, credentials: dict) -> None:
......
......@@ -39,7 +39,8 @@ class RetrievalService:
'flask_app': current_app._get_current_object(),
'dataset_id': dataset_id,
'query': query,
'top_k': top_k
'top_k': top_k,
'all_documents': all_documents
})
threads.append(keyword_thread)
keyword_thread.start()
......@@ -100,7 +101,7 @@ class RetrievalService:
documents = keyword.search(
query,
k=top_k
top_k=top_k
)
all_documents.extend(documents)
......@@ -120,7 +121,7 @@ class RetrievalService:
documents = vector.search_by_vector(
query,
search_type='similarity_score_threshold',
k=top_k,
top_k=top_k,
score_threshold=score_threshold,
filter={
'group_id': [dataset.id]
......
......@@ -7,4 +7,4 @@ class Field(Enum):
GROUP_KEY = "group_id"
VECTOR = "vector"
TEXT_KEY = "text"
PRIMARY_KEY = " id"
PRIMARY_KEY = "id"
......@@ -124,7 +124,12 @@ class MilvusVector(BaseVector):
def delete_by_ids(self, doc_ids: list[str]) -> None:
self._client.delete(collection_name=self._collection_name, pks=doc_ids)
result = self._client.query(collection_name=self._collection_name,
filter=f'metadata["doc_id"] in {doc_ids}',
output_fields=["id"])
if result:
ids = [item["id"] for item in result]
self._client.delete(collection_name=self._collection_name, pks=ids)
def delete(self) -> None:
alias = uuid4().hex
......
......@@ -12,6 +12,7 @@ class NotionInfo(BaseModel):
notion_obj_id: str
notion_page_type: str
document: Document = None
tenant_id: str
class Config:
arbitrary_types_allowed = True
......
......@@ -132,7 +132,8 @@ class ExtractProcessor:
notion_workspace_id=extract_setting.notion_info.notion_workspace_id,
notion_obj_id=extract_setting.notion_info.notion_obj_id,
notion_page_type=extract_setting.notion_info.notion_page_type,
document_model=extract_setting.notion_info.document
document_model=extract_setting.notion_info.document,
tenant_id=extract_setting.notion_info.tenant_id,
)
return extractor.extract()
else:
......
"""Abstract interface for document loader implementations."""
from typing import Optional
from bs4 import BeautifulSoup
from core.rag.extractor.extractor_base import BaseExtractor
from core.rag.extractor.helpers import detect_file_encodings
from core.rag.models.document import Document
class HtmlExtractor(BaseExtractor):
"""Load html files.
"""
Load html files.
Args:
......@@ -15,57 +16,19 @@ class HtmlExtractor(BaseExtractor):
"""
def __init__(
self,
file_path: str,
encoding: Optional[str] = None,
autodetect_encoding: bool = False,
source_column: Optional[str] = None,
csv_args: Optional[dict] = None,
self,
file_path: str
):
"""Initialize with file path."""
self._file_path = file_path
self._encoding = encoding
self._autodetect_encoding = autodetect_encoding
self.source_column = source_column
self.csv_args = csv_args or {}
def extract(self) -> list[Document]:
"""Load data into document objects."""
try:
with open(self._file_path, newline="", encoding=self._encoding) as csvfile:
docs = self._read_from_file(csvfile)
except UnicodeDecodeError as e:
if self._autodetect_encoding:
detected_encodings = detect_file_encodings(self._file_path)
for encoding in detected_encodings:
try:
with open(self._file_path, newline="", encoding=encoding.encoding) as csvfile:
docs = self._read_from_file(csvfile)
break
except UnicodeDecodeError:
continue
else:
raise RuntimeError(f"Error loading {self._file_path}") from e
return docs
return [Document(page_content=self._load_as_text())]
def _read_from_file(self, csvfile) -> list[Document]:
docs = []
csv_reader = csv.DictReader(csvfile, **self.csv_args) # type: ignore
for i, row in enumerate(csv_reader):
content = "\n".join(f"{k.strip()}: {v.strip()}" for k, v in row.items())
try:
source = (
row[self.source_column]
if self.source_column is not None
else ''
)
except KeyError:
raise ValueError(
f"Source column '{self.source_column}' not found in CSV file."
)
metadata = {"source": source, "row": i}
doc = Document(page_content=content, metadata=metadata)
docs.append(doc)
def _load_as_text(self) -> str:
with open(self._file_path, "rb") as fp:
soup = BeautifulSoup(fp, 'html.parser')
text = soup.get_text()
text = text.strip() if text else ''
return docs
return text
\ No newline at end of file
......@@ -30,8 +30,10 @@ class NotionExtractor(BaseExtractor):
notion_workspace_id: str,
notion_obj_id: str,
notion_page_type: str,
tenant_id: str,
document_model: Optional[DocumentModel] = None,
notion_access_token: Optional[str] = None
notion_access_token: Optional[str] = None,
):
self._notion_access_token = None
self._document_model = document_model
......
......@@ -200,7 +200,7 @@ class ApiTool(Tool):
# replace path parameters
for name, value in path_params.items():
url = url.replace(f'{{{name}}}', value)
url = url.replace(f'{{{name}}}', f'{value}')
# parse http body data if needed, for GET/HEAD/OPTIONS/TRACE, the body is ignored
if 'Content-Type' in headers:
......
import re
import uuid
from json import loads as json_loads
from requests import get
......@@ -46,7 +48,7 @@ class ApiBasedToolSchemaParser:
parameters = []
if 'parameters' in interface['operation']:
for parameter in interface['operation']['parameters']:
parameters.append(ToolParameter(
tool_parameter = ToolParameter(
name=parameter['name'],
label=I18nObject(
en_US=parameter['name'],
......@@ -61,7 +63,14 @@ class ApiBasedToolSchemaParser:
form=ToolParameter.ToolParameterForm.LLM,
llm_description=parameter.get('description'),
default=parameter['schema']['default'] if 'schema' in parameter and 'default' in parameter['schema'] else None,
))
)
# check if there is a type
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(parameter)
if typ:
tool_parameter.type = typ
parameters.append(tool_parameter)
# create tool bundle
# check if there is a request body
if 'requestBody' in interface['operation']:
......@@ -80,13 +89,14 @@ class ApiBasedToolSchemaParser:
root = root[ref]
# overwrite the content
interface['operation']['requestBody']['content'][content_type]['schema'] = root
# parse body parameters
if 'schema' in interface['operation']['requestBody']['content'][content_type]:
body_schema = interface['operation']['requestBody']['content'][content_type]['schema']
required = body_schema['required'] if 'required' in body_schema else []
properties = body_schema['properties'] if 'properties' in body_schema else {}
for name, property in properties.items():
parameters.append(ToolParameter(
tool = ToolParameter(
name=name,
label=I18nObject(
en_US=name,
......@@ -101,7 +111,14 @@ class ApiBasedToolSchemaParser:
form=ToolParameter.ToolParameterForm.LLM,
llm_description=property['description'] if 'description' in property else '',
default=property['default'] if 'default' in property else None,
))
)
# check if there is a type
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property)
if typ:
tool.type = typ
parameters.append(tool)
# check if parameters is duplicated
parameters_count = {}
......@@ -119,7 +136,11 @@ class ApiBasedToolSchemaParser:
path = interface['path']
if interface['path'].startswith('/'):
path = interface['path'][1:]
path = path.replace('/', '_')
# remove special characters like / to ensure the operation id is valid ^[a-zA-Z0-9_-]{1,64}$
path = re.sub(r'[^a-zA-Z0-9_-]', '', path)
if not path:
path = str(uuid.uuid4())
interface['operation']['operationId'] = f'{path}_{interface["method"]}'
bundles.append(ApiBasedToolBundle(
......@@ -134,7 +155,23 @@ class ApiBasedToolSchemaParser:
))
return bundles
@staticmethod
def _get_tool_parameter_type(parameter: dict) -> ToolParameter.ToolParameterType:
parameter = parameter or {}
typ = None
if 'type' in parameter:
typ = parameter['type']
elif 'schema' in parameter and 'type' in parameter['schema']:
typ = parameter['schema']['type']
if typ == 'integer' or typ == 'number':
return ToolParameter.ToolParameterType.NUMBER
elif typ == 'boolean':
return ToolParameter.ToolParameterType.BOOLEAN
elif typ == 'string':
return ToolParameter.ToolParameterType.STRING
@staticmethod
def parse_openapi_yaml_to_tool_bundle(yaml: str, extra_info: dict = None, warning: dict = None) -> list[ApiBasedToolBundle]:
"""
......
......@@ -133,8 +133,9 @@ class HitTestingService:
if embedding_length <= 1:
return [{'x': 0, 'y': 0}]
concatenate_data = np.array(embeddings).reshape(embedding_length, -1)
# concatenate_data = np.concatenate(embeddings)
noise = np.random.normal(0, 1e-4, np.array(embeddings).shape)
concatenate_data = np.array(embeddings) + noise
concatenate_data = concatenate_data.reshape(embedding_length, -1)
perplexity = embedding_length / 2 + 1
if perplexity >= embedding_length:
......
from flask import current_app
from flask_login import current_user
from extensions.ext_database import db
......@@ -31,7 +33,17 @@ class WorkspaceService:
can_replace_logo = FeatureService.get_features(tenant_info['id']).can_replace_logo
if can_replace_logo and TenantService.has_roles(tenant, [TenantAccountJoinRole.OWNER, TenantAccountJoinRole.ADMIN]):
if can_replace_logo and TenantService.has_roles(tenant,
[TenantAccountJoinRole.OWNER, TenantAccountJoinRole.ADMIN]):
base_url = current_app.config.get('FILES_URL')
replace_webapp_logo = f'{base_url}/files/workspaces/{tenant.id}/webapp-logo' if tenant.custom_config_dict.get('replace_webapp_logo') else None
remove_webapp_brand = tenant.custom_config_dict.get('remove_webapp_brand', False)
# Make sure the custom_config_dict is updated with the new values
tenant.custom_config_dict.update({
'remove_webapp_brand': remove_webapp_brand,
'replace_webapp_logo': replace_webapp_logo,
})
tenant_info['custom_config'] = tenant.custom_config_dict
return tenant_info
......@@ -58,7 +58,8 @@ def document_indexing_sync_task(dataset_id: str, document_id: str):
notion_workspace_id=workspace_id,
notion_obj_id=page_id,
notion_page_type=page_type,
notion_access_token=data_source_binding.access_token
notion_access_token=data_source_binding.access_token,
tenant_id=document.tenant_id
)
last_edited_time = loader.get_notion_last_edited_time()
......
......@@ -3,13 +3,16 @@ import React from 'react'
import Spinner from '../spinner'
export type IButtonProps = {
type?: string
/**
* The style of the button
*/
type?: 'primary' | 'warning' | (string & {})
className?: string
disabled?: boolean
loading?: boolean
tabIndex?: number
children: React.ReactNode
onClick?: MouseEventHandler<HTMLDivElement>
onClick?: MouseEventHandler<HTMLButtonElement>
}
const Button: FC<IButtonProps> = ({
......@@ -35,15 +38,16 @@ const Button: FC<IButtonProps> = ({
}
return (
<div
<button
className={`btn ${style} ${className && className}`}
tabIndex={tabIndex}
onClick={disabled ? undefined : onClick}
disabled={disabled}
onClick={onClick}
>
{children}
{/* Spinner is hidden when loading is false */}
<Spinner loading={loading} className='!text-white !h-3 !w-3 !border-2 !ml-1' />
</div>
</button>
)
}
......
import type { FC } from 'react'
import classNames from 'classnames'
type LogoSiteProps = {
className?: string
}
const LogoSite: FC<LogoSiteProps> = ({
className,
}) => {
return (
<img
src='/logo/logo-site.png'
className={`block w-auto h-10 ${className}`}
className={classNames('block w-auto h-10', className)}
alt='logo'
/>
)
......
'use client'
import type { FC } from 'react'
import React, { useEffect } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import PlanComp from '../plan'
import { ReceiptList } from '../../base/icons/src/vender/line/financeAndECommerce'
import { LinkExternal01 } from '../../base/icons/src/vender/line/general'
......@@ -12,17 +13,11 @@ import { useProviderContext } from '@/context/provider-context'
const Billing: FC = () => {
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
const [billingUrl, setBillingUrl] = React.useState('')
const { enableBilling } = useProviderContext()
useEffect(() => {
if (!enableBilling || !isCurrentWorkspaceManager)
return
(async () => {
const { url } = await fetchBillingUrl()
setBillingUrl(url)
})()
}, [isCurrentWorkspaceManager])
const { data: billingUrl } = useSWR(
(!enableBilling || !isCurrentWorkspaceManager) ? null : ['/billing/invoices'],
() => fetchBillingUrl().then(data => data.url),
)
return (
<div>
......@@ -39,4 +34,5 @@ const Billing: FC = () => {
</div>
)
}
export default React.memo(Billing)
......@@ -16,8 +16,6 @@ import {
updateCurrentWorkspace,
} from '@/service/common'
import { useAppContext } from '@/context/app-context'
import { API_PREFIX } from '@/config'
import { getPurifyHref } from '@/utils'
const ALLOW_FILE_EXTENSIONS = ['svg', 'png']
......@@ -123,7 +121,7 @@ const CustomWebAppBrand = () => {
POWERED BY
{
webappLogo
? <img key={webappLogo} src={`${getPurifyHref(API_PREFIX.slice(0, -12))}/files/workspaces/${currentWorkspace.id}/webapp-logo`} alt='logo' className='ml-2 block w-auto h-5' />
? <img key={webappLogo} src={webappLogo} alt='logo' className='ml-2 block w-auto h-5' />
: <LogoSite className='ml-2 !h-5' />
}
</div>
......
......@@ -289,9 +289,9 @@ The text generation application offers non-session support and is ideal for tran
</Col>
<Col sticky>
### Request Example
<CodeGroup title="Request" tag="POST" label="/completion-messages/:task_id/stop" targetCode={`curl -X POST 'https://cloud.dify.ai/v1/completion-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
<CodeGroup title="Request" tag="POST" label="/completion-messages/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/v1/completion-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST 'https://cloud.dify.ai/v1/completion-messages/:task_id/stop' \
curl -X POST '${props.appDetail.api_base_url}/v1/completion-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
......
......@@ -266,9 +266,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
- `result` (string) 固定返回 success
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/completion-messages/:task_id/stop" targetCode={`curl -X POST 'https://cloud.dify.ai/v1/completion-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
<CodeGroup title="Request" tag="POST" label="/completion-messages/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/v1/completion-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST 'https://cloud.dify.ai/v1/completion-messages/:task_id/stop' \
curl -X POST '${props.appDetail.api_base_url}/v1/completion-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
......
......@@ -344,9 +344,9 @@ Chat applications support session persistence, allowing previous chat history to
</Col>
<Col sticky>
### Request Example
<CodeGroup title="Request" tag="POST" label="/chat-messages/:task_id/stop" targetCode={`curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{"user": "abc-123"}'`}>
<CodeGroup title="Request" tag="POST" label="/chat-messages/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/v1/chat-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{"user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \
curl -X POST '${props.appDetail.api_base_url}/v1/chat-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
......@@ -1025,9 +1025,9 @@ Chat applications support session persistence, allowing previous chat history to
- (string) url of icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET 'https://cloud.dify.ai/v1/meta?user=abc-123' \\\n-H 'Authorization: Bearer {api_key}'`}>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/v1/meta?user=abc-123' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET 'https://cloud.dify.ai/v1/meta?user=abc-123' \
curl -X GET '${props.appDetail.api_base_url}/v1/meta?user=abc-123' \
-H 'Authorization: Bearer {api_key}'
```
......
......@@ -360,9 +360,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
- `result` (string) 固定返回 success
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/chat-messages/:task_id/stop" targetCode={`curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
<CodeGroup title="Request" tag="POST" label="/chat-messages/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/v1/chat-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \
curl -X POST '${props.appDetail.api_base_url}/v1/chat-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
......@@ -1022,9 +1022,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
- (string) 图标URL
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET 'https://cloud.dify.ai/v1/meta?user=abc-123' \\\n-H 'Authorization: Bearer {api_key}'`}>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/v1/meta?user=abc-123' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET 'https://cloud.dify.ai/v1/meta?user=abc-123' \
curl -X GET '${props.appDetail.api_base_url}/v1/meta?user=abc-123' \
-H 'Authorization: Bearer {api_key}'
```
......
'use client'
import type { FC } from 'react'
import React, { useEffect } from 'react'
import React from 'react'
import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import useSWR from 'swr'
import Toast from '../../base/toast'
import s from './style.module.css'
import ExploreContext from '@/context/explore-context'
import type { App, AppCategory } from '@/models/explore'
import type { App } from '@/models/explore'
import Category from '@/app/components/explore/category'
import AppCard from '@/app/components/explore/app-card'
import { fetchAppDetail, fetchAppList } from '@/service/explore'
......@@ -25,34 +26,44 @@ const Apps: FC = () => {
const { isCurrentWorkspaceManager } = useAppContext()
const router = useRouter()
const { hasEditPermission } = useContext(ExploreContext)
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
const [currCategory, setCurrCategory] = useTabSearchParams({
defaultTab: '',
defaultTab: allCategoriesEn,
})
const [allList, setAllList] = React.useState<App[]>([])
const [isLoaded, setIsLoaded] = React.useState(false)
const {
data: { categories, allList },
isLoading,
} = useSWR(
['/explore/apps'],
() =>
fetchAppList().then(({ categories, recommended_apps }) => ({
categories,
allList: recommended_apps.sort((a, b) => a.position - b.position),
})),
{
fallbackData: {
categories: [],
allList: [],
},
},
)
const currList = (() => {
if (currCategory === '')
if (currCategory === allCategoriesEn)
return allList
return allList.filter(item => item.category === currCategory)
})()
const [categories, setCategories] = React.useState<AppCategory[]>([])
useEffect(() => {
(async () => {
const { categories, recommended_apps }: any = await fetchAppList()
const sortedRecommendedApps = [...recommended_apps]
sortedRecommendedApps.sort((a, b) => a.position - b.position) // position from small to big
setCategories(categories)
setAllList(sortedRecommendedApps)
setIsLoaded(true)
})()
}, [])
const [currApp, setCurrApp] = React.useState<App | null>(null)
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
const onCreate: CreateAppModalProps['onConfirm'] = async ({ name, icon, icon_background }) => {
const { app_model_config: model_config } = await fetchAppDetail(currApp?.app.id as string)
const onCreate: CreateAppModalProps['onConfirm'] = async ({
name,
icon,
icon_background,
}) => {
const { app_model_config: model_config } = await fetchAppDetail(
currApp?.app.id as string,
)
try {
const app = await createApp({
......@@ -68,36 +79,45 @@ const Apps: FC = () => {
message: t('app.newApp.appCreated'),
})
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
router.push(
`/app/${app.id}/${
isCurrentWorkspaceManager ? 'configuration' : 'overview'
}`,
)
}
catch (e) {
Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
}
}
if (!isLoaded) {
if (!isLoading) {
return (
<div className='flex h-full items-center'>
<Loading type='area' />
<div className="flex h-full items-center">
<Loading type="area" />
</div>
)
}
return (
<div className='h-full flex flex-col border-l border-gray-200'>
<div className='shrink-0 pt-6 px-12'>
<div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>{t('explore.apps.title')}</div>
<div className='text-gray-500 text-sm'>{t('explore.apps.description')}</div>
<div className="h-full flex flex-col border-l border-gray-200">
<div className="shrink-0 pt-6 px-12">
<div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>
{t('explore.apps.title')}
</div>
<div className="text-gray-500 text-sm">
{t('explore.apps.description')}
</div>
</div>
<Category
className='mt-6 px-12'
className="mt-6 px-12"
list={categories}
value={currCategory}
onChange={setCurrCategory}
/>
<div className='relative flex flex-1 mt-6 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow'>
<div className="relative flex flex-1 mt-6 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow">
<nav
className={`${s.appList} grid content-start gap-4 px-6 sm:px-12 shrink-0`}>
className={`${s.appList} grid content-start gap-4 px-6 sm:px-12 shrink-0`}
>
{currList.map(app => (
<AppCard
key={app.app_id}
......
......@@ -138,16 +138,12 @@ export default function AccountSetting({
]
const scrollRef = useRef<HTMLDivElement>(null)
const [scrolled, setScrolled] = useState(false)
const scrollHandle = (e: Event) => {
if ((e.target as HTMLDivElement).scrollTop > 0)
setScrolled(true)
else
setScrolled(false)
}
useEffect(() => {
const targetElement = scrollRef.current
const scrollHandle = (e: Event) => {
const userScrolled = (e.target as HTMLDivElement).scrollTop > 0
setScrolled(userScrolled)
}
targetElement?.addEventListener('scroll', scrollHandle)
return () => {
targetElement?.removeEventListener('scroll', scrollHandle)
......
......@@ -54,7 +54,7 @@ const Header = () => {
</div>}
{!isMobile && <>
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite />
<LogoSite className='object-contain' />
</Link>
<GithubStar />
</>}
......
......@@ -16,7 +16,7 @@ export type App = {
app_id: string
description: string
copyright: string
privacy_policy: string
privacy_policy: string | null
category: AppCategory
position: number
is_listed: boolean
......
import { del, get, patch, post } from './base'
import type { App, AppCategory } from '@/models/explore'
export const fetchAppList = () => {
return get('/explore/apps')
return get<{
categories: AppCategory[]
recommended_apps: App[]
}>('/explore/apps')
}
export const fetchAppDetail = (id: string): Promise<any> => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment