Commit 22966a36 authored by Joel's avatar Joel

chore: merge main

parents aec46794 e9810a6d
...@@ -27,4 +27,4 @@ RUN chmod +x /entrypoint.sh ...@@ -27,4 +27,4 @@ RUN chmod +x /entrypoint.sh
ARG COMMIT_SHA ARG COMMIT_SHA
ENV COMMIT_SHA ${COMMIT_SHA} ENV COMMIT_SHA ${COMMIT_SHA}
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
\ No newline at end of file \ No newline at end of file
...@@ -2,6 +2,7 @@ import datetime ...@@ -2,6 +2,7 @@ import datetime
import logging import logging
import random import random
import string import string
import time
import click import click
from flask import current_app from flask import current_app
...@@ -13,7 +14,7 @@ from libs.helper import email as email_validate ...@@ -13,7 +14,7 @@ from libs.helper import email as email_validate
from extensions.ext_database import db from extensions.ext_database import db
from libs.rsa import generate_key_pair from libs.rsa import generate_key_pair
from models.account import InvitationCode, Tenant from models.account import InvitationCode, Tenant
from models.dataset import Dataset from models.dataset import Dataset, DatasetQuery, Document, DocumentSegment
from models.model import Account from models.model import Account
import secrets import secrets
import base64 import base64
...@@ -172,7 +173,7 @@ def recreate_all_dataset_indexes(): ...@@ -172,7 +173,7 @@ def recreate_all_dataset_indexes():
page = 1 page = 1
while True: while True:
try: try:
datasets = db.session.query(Dataset).filter(Dataset.indexing_technique == 'high_quality')\ datasets = db.session.query(Dataset).filter(Dataset.indexing_technique == 'high_quality') \
.order_by(Dataset.created_at.desc()).paginate(page=page, per_page=50) .order_by(Dataset.created_at.desc()).paginate(page=page, per_page=50)
except NotFound: except NotFound:
break break
...@@ -188,12 +189,66 @@ def recreate_all_dataset_indexes(): ...@@ -188,12 +189,66 @@ def recreate_all_dataset_indexes():
else: else:
click.echo('passed.') click.echo('passed.')
except Exception as e: except Exception as e:
click.echo(click.style('Recreate dataset index error: {} {}'.format(e.__class__.__name__, str(e)), fg='red')) click.echo(
click.style('Recreate dataset index error: {} {}'.format(e.__class__.__name__, str(e)), fg='red'))
continue continue
click.echo(click.style('Congratulations! Recreate {} dataset indexes.'.format(recreate_count), fg='green')) click.echo(click.style('Congratulations! Recreate {} dataset indexes.'.format(recreate_count), fg='green'))
@click.command('clean-unused-dataset-indexes', help='Clean unused dataset indexes.')
def clean_unused_dataset_indexes():
click.echo(click.style('Start clean unused dataset indexes.', fg='green'))
clean_days = int(current_app.config.get('CLEAN_DAY_SETTING'))
start_at = time.perf_counter()
thirty_days_ago = datetime.datetime.now() - datetime.timedelta(days=clean_days)
page = 1
while True:
try:
datasets = db.session.query(Dataset).filter(Dataset.created_at < thirty_days_ago) \
.order_by(Dataset.created_at.desc()).paginate(page=page, per_page=50)
except NotFound:
break
page += 1
for dataset in datasets:
dataset_query = db.session.query(DatasetQuery).filter(
DatasetQuery.created_at > thirty_days_ago,
DatasetQuery.dataset_id == dataset.id
).all()
if not dataset_query or len(dataset_query) == 0:
documents = db.session.query(Document).filter(
Document.dataset_id == dataset.id,
Document.indexing_status == 'completed',
Document.enabled == True,
Document.archived == False,
Document.updated_at > thirty_days_ago
).all()
if not documents or len(documents) == 0:
try:
# remove index
vector_index = IndexBuilder.get_index(dataset, 'high_quality')
kw_index = IndexBuilder.get_index(dataset, 'economy')
# delete from vector index
if vector_index:
vector_index.delete()
kw_index.delete()
# update document
update_params = {
Document.enabled: False
}
Document.query.filter_by(dataset_id=dataset.id).update(update_params)
db.session.commit()
click.echo(click.style('Cleaned unused dataset {} from db success!'.format(dataset.id),
fg='green'))
except Exception as e:
click.echo(
click.style('clean dataset index error: {} {}'.format(e.__class__.__name__, str(e)),
fg='red'))
end_at = time.perf_counter()
click.echo(click.style('Cleaned unused dataset from db success latency: {}'.format(end_at - start_at), fg='green'))
@click.command('sync-anthropic-hosted-providers', help='Sync anthropic hosted providers.') @click.command('sync-anthropic-hosted-providers', help='Sync anthropic hosted providers.')
def sync_anthropic_hosted_providers(): def sync_anthropic_hosted_providers():
click.echo(click.style('Start sync anthropic hosted providers.', fg='green')) click.echo(click.style('Start sync anthropic hosted providers.', fg='green'))
...@@ -218,7 +273,9 @@ def sync_anthropic_hosted_providers(): ...@@ -218,7 +273,9 @@ def sync_anthropic_hosted_providers():
) )
count += 1 count += 1
except Exception as e: except Exception as e:
click.echo(click.style('Sync tenant anthropic hosted provider error: {} {}'.format(e.__class__.__name__, str(e)), fg='red')) click.echo(click.style(
'Sync tenant anthropic hosted provider error: {} {}'.format(e.__class__.__name__, str(e)),
fg='red'))
continue continue
click.echo(click.style('Congratulations! Synced {} anthropic hosted providers.'.format(count), fg='green')) click.echo(click.style('Congratulations! Synced {} anthropic hosted providers.'.format(count), fg='green'))
...@@ -231,3 +288,4 @@ def register_commands(app): ...@@ -231,3 +288,4 @@ def register_commands(app):
app.cli.add_command(reset_encrypt_key_pair) app.cli.add_command(reset_encrypt_key_pair)
app.cli.add_command(recreate_all_dataset_indexes) app.cli.add_command(recreate_all_dataset_indexes)
app.cli.add_command(sync_anthropic_hosted_providers) app.cli.add_command(sync_anthropic_hosted_providers)
app.cli.add_command(clean_unused_dataset_indexes)
...@@ -53,7 +53,8 @@ DEFAULTS = { ...@@ -53,7 +53,8 @@ DEFAULTS = {
'DEFAULT_LLM_PROVIDER': 'openai', 'DEFAULT_LLM_PROVIDER': 'openai',
'OPENAI_HOSTED_QUOTA_LIMIT': 200, 'OPENAI_HOSTED_QUOTA_LIMIT': 200,
'ANTHROPIC_HOSTED_QUOTA_LIMIT': 1000, 'ANTHROPIC_HOSTED_QUOTA_LIMIT': 1000,
'TENANT_DOCUMENT_COUNT': 100 'TENANT_DOCUMENT_COUNT': 100,
'CLEAN_DAY_SETTING': 30
} }
...@@ -89,7 +90,7 @@ class Config: ...@@ -89,7 +90,7 @@ class Config:
self.CONSOLE_URL = get_env('CONSOLE_URL') self.CONSOLE_URL = get_env('CONSOLE_URL')
self.API_URL = get_env('API_URL') self.API_URL = get_env('API_URL')
self.APP_URL = get_env('APP_URL') self.APP_URL = get_env('APP_URL')
self.CURRENT_VERSION = "0.3.9" self.CURRENT_VERSION = "0.3.10"
self.COMMIT_SHA = get_env('COMMIT_SHA') self.COMMIT_SHA = get_env('COMMIT_SHA')
self.EDITION = "SELF_HOSTED" self.EDITION = "SELF_HOSTED"
self.DEPLOY_ENV = get_env('DEPLOY_ENV') self.DEPLOY_ENV = get_env('DEPLOY_ENV')
...@@ -215,6 +216,7 @@ class Config: ...@@ -215,6 +216,7 @@ class Config:
self.NOTION_INTEGRATION_TOKEN = get_env('NOTION_INTEGRATION_TOKEN') self.NOTION_INTEGRATION_TOKEN = get_env('NOTION_INTEGRATION_TOKEN')
self.TENANT_DOCUMENT_COUNT = get_env('TENANT_DOCUMENT_COUNT') self.TENANT_DOCUMENT_COUNT = get_env('TENANT_DOCUMENT_COUNT')
self.CLEAN_DAY_SETTING = get_env('CLEAN_DAY_SETTING')
class CloudEditionConfig(Config): class CloudEditionConfig(Config):
......
...@@ -3,7 +3,6 @@ from flask import request ...@@ -3,7 +3,6 @@ from flask import request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_restful import Resource, reqparse, fields, marshal, marshal_with from flask_restful import Resource, reqparse, fields, marshal, marshal_with
from werkzeug.exceptions import NotFound, Forbidden from werkzeug.exceptions import NotFound, Forbidden
import services import services
from controllers.console import api from controllers.console import api
from controllers.console.datasets.error import DatasetNameDuplicateError from controllers.console.datasets.error import DatasetNameDuplicateError
......
...@@ -69,9 +69,8 @@ class LLMCallbackHandler(BaseCallbackHandler): ...@@ -69,9 +69,8 @@ class LLMCallbackHandler(BaseCallbackHandler):
if not self.conversation_message_task.streaming: if not self.conversation_message_task.streaming:
self.conversation_message_task.append_message_text(response.generations[0][0].text) self.conversation_message_task.append_message_text(response.generations[0][0].text)
self.llm_message.completion = response.generations[0][0].text self.llm_message.completion = response.generations[0][0].text
self.llm_message.completion_tokens = response.llm_output['token_usage']['completion_tokens']
else: self.llm_message.completion_tokens = self.llm.get_num_tokens(self.llm_message.completion)
self.llm_message.completion_tokens = self.llm.get_num_tokens(self.llm_message.completion)
self.conversation_message_task.save_message(self.llm_message) self.conversation_message_task.save_message(self.llm_message)
......
...@@ -128,9 +128,13 @@ class LLMBuilder: ...@@ -128,9 +128,13 @@ class LLMBuilder:
azure_openai_provider = BaseProvider.get_valid_provider(tenant_id, ProviderName.AZURE_OPENAI.value) azure_openai_provider = BaseProvider.get_valid_provider(tenant_id, ProviderName.AZURE_OPENAI.value)
provider = None provider = None
if openai_provider: if openai_provider and openai_provider.provider_type == ProviderType.CUSTOM.value:
provider = openai_provider provider = openai_provider
elif azure_openai_provider: elif azure_openai_provider and azure_openai_provider.provider_type == ProviderType.CUSTOM.value:
provider = azure_openai_provider
elif openai_provider and openai_provider.provider_type == ProviderType.SYSTEM.value:
provider = openai_provider
elif azure_openai_provider and azure_openai_provider.provider_type == ProviderType.SYSTEM.value:
provider = azure_openai_provider provider = azure_openai_provider
if not provider: if not provider:
...@@ -139,9 +143,6 @@ class LLMBuilder: ...@@ -139,9 +143,6 @@ class LLMBuilder:
f"Please go to Settings -> Model Provider to complete your provider credentials." f"Please go to Settings -> Model Provider to complete your provider credentials."
) )
if provider.provider_type == ProviderType.SYSTEM.value: provider_name = provider.provider_name
provider_name = 'openai'
else:
provider_name = provider.provider_name
return provider_name return provider_name
...@@ -2,6 +2,7 @@ import json ...@@ -2,6 +2,7 @@ import json
import logging import logging
from typing import Optional, Union from typing import Optional, Union
import openai
import requests import requests
from core.llm.provider.base import BaseProvider from core.llm.provider.base import BaseProvider
...@@ -11,30 +12,37 @@ from models.provider import ProviderName ...@@ -11,30 +12,37 @@ from models.provider import ProviderName
class AzureProvider(BaseProvider): class AzureProvider(BaseProvider):
def get_models(self, model_id: Optional[str] = None, credentials: Optional[dict] = None) -> list[dict]: def get_models(self, model_id: Optional[str] = None, credentials: Optional[dict] = None) -> list[dict]:
credentials = self.get_credentials(model_id) if not credentials else credentials return []
url = "{}/openai/deployments?api-version={}".format(
str(credentials.get('openai_api_base')), def check_embedding_model(self, credentials: Optional[dict] = None):
str(credentials.get('openai_api_version')) credentials = self.get_credentials('text-embedding-ada-002') if not credentials else credentials
) try:
result = openai.Embedding.create(input=['test'],
headers = { engine='text-embedding-ada-002',
"api-key": str(credentials.get('openai_api_key')), timeout=60,
"content-type": "application/json; charset=utf-8" api_key=str(credentials.get('openai_api_key')),
} api_base=str(credentials.get('openai_api_base')),
api_type='azure',
response = requests.get(url, headers=headers) api_version=str(credentials.get('openai_api_version')))["data"][0][
"embedding"]
if response.status_code == 200: except openai.error.AuthenticationError as e:
result = response.json() raise AzureAuthenticationError(str(e))
return [{ except openai.error.APIConnectionError as e:
'id': deployment['id'], raise AzureRequestFailedError(
'name': '{} ({})'.format(deployment['id'], deployment['model']) 'Failed to request Azure OpenAI, please check your API Base Endpoint, The format is `https://xxx.openai.azure.com/`')
} for deployment in result['data'] if deployment['status'] == 'succeeded'] except openai.error.InvalidRequestError as e:
else: if e.http_status == 404:
if response.status_code == 401: raise AzureRequestFailedError("Please check your 'gpt-3.5-turbo' or 'text-embedding-ada-002' "
raise AzureAuthenticationError() "deployment name is exists in Azure AI")
else: else:
raise AzureRequestFailedError('Failed to request Azure OpenAI. Status code: {}'.format(response.status_code)) raise AzureRequestFailedError(
'Failed to request Azure OpenAI. cause: {}'.format(str(e)))
except openai.error.OpenAIError as e:
raise AzureRequestFailedError(
'Failed to request Azure OpenAI. cause: {}'.format(str(e)))
if not isinstance(result, list):
raise AzureRequestFailedError('Failed to request Azure OpenAI.')
def get_credentials(self, model_id: Optional[str] = None) -> dict: def get_credentials(self, model_id: Optional[str] = None) -> dict:
""" """
...@@ -94,32 +102,11 @@ class AzureProvider(BaseProvider): ...@@ -94,32 +102,11 @@ class AzureProvider(BaseProvider):
if 'openai_api_version' not in config: if 'openai_api_version' not in config:
config['openai_api_version'] = '2023-03-15-preview' config['openai_api_version'] = '2023-03-15-preview'
models = self.get_models(credentials=config) self.check_embedding_model(credentials=config)
if not models:
raise ValidateFailedError("Please add deployments for 'text-davinci-003', "
"'gpt-3.5-turbo', 'text-embedding-ada-002' (required) "
"and 'gpt-4', 'gpt-35-turbo-16k' (optional).")
fixed_model_ids = [
'text-davinci-003',
'gpt-35-turbo',
'text-embedding-ada-002'
]
current_model_ids = [model['id'] for model in models]
missing_model_ids = [fixed_model_id for fixed_model_id in fixed_model_ids if
fixed_model_id not in current_model_ids]
if missing_model_ids:
raise ValidateFailedError("Please add deployments for '{}'.".format(", ".join(missing_model_ids)))
except ValidateFailedError as e: except ValidateFailedError as e:
raise e raise e
except AzureAuthenticationError: except AzureAuthenticationError:
raise ValidateFailedError('Validation failed, please check your API Key.') raise ValidateFailedError('Validation failed, please check your API Key.')
except (requests.ConnectionError, requests.RequestException):
raise ValidateFailedError('Validation failed, please check your API Base Endpoint.')
except AzureRequestFailedError as ex: except AzureRequestFailedError as ex:
raise ValidateFailedError('Validation failed, error: {}.'.format(str(ex))) raise ValidateFailedError('Validation failed, error: {}.'.format(str(ex)))
except Exception as ex: except Exception as ex:
......
...@@ -44,14 +44,13 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): ...@@ -44,14 +44,13 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str):
if dataset_documents: if dataset_documents:
# save vector index # save vector index
index = IndexBuilder.get_index(dataset, 'high_quality', ignore_high_quality_check=True) index = IndexBuilder.get_index(dataset, 'high_quality', ignore_high_quality_check=True)
documents = []
for dataset_document in dataset_documents: for dataset_document in dataset_documents:
# delete from vector index # delete from vector index
segments = db.session.query(DocumentSegment).filter( segments = db.session.query(DocumentSegment).filter(
DocumentSegment.document_id == dataset_document.id, DocumentSegment.document_id == dataset_document.id,
DocumentSegment.enabled == True DocumentSegment.enabled == True
) .order_by(DocumentSegment.position.asc()).all() ) .order_by(DocumentSegment.position.asc()).all()
documents = []
for segment in segments: for segment in segments:
document = Document( document = Document(
page_content=segment.content, page_content=segment.content,
...@@ -65,8 +64,8 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): ...@@ -65,8 +64,8 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str):
documents.append(document) documents.append(document)
# save vector index # save vector index
index.add_texts(documents) index.add_texts(documents)
end_at = time.perf_counter() end_at = time.perf_counter()
logging.info( logging.info(
......
...@@ -2,7 +2,7 @@ version: '3.1' ...@@ -2,7 +2,7 @@ version: '3.1'
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.3.9 image: langgenius/dify-api:0.3.10
restart: always restart: always
environment: environment:
# Startup mode, 'api' starts the API server. # Startup mode, 'api' starts the API server.
...@@ -124,7 +124,7 @@ services: ...@@ -124,7 +124,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.3.9 image: langgenius/dify-api:0.3.10
restart: always restart: always
environment: environment:
# Startup mode, 'worker' starts the Celery worker for processing the queue. # Startup mode, 'worker' starts the Celery worker for processing the queue.
...@@ -176,7 +176,7 @@ services: ...@@ -176,7 +176,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.3.9 image: langgenius/dify-web:0.3.10
restart: always restart: always
environment: environment:
EDITION: SELF_HOSTED EDITION: SELF_HOSTED
......
...@@ -20,16 +20,20 @@ export const routes = { ...@@ -20,16 +20,20 @@ export const routes = {
}, },
getConversationMessages: { getConversationMessages: {
method: "GET", method: "GET",
url: () => "/messages", url: () => `/messages`,
}, },
getConversations: { getConversations: {
method: "GET", method: "GET",
url: () => "/conversations", url: () => `/conversations`,
}, },
renameConversation: { renameConversation: {
method: "PATCH", method: "PATCH",
url: (conversation_id) => `/conversations/${conversation_id}`, url: (conversation_id) => `/conversations/${conversation_id}`,
}, },
deleteConversation: {
method: "DELETE",
url: (conversation_id) => `/conversations/${conversation_id}`,
},
}; };
export class DifyClient { export class DifyClient {
...@@ -185,4 +189,13 @@ export class ChatClient extends DifyClient { ...@@ -185,4 +189,13 @@ export class ChatClient extends DifyClient {
data data
); );
} }
deleteConversation(conversation_id, user) {
const data = { user };
return this.sendRequest(
routes.deleteConversation.method,
routes.deleteConversation.url(conversation_id),
data
);
}
} }
\ No newline at end of file
name: Ruby
on: [push,pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
- name: Run the default task
run: |
gem install bundler -v 2.2.3
bundle install
bundle exec rake
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes
Layout/LineLength:
Max: 120
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at 427733928@qq.com. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
# frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in dify_client.gemspec
gemspec
gem "rake", "~> 13.0"
gem "minitest", "~> 5.0"
gem "rubocop", "~> 0.80"
gem 'webmock'
\ No newline at end of file
PATH
remote: .
specs:
dify_client (0.1.0)
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
crack (0.4.5)
rexml
hashdiff (1.0.1)
minitest (5.18.1)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
public_suffix (5.0.3)
racc (1.7.1)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.2.5)
rubocop (0.93.1)
parallel (~> 1.10)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8)
rexml
rubocop-ast (>= 0.6.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
unicode-display_width (1.8.0)
webmock (3.18.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
PLATFORMS
arm64-darwin-21
DEPENDENCIES
dify_client!
minitest (~> 5.0)
rake (~> 13.0)
rubocop (~> 0.80)
webmock
BUNDLED WITH
2.2.3
The MIT License (MIT)
Copyright (c) 2023 crazywoola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# DifyClient
Welcome to the DifyClient gem! This gem provides a Ruby client for interacting with the Dify.ai API. It allows you to perform various actions such as sending requests, providing feedback, creating completion messages, managing conversations, and more.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'dify_client'
```
And then execute:
$ bundle install
Or install it yourself as:
$ gem install dify_client
## Usage
To use the DifyClient gem, follow these steps:
1 Require the gem:
```ruby
require 'dify_client'
```
2 Create a new client instance:
```ruby
api_key = 'YOUR_API_KEY'
client = DifyClient::Client.new(api_key)
```
3 Use the available methods to interact with the Dify.ai API. Here are the methods provided by the DifyClient::Client class:
### Update API Key
```ruby
client.update_api_key('NEW_API_KEY')
```
Updates the API key used by the client.
### Message Feedback
```ruby
client.message_feedback(message_id, rating, user)
```
Submits feedback for a specific message identified by `message_id`. The `rating` parameter should be the rating value, and `user` is the user identifier.
### Get Application Parameters
```ruby
client.get_application_parameters(user)
```
### Create Completion Message
```ruby
client.create_completion_message(inputs, query, user, stream = false)
```
Creates a completion message with the provided `inputs`, `query`, and `user`. The stream parameter is optional and set to `false` by default. Set it to `true` to enable streaming response mode.
### Create Chat Message
```ruby
client.create_chat_message(inputs, query, user, stream = false, conversation_id = nil)
```
Creates a chat message with the provided `inputs`, `query`, and `user`. The stream parameter is optional and set to `false` by default. Set it to `true` to enable streaming response mode. The `conversation_id` parameter is optional and can be used to specify the conversation ID.
### Get Conversations
```ruby
client.get_conversations(user, first_id = nil, limit = nil, pinned = nil)
```
Retrieves the conversations for a given `user`. You can provide `first_id`, `limit`, and `pinned` parameters to customize the retrieval.
### Rename Conversation
```ruby
client.rename_conversation(conversation_id, name, user)
```
Renames a conversation identified by conversatio`n_id with the provided `name` for the given `user`.
### Delete Conversation
```ruby
client.delete_conversation(conversation_id, user)
```
Deletes a conversation identified by `conversation_id` for the given `user`.
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run` bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
## Contributing
Bug reports and pull requests are welcome on GitHub at [https://github.com/langgenius/dify/issues](https://github.com/langgenius/dify/issues).
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
\ No newline at end of file
# frozen_string_literal: true
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
end
require "rubocop/rake_task"
RuboCop::RakeTask.new
task default: %i[test rubocop]
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "dify_client"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
# frozen_string_literal: true
require_relative "lib/dify_client/version"
Gem::Specification.new do |spec|
spec.name = "dify_client"
spec.version = DifyClient::VERSION
spec.authors = ["crazywoola"]
spec.email = ["427733928@qq.com"]
spec.summary = "Ruby client for Dify"
spec.description = "Ruby client for Dify"
spec.homepage = "https://dify.ai"
spec.license = "MIT"
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/langgenius/dify/tree/main/sdks"
spec.metadata["changelog_uri"] = "https://github.com/langgenius/dify/tree/main/sdks"
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
# Uncomment to register a new dependency of your gem
# spec.add_dependency "example-gem", "~> 1.0"
# For more information and examples about making a new gem, checkout our
# guide at: https://bundler.io/guides/creating_gem.html
end
require 'test_helper'
require 'webmock/minitest'
require 'json'
require 'dify_client'
class DifyClientTest < Minitest::Test
def setup
@api_key = 'YOUR_API_KEY'
@client = DifyClient::Client.new(@api_key)
end
def test_update_api_key
new_api_key = 'NEW_API_KEY'
@client.update_api_key(new_api_key)
assert_equal new_api_key, @client.instance_variable_get(:@api_key)
end
def test_get_application_parameters
user = 'USER_ID'
expected_response = {}
stub_request(:get, "https://api.dify.ai/v1/parameters").
with(
body: {"user"=>"USER_ID"},
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Authorization'=>'Bearer YOUR_API_KEY',
'Content-Type'=>'application/x-www-form-urlencoded',
'Responsetype'=>'json',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: expected_response.to_json, headers: {})
response = @client.get_application_parameters(user)
assert_equal expected_response, response
end
end
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "dify_client"
require "minitest/autorun"
...@@ -26,4 +26,4 @@ RUN chmod +x /entrypoint.sh ...@@ -26,4 +26,4 @@ RUN chmod +x /entrypoint.sh
ARG COMMIT_SHA ARG COMMIT_SHA
ENV COMMIT_SHA ${COMMIT_SHA} ENV COMMIT_SHA ${COMMIT_SHA}
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
...@@ -3,6 +3,7 @@ import type { ReactNode } from 'react' ...@@ -3,6 +3,7 @@ import type { ReactNode } from 'react'
import SwrInitor from '@/app/components/swr-initor' import SwrInitor from '@/app/components/swr-initor'
import { AppContextProvider } from '@/context/app-context' import { AppContextProvider } from '@/context/app-context'
import GA, { GaType } from '@/app/components/base/ga' import GA, { GaType } from '@/app/components/base/ga'
import HeaderWrapper from '@/app/components/header/HeaderWrapper'
import Header from '@/app/components/header' import Header from '@/app/components/header'
const Layout = ({ children }: { children: ReactNode }) => { const Layout = ({ children }: { children: ReactNode }) => {
...@@ -11,7 +12,9 @@ const Layout = ({ children }: { children: ReactNode }) => { ...@@ -11,7 +12,9 @@ const Layout = ({ children }: { children: ReactNode }) => {
<GA gaType={GaType.admin} /> <GA gaType={GaType.admin} />
<SwrInitor> <SwrInitor>
<AppContextProvider> <AppContextProvider>
<Header /> <HeaderWrapper>
<Header />
</HeaderWrapper>
{children} {children}
</AppContextProvider> </AppContextProvider>
</SwrInitor> </SwrInitor>
......
...@@ -40,7 +40,7 @@ const Config: FC = () => { ...@@ -40,7 +40,7 @@ const Config: FC = () => {
} = useContext(ConfigContext) } = useContext(ConfigContext)
const isChatApp = mode === AppType.chat const isChatApp = mode === AppType.chat
const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo) const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo)
const targetProvider = userInfo?.providers?.find(({ token_is_set, is_valid }) => token_is_set && is_valid) const openaiProvider = userInfo?.providers?.find(({ token_is_set, is_valid, provider_name }) => token_is_set && is_valid && provider_name === 'openai')
const promptTemplate = modelConfig.configs.prompt_template const promptTemplate = modelConfig.configs.prompt_template
const promptVariables = modelConfig.configs.prompt_variables const promptVariables = modelConfig.configs.prompt_variables
...@@ -92,7 +92,7 @@ const Config: FC = () => { ...@@ -92,7 +92,7 @@ const Config: FC = () => {
}, },
}) })
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && targetProvider?.provider_name === 'openai')) const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && openaiProvider))
const hasToolbox = false const hasToolbox = false
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
...@@ -122,7 +122,7 @@ const Config: FC = () => { ...@@ -122,7 +122,7 @@ const Config: FC = () => {
isChatApp={isChatApp} isChatApp={isChatApp}
config={featureConfig} config={featureConfig}
onChange={handleFeatureChange} onChange={handleFeatureChange}
showSpeechToTextItem={targetProvider?.provider_name === 'openai'} showSpeechToTextItem={!!openaiProvider}
/> />
)} )}
{showAutomatic && ( {showAutomatic && (
......
...@@ -116,22 +116,22 @@ const Debug: FC<IDebug> = ({ ...@@ -116,22 +116,22 @@ const Debug: FC<IDebug> = ({
} }
const checkCanSend = () => { const checkCanSend = () => {
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => { const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) // compatible with old version }) // compatible with old version
// debugger // debugger
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs[key]) if (!inputs[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
...@@ -122,6 +122,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({ ...@@ -122,6 +122,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
items={(options || []).map(i => ({ name: i, value: i }))} items={(options || []).map(i => ({ name: i, value: i }))}
allowSearch={false} allowSearch={false}
bgClassName='bg-gray-50' bgClassName='bg-gray-50'
overlayClassName='z-[11]'
/> />
) )
: ( : (
......
'use client' 'use client'
import cn from 'classnames' import cn from 'classnames'
interface IAvatarProps { type AvatarProps = {
name: string name: string
avatar?: string avatar?: string
size?: number size?: number
className?: string className?: string
textClassName?: string
} }
const Avatar = ({ const Avatar = ({
name, name,
avatar, avatar,
size = 30, size = 30,
className className,
}: IAvatarProps) => { textClassName,
const avatarClassName = `shrink-0 flex items-center rounded-full bg-primary-600` }: AvatarProps) => {
const style = { width: `${size}px`, height:`${size}px`, fontSize: `${size}px`, lineHeight: `${size}px` } const avatarClassName = 'shrink-0 flex items-center rounded-full bg-primary-600'
const style = { width: `${size}px`, height: `${size}px`, fontSize: `${size}px`, lineHeight: `${size}px` }
if (avatar) { if (avatar) {
return ( return (
<img <img
className={cn(avatarClassName, className)} className={cn(avatarClassName, className)}
style={style} style={style}
alt={name} alt={name}
src={avatar} src={avatar}
/> />
) )
} }
return ( return (
<div <div
className={cn(avatarClassName, className)} className={cn(avatarClassName, className)}
style={style} style={style}
> >
<div <div
className={`text-center text-white scale-[0.4]`} className={cn(textClassName, 'text-center text-white scale-[0.4]')}
style={style} style={style}
> >
{name[0].toLocaleUpperCase()} {name[0].toLocaleUpperCase()}
...@@ -42,4 +44,4 @@ const Avatar = ({ ...@@ -42,4 +44,4 @@ const Avatar = ({
) )
} }
export default Avatar export default Avatar
\ No newline at end of file
<svg width="50" height="26" viewBox="0 0 50 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Dify">
<path d="M6.61784 2.064C8.37784 2.064 9.92184 2.408 11.2498 3.096C12.5938 3.784 13.6258 4.768 14.3458 6.048C15.0818 7.312 15.4498 8.784 15.4498 10.464C15.4498 12.144 15.0818 13.616 14.3458 14.88C13.6258 16.128 12.5938 17.096 11.2498 17.784C9.92184 18.472 8.37784 18.816 6.61784 18.816H0.761841V2.064H6.61784ZM6.49784 15.96C8.25784 15.96 9.61784 15.48 10.5778 14.52C11.5378 13.56 12.0178 12.208 12.0178 10.464C12.0178 8.72 11.5378 7.36 10.5778 6.384C9.61784 5.392 8.25784 4.896 6.49784 4.896H4.12184V15.96H6.49784Z" fill="#1D2939"/>
<path d="M20.869 3.936C20.277 3.936 19.781 3.752 19.381 3.384C18.997 3 18.805 2.528 18.805 1.968C18.805 1.408 18.997 0.944 19.381 0.576C19.781 0.192 20.277 0 20.869 0C21.461 0 21.949 0.192 22.333 0.576C22.733 0.944 22.933 1.408 22.933 1.968C22.933 2.528 22.733 3 22.333 3.384C21.949 3.752 21.461 3.936 20.869 3.936ZM22.525 5.52V18.816H19.165V5.52H22.525Z" fill="#1D2939"/>
<path d="M33.1407 8.28H30.8127V18.816H27.4047V8.28H25.8927V5.52H27.4047V4.848C27.4047 3.216 27.8687 2.016 28.7967 1.248C29.7247 0.48 31.1247 0.12 32.9967 0.168001V3C32.1807 2.984 31.6127 3.12 31.2927 3.408C30.9727 3.696 30.8127 4.216 30.8127 4.968V5.52H33.1407V8.28Z" fill="#1D2939"/>
<path d="M49.2381 5.52L41.0061 25.104H37.4301L40.3101 18.48L34.9821 5.52H38.7501L42.1821 14.808L45.6621 5.52H49.2381Z" fill="#1D2939"/>
</g>
</svg>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="github">
<path id="Vector" d="M9 1.125C4.64906 1.125 1.125 4.64906 1.125 9C1.125 12.4847 3.37922 15.428 6.50953 16.4714C6.90328 16.5403 7.05094 16.3041 7.05094 16.0973C7.05094 15.9103 7.04109 15.2902 7.04109 14.6306C5.0625 14.9948 4.55062 14.1483 4.39312 13.7053C4.30453 13.4789 3.92063 12.78 3.58594 12.593C3.31031 12.4453 2.91656 12.0811 3.57609 12.0712C4.19625 12.0614 4.63922 12.6422 4.78688 12.8784C5.49563 14.0695 6.62766 13.7348 7.08047 13.5281C7.14938 13.0163 7.35609 12.6717 7.5825 12.4748C5.83031 12.278 3.99938 11.5987 3.99938 8.58656C3.99938 7.73016 4.30453 7.02141 4.80656 6.47016C4.72781 6.27328 4.45219 5.46609 4.88531 4.38328C4.88531 4.38328 5.54484 4.17656 7.05094 5.19047C7.68094 5.01328 8.35031 4.92469 9.01969 4.92469C9.68906 4.92469 10.3584 5.01328 10.9884 5.19047C12.4945 4.16672 13.1541 4.38328 13.1541 4.38328C13.5872 5.46609 13.3116 6.27328 13.2328 6.47016C13.7348 7.02141 14.04 7.72031 14.04 8.58656C14.04 11.6086 12.1992 12.278 10.447 12.4748C10.7325 12.7209 10.9786 13.1934 10.9786 13.9317C10.9786 14.985 10.9688 15.8316 10.9688 16.0973C10.9688 16.3041 11.1164 16.5502 11.5102 16.4714C13.0735 15.9436 14.432 14.9389 15.3943 13.5986C16.3567 12.2583 16.8746 10.65 16.875 9C16.875 4.64906 13.3509 1.125 9 1.125Z" fill="#24292F"/>
</g>
</svg>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-up-right">
<path id="Icon" d="M4.08325 9.91665L9.91659 4.08331M9.91659 4.08331H4.08325M9.91659 4.08331V9.91665" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-down">
<path id="Icon" d="M3 4.5L6 7.5L9 4.5" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-right">
<path id="Icon" d="M5.25 10.5L8.75 7L5.25 3.5" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="check">
<path id="Icon" d="M13.3334 4L6.00008 11.3333L2.66675 8" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="log-out-01">
<path id="Icon" d="M9.33333 9.91667L12.25 7M12.25 7L9.33333 4.08333M12.25 7H5.25M5.25 1.75H4.55C3.56991 1.75 3.07986 1.75 2.70552 1.94074C2.37623 2.10852 2.10852 2.37623 1.94074 2.70552C1.75 3.07986 1.75 3.56991 1.75 4.55V9.45C1.75 10.4301 1.75 10.9201 1.94074 11.2945C2.10852 11.6238 2.37623 11.8915 2.70552 12.0593C3.07986 12.25 3.56991 12.25 4.55 12.25H5.25" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="terminal-square">
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M8.91927 1H3.08073C2.81716 0.999992 2.58977 0.999984 2.40249 1.01529C2.20481 1.03144 2.00821 1.06709 1.81902 1.16349C1.53677 1.3073 1.3073 1.53677 1.16349 1.81902C1.06709 2.00821 1.03144 2.20481 1.01529 2.40249C0.999984 2.58977 0.999992 2.81714 1 3.08071V8.91927C0.999992 9.18284 0.999984 9.41023 1.01529 9.59752C1.03144 9.79519 1.06709 9.9918 1.16349 10.181C1.3073 10.4632 1.53677 10.6927 1.81902 10.8365C2.00821 10.9329 2.20481 10.9686 2.40249 10.9847C2.58977 11 2.81715 11 3.08072 11H8.91928C9.18285 11 9.41023 11 9.59752 10.9847C9.79519 10.9686 9.9918 10.9329 10.181 10.8365C10.4632 10.6927 10.6927 10.4632 10.8365 10.181C10.9329 9.9918 10.9686 9.79519 10.9847 9.59752C11 9.41023 11 9.18285 11 8.91928V3.08072C11 2.81715 11 2.58977 10.9847 2.40249C10.9686 2.20481 10.9329 2.00821 10.8365 1.81902C10.6927 1.53677 10.4632 1.3073 10.181 1.16349C9.9918 1.06709 9.79519 1.03144 9.59752 1.01529C9.41023 0.999984 9.18284 0.999992 8.91927 1ZM3.85355 4.14645C3.65829 3.95118 3.34171 3.95118 3.14645 4.14645C2.95118 4.34171 2.95118 4.65829 3.14645 4.85355L4.29289 6L3.14645 7.14645C2.95118 7.34171 2.95118 7.65829 3.14645 7.85355C3.34171 8.04882 3.65829 8.04882 3.85355 7.85355L5.35355 6.35355C5.54882 6.15829 5.54882 5.84171 5.35355 5.64645L3.85355 4.14645ZM6.5 7C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H8.5C8.77614 8 9 7.77614 9 7.5C9 7.22386 8.77614 7 8.5 7H6.5Z" fill="#B54708"/>
</g>
</svg>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="beaker-02">
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M4.13856 0.500003H7.8617C7.92126 0.49998 7.99238 0.499953 8.05504 0.505073C8.12765 0.511005 8.23165 0.526227 8.34062 0.581751C8.48174 0.653656 8.59648 0.768392 8.66838 0.909513C8.72391 1.01849 8.73913 1.12248 8.74506 1.19509C8.75018 1.25775 8.75015 1.32888 8.75013 1.38844V2.61157C8.75015 2.67113 8.75018 2.74226 8.74506 2.80492C8.73913 2.87753 8.72391 2.98153 8.66838 3.0905C8.59648 3.23162 8.48174 3.34636 8.34062 3.41826C8.23165 3.47379 8.12765 3.48901 8.05504 3.49494C8.03725 3.49639 8.01877 3.49743 8.00006 3.49817V5.2506C8.00006 5.55312 8.00408 5.61265 8.01723 5.66153C8.03245 5.71807 8.05747 5.7715 8.09117 5.81939C8.1203 5.86078 8.16346 5.90197 8.39586 6.09564L10.2807 7.66627C10.4566 7.81255 10.6116 7.94145 10.7267 8.10509C10.8278 8.24875 10.9029 8.40904 10.9486 8.57867C11.0005 8.7719 11.0003 8.97351 11.0001 9.2023C11.0001 9.39886 11.0002 9.59542 11.0002 9.79198C11.0003 9.98232 11.0005 10.1463 10.9713 10.2927C10.853 10.8877 10.3878 11.3529 9.7928 11.4712C9.64637 11.5003 9.48246 11.5002 9.29211 11.5001H2.70822C2.51787 11.5002 2.35396 11.5003 2.20753 11.4712C1.98473 11.4269 1.78014 11.334 1.60515 11.2038C1.42854 11.0725 1.28221 10.9034 1.17753 10.7077C1.10892 10.5796 1.05831 10.4401 1.02899 10.2927C0.999862 10.1463 0.999992 9.98233 1.00014 9.79199C1.00014 9.59542 1.00006 9.39886 1.00003 9.20229C0.999794 8.97351 0.999584 8.7719 1.05157 8.57867C1.09721 8.40904 1.17229 8.24875 1.27338 8.10509C1.38855 7.94145 1.54356 7.81255 1.71947 7.66627L3.60427 6.09564C3.83667 5.90197 3.87983 5.86078 3.90896 5.81939C3.94266 5.7715 3.96768 5.71807 3.9829 5.66153C3.99605 5.61265 4.00006 5.55312 4.00006 5.2506V3.49817C3.9814 3.49743 3.96297 3.49639 3.94521 3.49494C3.8726 3.48901 3.76861 3.47379 3.65964 3.41826C3.51851 3.34636 3.40378 3.23162 3.33187 3.0905C3.27635 2.98153 3.26113 2.87753 3.25519 2.80492C3.25008 2.74226 3.2501 2.67113 3.25013 2.61158V1.38844C3.2501 1.32888 3.25008 1.25775 3.25519 1.19509C3.26113 1.12248 3.27635 1.01849 3.33187 0.909513C3.40378 0.768392 3.51851 0.653656 3.65964 0.581751C3.76861 0.526227 3.8726 0.511005 3.94521 0.505073C4.00787 0.499953 4.079 0.49998 4.13856 0.500003ZM9.11909 8.00004H2.88104L4.28066 6.83373C4.45657 6.68745 4.61158 6.55855 4.72675 6.39491C4.82784 6.25125 4.90292 6.09096 4.94856 5.92133C5.00054 5.7281 5.00033 5.52649 5.0001 5.29771L5.00006 3.50001H7.00006L7.00003 5.29771C6.99979 5.52649 6.99958 5.7281 7.05157 5.92133C7.09721 6.09096 7.17229 6.25125 7.27338 6.39491C7.38855 6.55855 7.54356 6.68745 7.71947 6.83373L9.11909 8.00004Z" fill="#0E7090"/>
</g>
</svg>
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "50",
"height": "26",
"viewBox": "0 0 50 26",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Dify"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.61784 2.064C8.37784 2.064 9.92184 2.408 11.2498 3.096C12.5938 3.784 13.6258 4.768 14.3458 6.048C15.0818 7.312 15.4498 8.784 15.4498 10.464C15.4498 12.144 15.0818 13.616 14.3458 14.88C13.6258 16.128 12.5938 17.096 11.2498 17.784C9.92184 18.472 8.37784 18.816 6.61784 18.816H0.761841V2.064H6.61784ZM6.49784 15.96C8.25784 15.96 9.61784 15.48 10.5778 14.52C11.5378 13.56 12.0178 12.208 12.0178 10.464C12.0178 8.72 11.5378 7.36 10.5778 6.384C9.61784 5.392 8.25784 4.896 6.49784 4.896H4.12184V15.96H6.49784Z",
"fill": "#1D2939"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M20.869 3.936C20.277 3.936 19.781 3.752 19.381 3.384C18.997 3 18.805 2.528 18.805 1.968C18.805 1.408 18.997 0.944 19.381 0.576C19.781 0.192 20.277 0 20.869 0C21.461 0 21.949 0.192 22.333 0.576C22.733 0.944 22.933 1.408 22.933 1.968C22.933 2.528 22.733 3 22.333 3.384C21.949 3.752 21.461 3.936 20.869 3.936ZM22.525 5.52V18.816H19.165V5.52H22.525Z",
"fill": "#1D2939"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M33.1407 8.28H30.8127V18.816H27.4047V8.28H25.8927V5.52H27.4047V4.848C27.4047 3.216 27.8687 2.016 28.7967 1.248C29.7247 0.48 31.1247 0.12 32.9967 0.168001V3C32.1807 2.984 31.6127 3.12 31.2927 3.408C30.9727 3.696 30.8127 4.216 30.8127 4.968V5.52H33.1407V8.28Z",
"fill": "#1D2939"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M49.2381 5.52L41.0061 25.104H37.4301L40.3101 18.48L34.9821 5.52H38.7501L42.1821 14.808L45.6621 5.52H49.2381Z",
"fill": "#1D2939"
},
"children": []
}
]
}
]
},
"name": "Dify"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Dify.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "18",
"height": "18",
"viewBox": "0 0 18 18",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "github"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"d": "M9 1.125C4.64906 1.125 1.125 4.64906 1.125 9C1.125 12.4847 3.37922 15.428 6.50953 16.4714C6.90328 16.5403 7.05094 16.3041 7.05094 16.0973C7.05094 15.9103 7.04109 15.2902 7.04109 14.6306C5.0625 14.9948 4.55062 14.1483 4.39312 13.7053C4.30453 13.4789 3.92063 12.78 3.58594 12.593C3.31031 12.4453 2.91656 12.0811 3.57609 12.0712C4.19625 12.0614 4.63922 12.6422 4.78688 12.8784C5.49563 14.0695 6.62766 13.7348 7.08047 13.5281C7.14938 13.0163 7.35609 12.6717 7.5825 12.4748C5.83031 12.278 3.99938 11.5987 3.99938 8.58656C3.99938 7.73016 4.30453 7.02141 4.80656 6.47016C4.72781 6.27328 4.45219 5.46609 4.88531 4.38328C4.88531 4.38328 5.54484 4.17656 7.05094 5.19047C7.68094 5.01328 8.35031 4.92469 9.01969 4.92469C9.68906 4.92469 10.3584 5.01328 10.9884 5.19047C12.4945 4.16672 13.1541 4.38328 13.1541 4.38328C13.5872 5.46609 13.3116 6.27328 13.2328 6.47016C13.7348 7.02141 14.04 7.72031 14.04 8.58656C14.04 11.6086 12.1992 12.278 10.447 12.4748C10.7325 12.7209 10.9786 13.1934 10.9786 13.9317C10.9786 14.985 10.9688 15.8316 10.9688 16.0973C10.9688 16.3041 11.1164 16.5502 11.5102 16.4714C13.0735 15.9436 14.432 14.9389 15.3943 13.5986C16.3567 12.2583 16.8746 10.65 16.875 9C16.875 4.64906 13.3509 1.125 9 1.125Z",
"fill": "#24292F"
},
"children": []
}
]
}
]
},
"name": "Github"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Github.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as Dify } from './Dify'
export { default as Github } from './Github'
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "arrow-up-right"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M4.08325 9.91665L9.91659 4.08331M9.91659 4.08331H4.08325M9.91659 4.08331V9.91665",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "ArrowUpRight"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ArrowUpRight.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "chevron-down"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M3 4.5L6 7.5L9 4.5",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "ChevronDown"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ChevronDown.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "chevron-right"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M5.25 10.5L8.75 7L5.25 3.5",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "ChevronRight"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ChevronRight.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as ArrowNarrowLeft } from './ArrowNarrowLeft' export { default as ArrowNarrowLeft } from './ArrowNarrowLeft'
export { default as ArrowUpRight } from './ArrowUpRight'
export { default as ChevronDown } from './ChevronDown'
export { default as ChevronRight } from './ChevronRight'
export { default as RefreshCw05 } from './RefreshCw05' export { default as RefreshCw05 } from './RefreshCw05'
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "check"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M13.3334 4L6.00008 11.3333L2.66675 8",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Check"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Check.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "log-out-01"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M9.33333 9.91667L12.25 7M12.25 7L9.33333 4.08333M12.25 7H5.25M5.25 1.75H4.55C3.56991 1.75 3.07986 1.75 2.70552 1.94074C2.37623 2.10852 2.10852 2.37623 1.94074 2.70552C1.75 3.07986 1.75 3.56991 1.75 4.55V9.45C1.75 10.4301 1.75 10.9201 1.94074 11.2945C2.10852 11.6238 2.37623 11.8915 2.70552 12.0593C3.07986 12.25 3.56991 12.25 4.55 12.25H5.25",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "LogOut01"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './LogOut01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as LinkExternal02 } from './LinkExternal02' export { default as LinkExternal02 } from './LinkExternal02'
export { default as Check } from './Check'
export { default as Loading02 } from './Loading02' export { default as Loading02 } from './Loading02'
export { default as LogOut01 } from './LogOut01'
export { default as Trash03 } from './Trash03' export { default as Trash03 } from './Trash03'
export { default as XClose } from './XClose' export { default as XClose } from './XClose'
export { default as X } from './X' export { default as X } from './X'
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "terminal-square"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M8.91927 1H3.08073C2.81716 0.999992 2.58977 0.999984 2.40249 1.01529C2.20481 1.03144 2.00821 1.06709 1.81902 1.16349C1.53677 1.3073 1.3073 1.53677 1.16349 1.81902C1.06709 2.00821 1.03144 2.20481 1.01529 2.40249C0.999984 2.58977 0.999992 2.81714 1 3.08071V8.91927C0.999992 9.18284 0.999984 9.41023 1.01529 9.59752C1.03144 9.79519 1.06709 9.9918 1.16349 10.181C1.3073 10.4632 1.53677 10.6927 1.81902 10.8365C2.00821 10.9329 2.20481 10.9686 2.40249 10.9847C2.58977 11 2.81715 11 3.08072 11H8.91928C9.18285 11 9.41023 11 9.59752 10.9847C9.79519 10.9686 9.9918 10.9329 10.181 10.8365C10.4632 10.6927 10.6927 10.4632 10.8365 10.181C10.9329 9.9918 10.9686 9.79519 10.9847 9.59752C11 9.41023 11 9.18285 11 8.91928V3.08072C11 2.81715 11 2.58977 10.9847 2.40249C10.9686 2.20481 10.9329 2.00821 10.8365 1.81902C10.6927 1.53677 10.4632 1.3073 10.181 1.16349C9.9918 1.06709 9.79519 1.03144 9.59752 1.01529C9.41023 0.999984 9.18284 0.999992 8.91927 1ZM3.85355 4.14645C3.65829 3.95118 3.34171 3.95118 3.14645 4.14645C2.95118 4.34171 2.95118 4.65829 3.14645 4.85355L4.29289 6L3.14645 7.14645C2.95118 7.34171 2.95118 7.65829 3.14645 7.85355C3.34171 8.04882 3.65829 8.04882 3.85355 7.85355L5.35355 6.35355C5.54882 6.15829 5.54882 5.84171 5.35355 5.64645L3.85355 4.14645ZM6.5 7C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H8.5C8.77614 8 9 7.77614 9 7.5C9 7.22386 8.77614 7 8.5 7H6.5Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "TerminalSquare"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './TerminalSquare.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
...@@ -2,3 +2,4 @@ export { default as Container } from './Container' ...@@ -2,3 +2,4 @@ export { default as Container } from './Container'
export { default as Database02 } from './Database02' export { default as Database02 } from './Database02'
export { default as Database03 } from './Database03' export { default as Database03 } from './Database03'
export { default as PuzzlePiece01 } from './PuzzlePiece01' export { default as PuzzlePiece01 } from './PuzzlePiece01'
export { default as TerminalSquare } from './TerminalSquare'
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "beaker-02"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M4.13856 0.500003H7.8617C7.92126 0.49998 7.99238 0.499953 8.05504 0.505073C8.12765 0.511005 8.23165 0.526227 8.34062 0.581751C8.48174 0.653656 8.59648 0.768392 8.66838 0.909513C8.72391 1.01849 8.73913 1.12248 8.74506 1.19509C8.75018 1.25775 8.75015 1.32888 8.75013 1.38844V2.61157C8.75015 2.67113 8.75018 2.74226 8.74506 2.80492C8.73913 2.87753 8.72391 2.98153 8.66838 3.0905C8.59648 3.23162 8.48174 3.34636 8.34062 3.41826C8.23165 3.47379 8.12765 3.48901 8.05504 3.49494C8.03725 3.49639 8.01877 3.49743 8.00006 3.49817V5.2506C8.00006 5.55312 8.00408 5.61265 8.01723 5.66153C8.03245 5.71807 8.05747 5.7715 8.09117 5.81939C8.1203 5.86078 8.16346 5.90197 8.39586 6.09564L10.2807 7.66627C10.4566 7.81255 10.6116 7.94145 10.7267 8.10509C10.8278 8.24875 10.9029 8.40904 10.9486 8.57867C11.0005 8.7719 11.0003 8.97351 11.0001 9.2023C11.0001 9.39886 11.0002 9.59542 11.0002 9.79198C11.0003 9.98232 11.0005 10.1463 10.9713 10.2927C10.853 10.8877 10.3878 11.3529 9.7928 11.4712C9.64637 11.5003 9.48246 11.5002 9.29211 11.5001H2.70822C2.51787 11.5002 2.35396 11.5003 2.20753 11.4712C1.98473 11.4269 1.78014 11.334 1.60515 11.2038C1.42854 11.0725 1.28221 10.9034 1.17753 10.7077C1.10892 10.5796 1.05831 10.4401 1.02899 10.2927C0.999862 10.1463 0.999992 9.98233 1.00014 9.79199C1.00014 9.59542 1.00006 9.39886 1.00003 9.20229C0.999794 8.97351 0.999584 8.7719 1.05157 8.57867C1.09721 8.40904 1.17229 8.24875 1.27338 8.10509C1.38855 7.94145 1.54356 7.81255 1.71947 7.66627L3.60427 6.09564C3.83667 5.90197 3.87983 5.86078 3.90896 5.81939C3.94266 5.7715 3.96768 5.71807 3.9829 5.66153C3.99605 5.61265 4.00006 5.55312 4.00006 5.2506V3.49817C3.9814 3.49743 3.96297 3.49639 3.94521 3.49494C3.8726 3.48901 3.76861 3.47379 3.65964 3.41826C3.51851 3.34636 3.40378 3.23162 3.33187 3.0905C3.27635 2.98153 3.26113 2.87753 3.25519 2.80492C3.25008 2.74226 3.2501 2.67113 3.25013 2.61158V1.38844C3.2501 1.32888 3.25008 1.25775 3.25519 1.19509C3.26113 1.12248 3.27635 1.01849 3.33187 0.909513C3.40378 0.768392 3.51851 0.653656 3.65964 0.581751C3.76861 0.526227 3.8726 0.511005 3.94521 0.505073C4.00787 0.499953 4.079 0.49998 4.13856 0.500003ZM9.11909 8.00004H2.88104L4.28066 6.83373C4.45657 6.68745 4.61158 6.55855 4.72675 6.39491C4.82784 6.25125 4.90292 6.09096 4.94856 5.92133C5.00054 5.7281 5.00033 5.52649 5.0001 5.29771L5.00006 3.50001H7.00006L7.00003 5.29771C6.99979 5.52649 6.99958 5.7281 7.05157 5.92133C7.09721 6.09096 7.17229 6.25125 7.27338 6.39491C7.38855 6.55855 7.54356 6.68745 7.71947 6.83373L9.11909 8.00004Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Beaker02"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Beaker02.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as Beaker02 } from './Beaker02'
...@@ -31,6 +31,7 @@ export type ISelectProps = { ...@@ -31,6 +31,7 @@ export type ISelectProps = {
allowSearch?: boolean allowSearch?: boolean
bgClassName?: string bgClassName?: string
placeholder?: string placeholder?: string
overlayClassName?: string
} }
const Select: FC<ISelectProps> = ({ const Select: FC<ISelectProps> = ({
className, className,
...@@ -40,6 +41,7 @@ const Select: FC<ISelectProps> = ({ ...@@ -40,6 +41,7 @@ const Select: FC<ISelectProps> = ({
onSelect, onSelect,
allowSearch = true, allowSearch = true,
bgClassName = 'bg-gray-100', bgClassName = 'bg-gray-100',
overlayClassName,
}) => { }) => {
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
...@@ -48,9 +50,9 @@ const Select: FC<ISelectProps> = ({ ...@@ -48,9 +50,9 @@ const Select: FC<ISelectProps> = ({
useEffect(() => { useEffect(() => {
let defaultSelect = null let defaultSelect = null
const existed = items.find((item: Item) => item.value === defaultValue) const existed = items.find((item: Item) => item.value === defaultValue)
if (existed) { if (existed)
defaultSelect = existed defaultSelect = existed
}
setSelectedItem(defaultSelect) setSelectedItem(defaultSelect)
}, [defaultValue]) }, [defaultValue])
...@@ -104,7 +106,7 @@ const Select: FC<ISelectProps> = ({ ...@@ -104,7 +106,7 @@ const Select: FC<ISelectProps> = ({
</div> </div>
{filteredItems.length > 0 && ( {filteredItems.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm"> <Combobox.Options className={`absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm ${overlayClassName}`}>
{filteredItems.map((item: Item) => ( {filteredItems.map((item: Item) => (
<Combobox.Option <Combobox.Option
key={item.value} key={item.value}
...@@ -155,9 +157,9 @@ const SimpleSelect: FC<ISelectProps> = ({ ...@@ -155,9 +157,9 @@ const SimpleSelect: FC<ISelectProps> = ({
useEffect(() => { useEffect(() => {
let defaultSelect = null let defaultSelect = null
const existed = items.find((item: Item) => item.value === defaultValue) const existed = items.find((item: Item) => item.value === defaultValue)
if (existed) { if (existed)
defaultSelect = existed defaultSelect = existed
}
setSelectedItem(defaultSelect) setSelectedItem(defaultSelect)
}, [defaultValue]) }, [defaultValue])
...@@ -173,7 +175,7 @@ const SimpleSelect: FC<ISelectProps> = ({ ...@@ -173,7 +175,7 @@ const SimpleSelect: FC<ISelectProps> = ({
> >
<div className={`relative h-9 ${wrapperClassName}`}> <div className={`relative h-9 ${wrapperClassName}`}>
<Listbox.Button className={`w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer ${className}`}> <Listbox.Button className={`w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer ${className}`}>
<span className={classNames("block truncate text-left", !selectedItem?.name && 'text-gray-400')}>{selectedItem?.name ?? localPlaceholder}</span> <span className={classNames('block truncate text-left', !selectedItem?.name && 'text-gray-400')}>{selectedItem?.name ?? localPlaceholder}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon <ChevronDownIcon
className="h-5 w-5 text-gray-400" className="h-5 w-5 text-gray-400"
......
'use client'
import classNames from 'classnames'
import { usePathname } from 'next/navigation'
import s from './index.module.css'
import { useAppContext } from '@/context/app-context'
type HeaderWrapperProps = {
children: React.ReactNode
}
const HeaderWrapper = ({
children,
}: HeaderWrapperProps) => {
const pathname = usePathname()
const { langeniusVersionInfo } = useAppContext()
const isBordered = ['/apps', '/datasets'].includes(pathname)
return (
<div className={classNames(
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
s.header,
isBordered ? 'border-b border-gray-200' : '',
)}
>
<div className={classNames(
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
)}>
{children}
</div>
</div>
)
}
export default HeaderWrapper
.logo-icon { .logo-icon {
background: url(../assets/logo-icon.png) center center no-repeat; background: url(../assets/logo-icon.png) center center no-repeat;
background-size: 32px; background-size: 32px;
box-shadow: 0px 4px 6px -1px rgba(0, 0, 0, 0.05), 0px 2px 4px -2px rgba(0, 0, 0, 0.05); box-shadow: 0px 2px 4px -2px rgba(0, 0, 0, 0.05), 0px 4px 6px -1px rgba(0, 0, 0, 0.05);
} }
.logo-text { .logo-text {
......
'use client' 'use client'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { XMarkIcon } from '@heroicons/react/24/outline'
import classNames from 'classnames' import classNames from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import s from './index.module.css' import s from './index.module.css'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import { Dify } from '@/app/components/base/icons/src/public/common'
import type { LangGeniusVersionResponse } from '@/models/common' import type { LangGeniusVersionResponse } from '@/models/common'
import { IS_CE_EDITION } from '@/config' import { IS_CE_EDITION } from '@/config'
...@@ -30,16 +31,15 @@ export default function AccountAbout({ ...@@ -30,16 +31,15 @@ export default function AccountAbout({
className={s.modal} className={s.modal}
> >
<div className='relative'> <div className='relative'>
<XMarkIcon className='absolute top-0 -right-2 w-4 h-4 cursor-pointer' onClick={onCancel} /> <div className='absolute -top-2 -right-4 flex justify-center items-center w-8 h-8 cursor-pointer' onClick={onCancel}>
<XClose className='w-4 h-4 text-gray-500' />
</div>
<div> <div>
<div className={classNames( <div className={classNames(
s['logo-icon'], s['logo-icon'],
'mx-auto mb-3 w-12 h-12 bg-white rounded-xl border border-gray-200', 'mx-auto mb-3 w-12 h-12 bg-white rounded-xl border-[0.5px] border-gray-200',
)} />
<div className={classNames(
s['logo-text'],
'mx-auto mb-2',
)} /> )} />
<Dify className='mx-auto mb-2' />
<div className='mb-3 text-center text-xs font-normal text-gray-500'>Version {langeniusVersionInfo?.current_version}</div> <div className='mb-3 text-center text-xs font-normal text-gray-500'>Version {langeniusVersionInfo?.current_version}</div>
<div className='mb-4 text-center text-xs font-normal text-gray-700'> <div className='mb-4 text-center text-xs font-normal text-gray-700'>
<div>© 2023 LangGenius, Inc., Contributors.</div> <div>© 2023 LangGenius, Inc., Contributors.</div>
...@@ -55,7 +55,7 @@ export default function AccountAbout({ ...@@ -55,7 +55,7 @@ export default function AccountAbout({
</div> </div>
</div> </div>
</div> </div>
<div className='mb-4 h-0 border-[0.5px] border-gray-200' /> <div className='mb-4 -mx-8 h-[0.5px] bg-gray-200' />
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<div className='text-xs font-medium text-gray-800'> <div className='text-xs font-medium text-gray-800'>
{ {
......
...@@ -5,27 +5,23 @@ import { useRouter } from 'next/navigation' ...@@ -5,27 +5,23 @@ import { useRouter } from 'next/navigation'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import classNames from 'classnames' import classNames from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { ArrowRightOnRectangleIcon, ArrowTopRightOnSquareIcon, ChevronDownIcon } from '@heroicons/react/24/solid'
import { Menu, Transition } from '@headlessui/react' import { Menu, Transition } from '@headlessui/react'
import Indicator from '../indicator' import Indicator from '../indicator'
import AccountSetting from '../account-setting' import AccountSetting from '../account-setting'
import AccountAbout from '../account-about' import AccountAbout from '../account-about'
import WorkplaceSelector from './workplace-selector' import WorkplaceSelector from './workplace-selector'
import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import { KeyValidatorContextProvider } from '@/context/key-validator' import { KeyValidatorContextProvider } from '@/context/key-validator'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import Avatar from '@/app/components/base/avatar' import Avatar from '@/app/components/base/avatar'
import { logout } from '@/service/common' import { logout } from '@/service/common'
import { useAppContext } from '@/context/app-context'
import { ArrowUpRight, ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
type IAppSelectorProps = { export default function AppSelector() {
userProfile: UserProfileResponse
langeniusVersionInfo: LangGeniusVersionResponse
}
export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppSelectorProps) {
const itemClassName = ` const itemClassName = `
flex items-center w-full h-10 px-3 text-gray-700 text-[14px] flex items-center w-full h-9 px-3 text-gray-700 text-[14px]
rounded-lg font-normal hover:bg-gray-100 cursor-pointer rounded-lg font-normal hover:bg-gray-50 cursor-pointer
` `
const router = useRouter() const router = useRouter()
const [settingVisible, setSettingVisible] = useState(false) const [settingVisible, setSettingVisible] = useState(false)
...@@ -33,6 +29,7 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS ...@@ -33,6 +29,7 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const { t } = useTranslation() const { t } = useTranslation()
const { userProfile, langeniusVersionInfo } = useAppContext()
const handleLogout = async () => { const handleLogout = async () => {
await logout({ await logout({
...@@ -45,90 +42,94 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS ...@@ -45,90 +42,94 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
return ( return (
<div className=""> <div className="">
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div> {
<Menu.Button ({ open }) => (
className=" <>
inline-flex items-center h-[38px] <div>
rounded-xl pl-2 pr-2.5 text-[14px] font-normal <Menu.Button
text-gray-800 hover:bg-gray-200 className={`
" inline-flex items-center
> rounded-[20px] py-1 pr-2.5 pl-1 text-sm
<Avatar name={userProfile.name} className='mr-2' /> text-gray-700 hover:bg-gray-200
{userProfile.name} ${open && 'bg-gray-200'}
<ChevronDownIcon `}
className="w-3 h-3 ml-1"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="
absolute right-0 mt-1.5 w-60 max-w-80
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
"
>
<Menu.Item>
<div className='flex flex-nowrap items-center px-4 py-[13px]'>
<Avatar name={userProfile.name} size={36} className='mr-3' />
<div className='grow'>
<div className='leading-5 font-normal text-[14px] text-gray-800 break-all'>{userProfile.name}</div>
<div className='leading-[18px] text-xs font-normal text-gray-500 break-all'>{userProfile.email}</div>
</div>
</div>
</Menu.Item>
<div className='px-1 py-1'>
<div className='mt-2 px-3 text-xs font-medium text-gray-500'>{t('common.userProfile.workspace')}</div>
<WorkplaceSelector />
</div>
<div className="px-1 py-1">
<Menu.Item>
<div className={itemClassName} onClick={() => setSettingVisible(true)}>
<div>{t('common.userProfile.settings')}</div>
</div>
</Menu.Item>
<Menu.Item>
<Link
className={classNames(itemClassName, 'group justify-between')}
href={
locale === 'zh-Hans' ? 'https://docs.dify.ai/v/zh-hans/' : 'https://docs.dify.ai/'
}
target='_blank'>
<div>{t('common.userProfile.helpCenter')}</div>
<ArrowTopRightOnSquareIcon className='hidden w-4 h-4 group-hover:flex' />
</Link>
</Menu.Item>
<Menu.Item>
<div className={classNames(itemClassName, 'justify-between')} onClick={() => setAboutVisible(true)}>
<div>{t('common.userProfile.about')}</div>
<div className='flex items-center'>
<div className='mr-2 text-xs font-normal text-gray-500'>{langeniusVersionInfo.current_version}</div>
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
</div>
</Menu.Item>
</div>
<Menu.Item>
<div className='p-1' onClick={() => handleLogout()}>
<div
className='flex items-center justify-between h-12 px-3 rounded-lg cursor-pointer group hover:bg-gray-100'
> >
<div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div> <Avatar name={userProfile.name} className='mr-2' size={32} />
<ArrowRightOnRectangleIcon className='hidden w-4 h-4 group-hover:flex' /> {userProfile.name}
</div> <ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
</Menu.Button>
</div> </div>
</Menu.Item> <Transition
</Menu.Items> as={Fragment}
</Transition> enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="
absolute right-0 mt-1.5 w-60 max-w-80
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
"
>
<Menu.Item>
<div className='flex flex-nowrap items-center px-4 py-[13px]'>
<Avatar name={userProfile.name} size={36} className='mr-3' />
<div className='grow'>
<div className='leading-5 font-normal text-[14px] text-gray-800 break-all'>{userProfile.name}</div>
<div className='leading-[18px] text-xs font-normal text-gray-500 break-all'>{userProfile.email}</div>
</div>
</div>
</Menu.Item>
<div className='px-1 py-1'>
<div className='mt-2 px-3 text-xs font-medium text-gray-500'>{t('common.userProfile.workspace')}</div>
<WorkplaceSelector />
</div>
<div className="px-1 py-1">
<Menu.Item>
<div className={itemClassName} onClick={() => setSettingVisible(true)}>
<div>{t('common.userProfile.settings')}</div>
</div>
</Menu.Item>
<Menu.Item>
<Link
className={classNames(itemClassName, 'group justify-between')}
href={
locale === 'zh-Hans' ? 'https://docs.dify.ai/v/zh-hans/' : 'https://docs.dify.ai/'
}
target='_blank'>
<div>{t('common.userProfile.helpCenter')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
</Link>
</Menu.Item>
<Menu.Item>
<div className={classNames(itemClassName, 'justify-between')} onClick={() => setAboutVisible(true)}>
<div>{t('common.userProfile.about')}</div>
<div className='flex items-center'>
<div className='mr-2 text-xs font-normal text-gray-500'>{langeniusVersionInfo.current_version}</div>
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
</div>
</Menu.Item>
</div>
<Menu.Item>
<div className='p-1' onClick={() => handleLogout()}>
<div
className='flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-gray-50'
>
<div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div>
<LogOut01 className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
</div>
</div>
</Menu.Item>
</Menu.Items>
</Transition>
</>
)
}
</Menu> </Menu>
{ {
settingVisible && ( settingVisible && (
......
import { Fragment } from 'react' import { Fragment } from 'react'
import { switchWorkspace } from '@/service/common'
import { Menu, Transition } from '@headlessui/react'
import { ChevronRightIcon, CheckIcon } from '@heroicons/react/24/outline'
import cn from 'classnames'
import s from './index.module.css'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { ToastContext } from '@/app/components/base/toast'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { Menu, Transition } from '@headlessui/react'
import cn from 'classnames'
import s from './index.module.css'
import { switchWorkspace } from '@/service/common'
import { useWorkspacesContext } from '@/context/workspace-context' import { useWorkspacesContext } from '@/context/workspace-context'
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import { ToastContext } from '@/app/components/base/toast'
const itemClassName = ` const itemClassName = `
flex items-center px-3 py-2 h-10 cursor-pointer flex items-center px-3 py-2 h-10 cursor-pointer
` `
const itemIconClassName = ` const itemIconClassName = `
shrink-0 mr-2 w-6 h-6 bg-[#EFF4FF] rounded-md shrink-0 mr-2 flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600
` `
const itemNameClassName = ` const itemNameClassName = `
grow mr-2 text-sm text-gray-700 text-left grow mr-2 text-sm text-gray-700 text-left
...@@ -32,12 +33,12 @@ const WorkplaceSelector = () => { ...@@ -32,12 +33,12 @@ const WorkplaceSelector = () => {
const handleSwitchWorkspace = async (tenant_id: string) => { const handleSwitchWorkspace = async (tenant_id: string) => {
try { try {
await switchWorkspace({ url: `/workspaces/switch`, body: { tenant_id } }) await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
router.replace('/apps') router.replace('/apps')
} catch (e) { }
catch (e) {
notify({ type: 'error', message: t('common.provider.saveFailed') }) notify({ type: 'error', message: t('common.provider.saveFailed') })
} finally {
} }
} }
...@@ -49,12 +50,12 @@ const WorkplaceSelector = () => { ...@@ -49,12 +50,12 @@ const WorkplaceSelector = () => {
<Menu.Button className={cn( <Menu.Button className={cn(
` `
${itemClassName} w-full ${itemClassName} w-full
group hover:bg-gray-50 cursor-pointer ${open && 'bg-gray-50'} group hover:bg-gray-50 cursor-pointer ${open && 'bg-gray-50'} rounded-lg
` `,
)}> )}>
<div className={itemIconClassName} /> <div className={itemIconClassName}>{currentWrokspace?.name[0].toLocaleUpperCase()}</div>
<div className={`${itemNameClassName} truncate`}>{currentWrokspace?.name}</div> <div className={`${itemNameClassName} truncate`}>{currentWrokspace?.name}</div>
<ChevronRightIcon className='shrink-0 w-[14px] h-[14px]' /> <ChevronRight className='shrink-0 w-[14px] h-[14px] text-gray-500' />
</Menu.Button> </Menu.Button>
<Transition <Transition
as={Fragment} as={Fragment}
...@@ -71,16 +72,16 @@ const WorkplaceSelector = () => { ...@@ -71,16 +72,16 @@ const WorkplaceSelector = () => {
absolute top-[1px] min-w-[200px] z-10 bg-white border-[0.5px] border-gray-200 absolute top-[1px] min-w-[200px] z-10 bg-white border-[0.5px] border-gray-200
divide-y divide-gray-100 origin-top-right rounded-xl divide-y divide-gray-100 origin-top-right rounded-xl
`, `,
s.popup s.popup,
)} )}
> >
<div className="px-1 py-1"> <div className="px-1 py-1">
{ {
workspaces.map(workspace => ( workspaces.map(workspace => (
<div className={itemClassName} key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}> <div className={itemClassName} key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
<div className={itemIconClassName} /> <div className={itemIconClassName}>{workspace.name[0].toLocaleUpperCase()}</div>
<div className={itemNameClassName}>{workspace.name}</div> <div className={itemNameClassName}>{workspace.name}</div>
{workspace.current && <CheckIcon className={itemCheckClassName} />} {workspace.current && <Check className={itemCheckClassName} />}
</div> </div>
)) ))
} }
...@@ -94,4 +95,4 @@ const WorkplaceSelector = () => { ...@@ -94,4 +95,4 @@ const WorkplaceSelector = () => {
) )
} }
export default WorkplaceSelector export default WorkplaceSelector
\ No newline at end of file
'use client'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams, usePathname } from 'next/navigation' import { useParams, usePathname } from 'next/navigation'
......
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.13856 0.500003H7.8617C7.92126 0.49998 7.99238 0.499953 8.05504 0.505073C8.12765 0.511005 8.23165 0.526227 8.34062 0.581751C8.48174 0.653656 8.59648 0.768392 8.66838 0.909513C8.72391 1.01849 8.73913 1.12248 8.74506 1.19509C8.75018 1.25775 8.75015 1.32888 8.75013 1.38844V2.61157C8.75015 2.67113 8.75018 2.74226 8.74506 2.80492C8.73913 2.87753 8.72391 2.98153 8.66838 3.0905C8.59648 3.23162 8.48174 3.34636 8.34062 3.41826C8.23165 3.47379 8.12765 3.48901 8.05504 3.49494C8.03725 3.49639 8.01877 3.49743 8.00006 3.49817V5.2506C8.00006 5.55312 8.00408 5.61265 8.01723 5.66153C8.03245 5.71807 8.05747 5.7715 8.09117 5.81939C8.1203 5.86078 8.16346 5.90197 8.39586 6.09564L10.2807 7.66627C10.4566 7.81255 10.6116 7.94145 10.7267 8.10509C10.8278 8.24875 10.9029 8.40904 10.9486 8.57867C11.0005 8.7719 11.0003 8.97351 11.0001 9.2023C11.0001 9.39886 11.0002 9.59542 11.0002 9.79198C11.0003 9.98232 11.0005 10.1463 10.9713 10.2927C10.853 10.8877 10.3878 11.3529 9.7928 11.4712C9.64637 11.5003 9.48246 11.5002 9.29211 11.5001H2.70822C2.51787 11.5002 2.35396 11.5003 2.20753 11.4712C1.98473 11.4269 1.78014 11.334 1.60515 11.2038C1.42854 11.0725 1.28221 10.9034 1.17753 10.7077C1.10892 10.5796 1.05831 10.4401 1.02899 10.2927C0.999862 10.1463 0.999992 9.98233 1.00014 9.79199C1.00014 9.59542 1.00006 9.39886 1.00003 9.20229C0.999794 8.97351 0.999584 8.7719 1.05157 8.57867C1.09721 8.40904 1.17229 8.24875 1.27338 8.10509C1.38855 7.94145 1.54356 7.81255 1.71947 7.66627L3.60427 6.09564C3.83667 5.90197 3.87983 5.86078 3.90896 5.81939C3.94266 5.7715 3.96768 5.71807 3.9829 5.66153C3.99605 5.61265 4.00006 5.55312 4.00006 5.2506V3.49817C3.9814 3.49743 3.96297 3.49639 3.94521 3.49494C3.8726 3.48901 3.76861 3.47379 3.65964 3.41826C3.51851 3.34636 3.40378 3.23162 3.33187 3.0905C3.27635 2.98153 3.26113 2.87753 3.25519 2.80492C3.25008 2.74226 3.2501 2.67113 3.25013 2.61158V1.38844C3.2501 1.32888 3.25008 1.25775 3.25519 1.19509C3.26113 1.12248 3.27635 1.01849 3.33187 0.909513C3.40378 0.768392 3.51851 0.653656 3.65964 0.581751C3.76861 0.526227 3.8726 0.511005 3.94521 0.505073C4.00787 0.499953 4.079 0.49998 4.13856 0.500003ZM9.11909 8.00004H2.88104L4.28066 6.83373C4.45657 6.68745 4.61158 6.55855 4.72675 6.39491C4.82784 6.25125 4.90292 6.09096 4.94856 5.92133C5.00054 5.7281 5.00033 5.52649 5.0001 5.29771L5.00006 3.50001H7.00006L7.00003 5.29771C6.99979 5.52649 6.99958 5.7281 7.05157 5.92133C7.09721 6.09096 7.17229 6.25125 7.27338 6.39491C7.38855 6.55855 7.54356 6.68745 7.71947 6.83373L9.11909 8.00004Z" fill="#0E7090"/>
</svg>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1.125C4.64906 1.125 1.125 4.64906 1.125 9C1.125 12.4847 3.37922 15.428 6.50953 16.4714C6.90328 16.5403 7.05094 16.3041 7.05094 16.0973C7.05094 15.9103 7.04109 15.2902 7.04109 14.6306C5.0625 14.9948 4.55062 14.1483 4.39312 13.7053C4.30453 13.4789 3.92062 12.78 3.58594 12.593C3.31031 12.4453 2.91656 12.0811 3.57609 12.0712C4.19625 12.0614 4.63922 12.6422 4.78688 12.8784C5.49563 14.0695 6.62766 13.7348 7.08047 13.5281C7.14937 13.0163 7.35609 12.6717 7.5825 12.4748C5.83031 12.278 3.99938 11.5988 3.99938 8.58656C3.99938 7.73016 4.30453 7.02141 4.80656 6.47016C4.72781 6.27328 4.45219 5.46609 4.88531 4.38328C4.88531 4.38328 5.54484 4.17656 7.05094 5.19047C7.68094 5.01328 8.35031 4.92469 9.01969 4.92469C9.68906 4.92469 10.3584 5.01328 10.9884 5.19047C12.4945 4.16672 13.1541 4.38328 13.1541 4.38328C13.5872 5.46609 13.3116 6.27328 13.2328 6.47016C13.7348 7.02141 14.04 7.72031 14.04 8.58656C14.04 11.6086 12.1992 12.278 10.447 12.4748C10.7325 12.7209 10.9786 13.1934 10.9786 13.9317C10.9786 14.985 10.9688 15.8316 10.9688 16.0973C10.9688 16.3041 11.1164 16.5502 11.5102 16.4714C13.0735 15.9436 14.432 14.9389 15.3943 13.5986C16.3567 12.2583 16.8746 10.65 16.875 9C16.875 4.64906 13.3509 1.125 9 1.125Z" fill="#24292F"/>
</svg>
'use client'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
......
'use client'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
import { Beaker02 } from '@/app/components/base/icons/src/vender/solid/education'
import { TerminalSquare } from '@/app/components/base/icons/src/vender/solid/development'
const headerEnvClassName: { [k: string]: string } = {
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]',
}
const EnvNav = () => {
const { t } = useTranslation()
const { langeniusVersionInfo } = useAppContext()
const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
if (!showEnvTag)
return null
return (
<div className={`
flex items-center h-[22px] mr-4 rounded-md px-2 text-xs font-medium border
${headerEnvClassName[langeniusVersionInfo.current_env]}
`}>
{
langeniusVersionInfo.current_env === 'TESTING' && (
<>
<Beaker02 className='w-3 h-3 mr-1' />
{t('common.environment.testing')}
</>
)
}
{
langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
<>
<TerminalSquare className='w-3 h-3 mr-1' />
{t('common.environment.development')}
</>
)
}
</div>
)
}
export default EnvNav
'use client'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
import { Grid01 as Grid01Solid } from '@/app/components/base/icons/src/vender/solid/layout'
type ExploreNavProps = {
className?: string
}
const ExploreNav = ({
className,
}: ExploreNavProps) => {
const { t } = useTranslation()
const selectedSegment = useSelectedLayoutSegment()
const actived = selectedSegment === 'explore'
return (
<Link href="/explore/apps" className={classNames(
className, 'group',
actived && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
actived ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
)}>
{
actived
? <Grid01Solid className='mr-2 w-4 h-4' />
: <Grid01 className='mr-2 w-4 h-4' />
}
{t('common.menus.explore')}
</Link>
)
}
export default ExploreNav
import { Github } from '@/app/components/base/icons/src/public/common'
import type { GithubRepo } from '@/models/common'
const getStar = async () => {
const res = await fetch('https://api.github.com/repos/langgenius/dify')
if (!res.ok)
throw new Error('Failed to fetch data')
return res.json()
}
const GithubStar = async () => {
let githubRepo: GithubRepo = { stargazers_count: 0 }
if (process.env.NODE_ENV === 'development')
return null
try {
githubRepo = await getStar()
}
catch (e) {
return null
}
return (
<a
href='https://github.com/langgenius/dify'
target='_blank'
className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
<div className='flex items-center px-2 py-1 bg-gray-100'>
<Github className='mr-1 w-[18px] h-[18px]' />
Star
</div>
<div className='px-2 py-1 bg-white border-l border-gray-200'>{`${githubRepo.stargazers_count}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
</a>
)
}
export default GithubStar
...@@ -15,23 +15,8 @@ ...@@ -15,23 +15,8 @@
background-size: contain; background-size: contain;
} }
.github-icon {
width: 18px;
height: 18px;
background: url(./assets/github-icon.svg) center center no-repeat;
background-size: contain;
}
.alpha { .alpha {
width: 12px; width: 12px;
height: 12px; height: 12px;
background: url(./assets/alpha.svg) center center no-repeat; background: url(./assets/alpha.svg) center center no-repeat;
}
.beaker-icon {
width: 12px;
height: 12px;
margin-right: 4px;
background: url(./assets/beaker.svg) center center no-repeat;
background-size: contain;
} }
\ No newline at end of file
'use client'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { usePathname, useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { CommandLineIcon } from '@heroicons/react/24/solid'
import Link from 'next/link' import Link from 'next/link'
import AccountDropdown from './account-dropdown' import AccountDropdown from './account-dropdown'
import AppNav from './app-nav' import AppNav from './app-nav'
import DatasetNav from './dataset-nav' import DatasetNav from './dataset-nav'
import EnvNav from './env-nav'
import ExploreNav from './explore-nav'
import GithubStar from './github-star'
import PluginNav from './plugin-nav'
import s from './index.module.css' import s from './index.module.css'
import type { GithubRepo } from '@/models/common'
import { WorkspaceProvider } from '@/context/workspace-context' import { WorkspaceProvider } from '@/context/workspace-context'
import { useAppContext } from '@/context/app-context'
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
import { Grid01 as Grid01Solid } from '@/app/components/base/icons/src/vender/solid/layout'
import { PuzzlePiece01 } from '@/app/components/base/icons/src/vender/line/development'
import { PuzzlePiece01 as PuzzlePiece01Solid } from '@/app/components/base/icons/src/vender/solid/development'
const navClassName = ` const navClassName = `
flex items-center relative mr-3 px-3 h-8 rounded-xl flex items-center relative mr-3 px-3 h-8 rounded-xl
font-medium text-sm font-medium text-sm
cursor-pointer cursor-pointer
` `
const headerEnvClassName: { [k: string]: string } = {
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]',
}
const Header = () => {
const { t } = useTranslation()
const pathname = usePathname()
const { userProfile, langeniusVersionInfo } = useAppContext()
const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
const selectedSegment = useSelectedLayoutSegment()
const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon'
const isExplore = selectedSegment === 'explore'
const [starCount, setStarCount] = useState(0)
const isBordered = ['/apps', '/datasets'].includes(pathname)
useEffect(() => {
globalThis.fetch('https://api.github.com/repos/langgenius/dify').then(res => res.json()).then((data: GithubRepo) => {
setStarCount(data.stargazers_count)
})
}, [])
const Header = () => {
return ( return (
<div className={classNames( <>
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14', <div className='flex items-center'>
s.header, <Link href="/apps" className='flex items-center mr-4'>
isBordered ? 'border-b border-gray-200' : '', <div className={s.logo} />
)} </Link>
> {/* @ts-expect-error Async Server Component */}
<div className={classNames( <GithubStar />
s[`header-${langeniusVersionInfo.current_env}`], </div>
'flex flex-1 items-center justify-between px-4', <div className='flex items-center'>
)}> <ExploreNav className={navClassName} />
<div className='flex items-center'> <AppNav />
<Link href="/apps" className='flex items-center mr-4'> <PluginNav className={navClassName} />
<div className={s.logo} /> <DatasetNav />
</Link> </div>
{ <div className='flex items-center flex-shrink-0'>
starCount > 0 && ( <EnvNav />
<Link <WorkspaceProvider>
href='https://github.com/langgenius/dify' <AccountDropdown />
target='_blank' </WorkspaceProvider>
className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
<div className='flex items-center px-2 py-1 bg-gray-100'>
<div className={`${s['github-icon']} mr-1 rounded-full`} />
Star
</div>
<div className='px-2 py-1 bg-white border-l border-gray-200'>{`${starCount}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
</Link>
)
}
</div>
<div className='flex items-center'>
<Link href="/explore/apps" className={classNames(
navClassName, 'group',
isExplore && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
isExplore ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
)}>
{
isExplore
? <Grid01Solid className='mr-2 w-4 h-4' />
: <Grid01 className='mr-2 w-4 h-4' />
}
{t('common.menus.explore')}
</Link>
<AppNav />
<Link href="/plugins-coming-soon" className={classNames(
navClassName, 'group',
isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
)}>
{
isPluginsComingSoon
? <PuzzlePiece01Solid className='mr-2 w-4 h-4' />
: <PuzzlePiece01 className='mr-2 w-4 h-4' />
}
{t('common.menus.plugins')}
</Link>
<DatasetNav />
</div>
<div className='flex items-center flex-shrink-0'>
{
showEnvTag && (
<div className={`
flex items-center h-[22px] mr-4 rounded-md px-2 text-xs font-medium border
${headerEnvClassName[langeniusVersionInfo.current_env]}
`}>
{
langeniusVersionInfo.current_env === 'TESTING' && (
<>
<div className={s['beaker-icon']} />
{t('common.environment.testing')}
</>
)
}
{
langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
<>
<CommandLineIcon className='w-3 h-3 mr-1' />
{t('common.environment.development')}
</>
)
}
</div>
)
}
<WorkspaceProvider>
<AccountDropdown userProfile={userProfile} langeniusVersionInfo={langeniusVersionInfo} />
</WorkspaceProvider>
</div>
</div> </div>
</div> </>
) )
} }
export default Header export default Header
'use client'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { PuzzlePiece01 } from '@/app/components/base/icons/src/vender/line/development'
import { PuzzlePiece01 as PuzzlePiece01Solid } from '@/app/components/base/icons/src/vender/solid/development'
type PluginNavProps = {
className?: string
}
const PluginNav = ({
className,
}: PluginNavProps) => {
const { t } = useTranslation()
const selectedSegment = useSelectedLayoutSegment()
const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon'
return (
<Link href="/plugins-coming-soon" className={classNames(
className, 'group',
isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
)}>
{
isPluginsComingSoon
? <PuzzlePiece01Solid className='mr-2 w-4 h-4' />
: <PuzzlePiece01 className='mr-2 w-4 h-4' />
}
{t('common.menus.plugins')}
</Link>
)
}
export default PluginNav
...@@ -397,21 +397,21 @@ const Main: FC<IMainProps> = ({ ...@@ -397,21 +397,21 @@ const Main: FC<IMainProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
...@@ -51,7 +51,9 @@ const List: FC<IListProps> = ({ ...@@ -51,7 +51,9 @@ const List: FC<IListProps> = ({
useInfiniteScroll( useInfiniteScroll(
async () => { async () => {
if (!isNoMore) { if (!isNoMore) {
const lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
if (lastId === '-1')
lastId = undefined
let res: any let res: any
if (isUniversalChat) if (isUniversalChat)
res = await fetchUniversalConversations(lastId, isPinned) res = await fetchUniversalConversations(lastId, isPinned)
......
...@@ -130,21 +130,21 @@ const Welcome: FC<IWelcomeProps> = ({ ...@@ -130,21 +130,21 @@ const Welcome: FC<IWelcomeProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
...@@ -390,21 +390,21 @@ const Main: FC<IMainProps> = ({ ...@@ -390,21 +390,21 @@ const Main: FC<IMainProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
...@@ -130,21 +130,21 @@ const Welcome: FC<IWelcomeProps> = ({ ...@@ -130,21 +130,21 @@ const Welcome: FC<IWelcomeProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import {
Bars3Icon,
PencilSquareIcon,
} from '@heroicons/react/24/solid'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
export type IHeaderProps = { export type IHeaderProps = {
title: string title: string
customerIcon?: React.ReactNode customerIcon?: React.ReactNode
...@@ -8,6 +13,8 @@ export type IHeaderProps = { ...@@ -8,6 +13,8 @@ export type IHeaderProps = {
icon_background: string icon_background: string
isMobile?: boolean isMobile?: boolean
isEmbedScene?: boolean isEmbedScene?: boolean
onShowSideBar?: () => void
onCreateNewChat?: () => void
} }
const Header: FC<IHeaderProps> = ({ const Header: FC<IHeaderProps> = ({
title, title,
...@@ -16,22 +23,25 @@ const Header: FC<IHeaderProps> = ({ ...@@ -16,22 +23,25 @@ const Header: FC<IHeaderProps> = ({
icon, icon,
icon_background, icon_background,
isEmbedScene = false, isEmbedScene = false,
onShowSideBar,
onCreateNewChat,
}) => { }) => {
return !isMobile if (!isMobile)
? null return null
: (
if (isEmbedScene) {
return (
<div <div
className={`shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100 ${ className={`
isEmbedScene ? 'bg-gradient-to-r from-blue-600 to-sky-500' : '' shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100
}`} bg-gradient-to-r from-blue-600 to-sky-500
`}
> >
<div></div> <div></div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{customerIcon || <AppIcon size="small" icon={icon} background={icon_background} />} {customerIcon || <AppIcon size="small" icon={icon} background={icon_background} />}
<div <div
className={`text-sm text-gray-800 font-bold ${ className={'text-sm font-bold text-white'}
isEmbedScene ? 'text-white' : ''
}`}
> >
{title} {title}
</div> </div>
...@@ -39,6 +49,27 @@ const Header: FC<IHeaderProps> = ({ ...@@ -39,6 +49,27 @@ const Header: FC<IHeaderProps> = ({
<div></div> <div></div>
</div> </div>
) )
}
return (
<div className="shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100">
<div
className='flex items-center justify-center h-8 w-8 cursor-pointer'
onClick={() => onShowSideBar?.()}
>
<Bars3Icon className="h-4 w-4 text-gray-500" />
</div>
<div className='flex items-center space-x-2'>
<AppIcon size="small" icon={icon} background={icon_background} />
<div className=" text-sm text-gray-800 font-bold">{title}</div>
</div>
<div className='flex items-center justify-center h-8 w-8 cursor-pointer'
onClick={() => onCreateNewChat?.()}
>
<PencilSquareIcon className="h-4 w-4 text-gray-500" />
</div>
</div>
)
} }
export default React.memo(Header) export default React.memo(Header)
...@@ -80,21 +80,21 @@ const Result: FC<IResultProps> = ({ ...@@ -80,21 +80,21 @@ const Result: FC<IResultProps> = ({
if (!prompt_variables || prompt_variables?.length === 0) if (!prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs[key]) if (!inputs[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
......
...@@ -67,7 +67,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => ...@@ -67,7 +67,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
const result = await userProfileResponse.json() const result = await userProfileResponse.json()
setUserProfile(result) setUserProfile(result)
const current_version = userProfileResponse.headers.get('x-version') const current_version = userProfileResponse.headers.get('x-version')
const current_env = userProfileResponse.headers.get('x-env') const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } }) const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env }) setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
} }
......
import { useState } from 'react' import { useCallback, useState } from 'react'
import writeText from 'copy-to-clipboard'
type CopiedValue = string | null type CopiedValue = string | null
type CopyFn = (text: string) => Promise<boolean> type CopyFn = (text: string) => Promise<boolean>
function useCopyToClipboard(): [CopiedValue, CopyFn] { function useCopyToClipboard(): [CopiedValue, CopyFn] {
const [copiedText, setCopiedText] = useState<CopiedValue>(null) const [copiedText, setCopiedText] = useState<CopiedValue>(null)
const copy: CopyFn = async text => { const copy: CopyFn = useCallback(async (text: string) => {
if (!navigator?.clipboard) { if (!navigator?.clipboard) {
console.warn('Clipboard not supported') console.warn('Clipboard not supported')
return false return false
} }
try { try {
await navigator.clipboard.writeText(text) writeText(text)
setCopiedText(text) setCopiedText(text)
return true return true
} catch (error) { }
console.warn('Copy failed', error) catch (error) {
setCopiedText(null) console.warn('Copy failed', error)
return false setCopiedText(null)
} return false
} }
}, [])
return [copiedText, copy] return [copiedText, copy]
} }
export default useCopyToClipboard export default useCopyToClipboard
\ No newline at end of file
...@@ -87,7 +87,7 @@ const translation = { ...@@ -87,7 +87,7 @@ const translation = {
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: 'name of the key: {{key}} required', nameOfKeyRequired: 'name of the key: {{key}} required',
valueOfVarRequired: 'Variables value can not be empty', valueOfVarRequired: '{{key}} value can not be empty',
queryRequired: 'Request text is required.', queryRequired: 'Request text is required.',
waitForResponse: waitForResponse:
'Please wait for the response to the previous message to complete.', 'Please wait for the response to the previous message to complete.',
......
...@@ -86,7 +86,7 @@ const translation = { ...@@ -86,7 +86,7 @@ const translation = {
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: '变量 {{key}} 对应的名称必填', nameOfKeyRequired: '变量 {{key}} 对应的名称必填',
valueOfVarRequired: '变量值必填', valueOfVarRequired: '{{key}}必填',
queryRequired: '主要文本必填', queryRequired: '主要文本必填',
waitForResponse: '请等待上条信息响应完成', waitForResponse: '请等待上条信息响应完成',
waitForBatchResponse: '请等待批量任务完成', waitForBatchResponse: '请等待批量任务完成',
......
{ {
"name": "dify-web", "name": "dify-web",
"version": "0.3.9", "version": "0.3.10",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
......
...@@ -15,6 +15,7 @@ async function embedChatbot () { ...@@ -15,6 +15,7 @@ async function embedChatbot () {
return; return;
} }
const isDev = !!difyChatbotConfig.isDev const isDev = !!difyChatbotConfig.isDev
const baseUrl = difyChatbotConfig.baseUrl || `https://${isDev ? 'dev.' : ''}udify.app`
const openIcon = `<svg const openIcon = `<svg
id="openIcon" id="openIcon"
width="24" width="24"
...@@ -53,7 +54,7 @@ async function embedChatbot () { ...@@ -53,7 +54,7 @@ async function embedChatbot () {
iframe.allow = "fullscreen;microphone" iframe.allow = "fullscreen;microphone"
iframe.title = "dify chatbot bubble window" iframe.title = "dify chatbot bubble window"
iframe.id = 'dify-chatbot-bubble-window' iframe.id = 'dify-chatbot-bubble-window'
iframe.src = `https://${isDev ? 'dev.' : ''}udify.app/chatbot/${difyChatbotConfig.token}`; iframe.src = `${baseUrl}/chatbot/${difyChatbotConfig.token}`
iframe.style.cssText = 'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;' iframe.style.cssText = 'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;'
document.body.appendChild(iframe); document.body.appendChild(iframe);
} }
......
async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){const o=!!t.isDev,n=`<svg async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){var e=!!t.isDev;const o=t.baseUrl||`https://${e?"dev.":""}udify.app`,n=`<svg
id="openIcon" id="openIcon"
width="24" width="24"
height="24" height="24"
...@@ -27,4 +27,4 @@ async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){co ...@@ -27,4 +27,4 @@ async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){co
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
</svg>`;if(!document.getElementById("dify-chatbot-bubble-button")){var e=document.createElement("div");e.id="dify-chatbot-bubble-button",e.style.cssText="position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}";const d=document.createElement("div");d.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",d.innerHTML=n,e.appendChild(d),document.body.appendChild(e),e.addEventListener("click",function(){var e=document.getElementById("dify-chatbot-bubble-window");e?"none"===e.style.display?(e.style.display="block",d.innerHTML=i):(e.style.display="none",d.innerHTML=n):((e=document.createElement("iframe")).allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id="dify-chatbot-bubble-window",e.src=`https://${o?"dev.":""}udify.app/chatbot/`+t.token,e.style.cssText="border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;",document.body.appendChild(e),d.innerHTML=i)})}}else console.error("difyChatbotConfig is empty or token is not provided")}document.body.onload=embedChatbot; </svg>`;if(!document.getElementById("dify-chatbot-bubble-button")){e=document.createElement("div");e.id="dify-chatbot-bubble-button",e.style.cssText="position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}";const d=document.createElement("div");d.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",d.innerHTML=n,e.appendChild(d),document.body.appendChild(e),e.addEventListener("click",function(){var e=document.getElementById("dify-chatbot-bubble-window");e?"none"===e.style.display?(e.style.display="block",d.innerHTML=i):(e.style.display="none",d.innerHTML=n):((e=document.createElement("iframe")).allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id="dify-chatbot-bubble-window",e.src=o+"/chatbot/"+t.token,e.style.cssText="border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;",document.body.appendChild(e),d.innerHTML=i)})}}else console.error("difyChatbotConfig is empty or token is not provided")}document.body.onload=embedChatbot;
\ No newline at end of file \ No newline at end of file
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