Merge branch 'main' into feat/dataset-notion-import
commit
201d9943bb
@ -0,0 +1,32 @@
|
||||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please provide a clear and concise description of what the bug is. Include
|
||||
screenshots if needed. Please test using the latest version of the relevant
|
||||
Dify packages to make sure your issue has not already been fixed.
|
||||
-->
|
||||
|
||||
Dify version: Cloud | Self Host
|
||||
|
||||
## Steps To Reproduce
|
||||
<!--
|
||||
Your bug will get fixed much faster if we can run your code and it doesn't
|
||||
have dependencies other than Dify. Issues without reproduction steps or
|
||||
code examples may be immediately closed as not actionable.
|
||||
-->
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
|
||||
## The current behavior
|
||||
|
||||
|
||||
## The expected behavior
|
||||
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: "\U0001F680 Feature request"
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: "\U0001F914 Questions and Help"
|
||||
about: Ask a usage or consultation question
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
# コントリビュート
|
||||
|
||||
[Dify](https://dify.ai) に興味を持ち、貢献したいと思うようになったことに感謝します!始める前に、
|
||||
[行動規範](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)を読み、
|
||||
[既存の問題](https://github.com/langgenius/langgenius-gateway/issues)をチェックしてください。
|
||||
本ドキュメントは、[Dify](https://dify.ai) をビルドしてテストするための開発環境の構築方法を説明するものです。
|
||||
|
||||
### 依存関係のインストール
|
||||
|
||||
[Dify](https://dify.ai)をビルドするには、お使いのマシンに以下の依存関係をインストールし、設定する必要があります:
|
||||
|
||||
- [Git](http://git-scm.com/)
|
||||
- [Docker](https://www.docker.com/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
- [Node.js v18.x (LTS)](http://nodejs.org)
|
||||
- [npm](https://www.npmjs.com/) バージョン 8.x.x もしくは [Yarn](https://yarnpkg.com/)
|
||||
- [Python](https://www.python.org/) バージョン 3.10.x
|
||||
|
||||
## ローカル開発
|
||||
|
||||
開発環境を構築するには、プロジェクトの git リポジトリをフォークし、適切なパッケージマネージャを使用してバックエンドとフロントエンドの依存関係をインストールし、docker-compose スタックを実行するように作成します。
|
||||
|
||||
### リポジトリのフォーク
|
||||
|
||||
[リポジトリ](https://github.com/langgenius/dify) をフォークする必要があります。
|
||||
|
||||
### リポジトリのクローン
|
||||
|
||||
GitHub でフォークしたリポジトリのクローンを作成する:
|
||||
|
||||
```
|
||||
git clone git@github.com:<github_username>/dify.git
|
||||
```
|
||||
|
||||
### バックエンドのインストール
|
||||
|
||||
バックエンドアプリケーションのインストール方法については、[Backend README](api/README.md) を参照してください。
|
||||
|
||||
### フロントエンドのインストール
|
||||
|
||||
フロントエンドアプリケーションのインストール方法については、[Frontend README](web/README.md) を参照してください。
|
||||
|
||||
### ブラウザで dify にアクセス
|
||||
|
||||
[Dify](https://dify.ai) をローカル環境で見ることができるようになりました [http://localhost:3000](http://localhost:3000)。
|
||||
|
||||
## プルリクエストの作成
|
||||
|
||||
変更後、プルリクエスト (PR) をオープンしてください。プルリクエストを提出すると、Dify チーム/コミュニティの他の人があなたと一緒にそれをレビューします。
|
||||
|
||||
マージコンフリクトなどの問題が発生したり、プルリクエストの開き方がわからなくなったりしませんでしたか? [GitHub's pull request tutorial](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) で、マージコンフリクトやその他の問題を解決する方法をチェックしてみてください。あなたの PR がマージされると、[コントリビュータチャート](https://github.com/langgenius/langgenius-gateway/graphs/contributors)にコントリビュータとして誇らしげに掲載されます。
|
||||
|
||||
## コミュニティチャンネル
|
||||
|
||||
お困りですか?何か質問がありますか? [Discord Community サーバ](https://discord.gg/AhzKf7dNgk)に参加してください。私たちがお手伝いします!
|
||||
@ -0,0 +1,120 @@
|
||||

|
||||
<p align="center">
|
||||
<a href="./README.md">English</a> |
|
||||
<a href="./README_CN.md">简体中文</a> |
|
||||
<a href="./README_JA.md">日本語</a>
|
||||
</p>
|
||||
|
||||
[Web サイト](https://dify.ai) • [ドキュメント](https://docs.dify.ai) • [Twitter](https://twitter.com/dify_ai) • [Discord](https://discord.gg/FngNHpbcY7)
|
||||
|
||||
Product Huntで私たちに投票してください ↓
|
||||
<a href="https://www.producthunt.com/posts/dify-ai"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?sanitize=true&post_id=dify-ai&theme=light" alt="Product Hunt Badge" width="250" height="54"></a>
|
||||
|
||||
|
||||
**Dify** は、より多くの人々が持続可能な AI ネイティブアプリケーションを作成できるように設計された、使いやすい LLMOps プラットフォームです。様々なアプリケーションタイプに対応したビジュアルオーケストレーションにより Dify は Backend-as-a-Service API としても機能する、すぐに使えるアプリケーションを提供します。プラグインやデータセットを統合するための1つの API で開発プロセスを統一し、プロンプトエンジニアリング、ビジュアル分析、継続的な改善のための1つのインターフェイスを使って業務を合理化します。
|
||||
|
||||
Difyで作成したアプリケーションは以下の通りです:
|
||||
|
||||
フォームモードとチャット会話モードをサポートする、すぐに使える Web サイト
|
||||
プラグイン機能、コンテキストの強化などを網羅する単一の API により、バックエンドのコーディングの手間を省きます。
|
||||
アプリケーションの視覚的なデータ分析、ログレビュー、アノテーションが可能です。
|
||||
Dify は LangChain と互換性があり、複数の LLM を徐々にサポートします:
|
||||
|
||||
- GPT 3 (text-davinci-003)
|
||||
- GPT 3.5 Turbo(ChatGPT)
|
||||
- GPT-4
|
||||
|
||||
## クラウドサービスの利用
|
||||
|
||||
[Dify.ai](https://dify.ai) をご覧ください
|
||||
|
||||
## Community Edition のインストール
|
||||
|
||||
### システム要件
|
||||
|
||||
Dify をインストールする前に、お使いのマシンが以下の最低システム要件を満たしていることを確認してください:
|
||||
|
||||
- CPU >= 1 Core
|
||||
- RAM >= 4GB
|
||||
|
||||
### クイックスタート
|
||||
|
||||
Dify サーバーを起動する最も簡単な方法は、[docker-compose.yml](docker/docker-compose.yaml) ファイルを実行することです。インストールコマンドを実行する前に、[Docker](https://docs.docker.com/get-docker/) と [Docker Compose](https://docs.docker.com/compose/install/) がお使いのマシンにインストールされていることを確認してください:
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
実行後、ブラウザで [http://localhost/install](http://localhost/install) にアクセスし、初期化インストール作業を開始することができます。
|
||||
|
||||
### 構成
|
||||
|
||||
カスタマイズが必要な場合は、[docker-compose.yml](docker/docker-compose.yaml) ファイルのコメントを参照し、手動で環境設定をお願いします。変更後、再度 'docker-compose up -d' を実行してください。
|
||||
|
||||
## ロードマップ
|
||||
|
||||
開発中の機能:
|
||||
|
||||
- **データセット**, Notionやウェブページからのコンテンツ同期など、より多くのデータセットをサポートします
|
||||
テキスト、ウェブページ、さらには Notion コンテンツなど、より多くのデータセットをサポートする予定です。ユーザーは、自分のデータソースをもとに AI アプリケーションを構築することができます。
|
||||
- **プラグイン**, アプリケーションに ChatGPT プラグイン標準のプラグインを導入する、または Dify 制作のプラグインを利用する
|
||||
今後、ChatGPT 規格に準拠したプラグインや、ディファイ独自のプラグインを公開し、より多くの機能をアプリケーションで実現できるようにします。
|
||||
- **オープンソースモデル**, 例えばモデルプロバイダーとして Llama を採用したり、さらにファインチューニングを行う
|
||||
Llama のような優れたオープンソースモデルを、私たちのプラットフォームのモデルオプションとして提供したり、さらなる微調整のために使用したりすることで、協力していきます。
|
||||
|
||||
|
||||
## Q&A
|
||||
|
||||
**Q: Dify で何ができるのか?**
|
||||
|
||||
A: Dify はシンプルでパワフルな LLM 開発・運用ツールです。商用グレードのアプリケーション、パーソナルアシスタントを構築するために使用することができます。独自のアプリケーションを開発したい場合、LangDifyGenius は OpenAI と統合する際のバックエンド作業を省き、視覚的な操作機能を提供し、GPT モデルを継続的に改善・訓練することが可能です。
|
||||
|
||||
**Q: Dify を使って、自分のモデルを「トレーニング」するにはどうすればいいのでしょうか?**
|
||||
|
||||
A: プロンプトエンジニアリング、コンテキスト拡張、ファインチューニングからなる価値あるアプリケーションです。プロンプトとプログラミング言語を組み合わせたハイブリッドプログラミングアプローチ(テンプレートエンジンのようなもの)で、長文の埋め込みやユーザー入力の YouTube 動画からの字幕取り込みなどを簡単に実現し、これらはすべて LLM が処理するコンテキストとして提出される予定です。また、アプリケーションの操作性を重視し、ユーザーがアプリケーションを使用する際に生成したデータを分析、アノテーション、継続的なトレーニングに利用できるようにしました。適切なツールがなければ、これらのステップに時間がかかることがあります。
|
||||
|
||||
**Q: 自分でアプリケーションを作りたい場合、何を準備すればよいですか?**
|
||||
|
||||
A: すでに OpenAI API Key をお持ちだと思いますが、お持ちでない場合はご登録ください。もし、すでにトレーニングのコンテキストとなるコンテンツをお持ちでしたら、それは素晴らしいことです!
|
||||
|
||||
**Q: インターフェイスにどの言語が使えますか?**
|
||||
|
||||
A: 現在、英語と中国語に対応しており、言語パックを寄贈することも可能です。
|
||||
|
||||
## Star ヒストリー
|
||||
|
||||
[](https://star-history.com/#langgenius/dify&Date)
|
||||
|
||||
## お問合せ
|
||||
|
||||
ご質問、ご提案、パートナーシップに関するお問い合わせは、以下のチャンネルからお気軽にご連絡ください:
|
||||
|
||||
- GitHub Repo で Issue や PR を提出する
|
||||
- [Discord](https://discord.gg/FngNHpbcY7) コミュニティで議論に参加する。
|
||||
- hello@dify.ai にメールを送信します
|
||||
|
||||
私たちは、皆様のお手伝いをさせていただき、より楽しく、より便利な AI アプリケーションを一緒に作っていきたいと思っています!
|
||||
|
||||
## コントリビュート
|
||||
|
||||
適切なレビューを行うため、コミットへの直接アクセスが可能なコントリビュータを含むすべてのコードコントリビュータは、プルリクエストで提出し、マージされる前にコア開発チームによって承認される必要があります。
|
||||
|
||||
私たちはすべてのプルリクエストを歓迎します!協力したい方は、[コントリビューションガイド](CONTRIBUTING.md) をチェックしてみてください。
|
||||
|
||||
## セキュリティ
|
||||
|
||||
プライバシー保護のため、GitHub へのセキュリティ問題の投稿は避けてください。代わりに、あなたの質問を security@dify.ai に送ってください。より詳細な回答を提供します。
|
||||
|
||||
## 引用
|
||||
|
||||
本ソフトウェアは、以下のオープンソースソフトウェアを使用しています:
|
||||
|
||||
- Chase, H. (2022). LangChain [Computer software]. https://github.com/hwchase17/langchain
|
||||
- Liu, J. (2022). LlamaIndex [Computer software]. doi: 10.5281/zenodo.1234.
|
||||
|
||||
詳しくは、各ソフトウェアの公式サイトまたはライセンス文をご参照ください。
|
||||
|
||||
## ライセンス
|
||||
|
||||
このリポジトリは、[Dify Open Source License](LICENSE) のもとで利用できます。
|
||||
@ -0,0 +1,64 @@
|
||||
import os
|
||||
|
||||
from langchain.llms import AzureOpenAI
|
||||
from langchain.schema import LLMResult
|
||||
from typing import Optional, List, Dict, Mapping, Any
|
||||
|
||||
from pydantic import root_validator
|
||||
|
||||
from core.llm.error_handle_wraps import handle_llm_exceptions, handle_llm_exceptions_async
|
||||
|
||||
|
||||
class StreamableAzureOpenAI(AzureOpenAI):
|
||||
openai_api_type: str = "azure"
|
||||
openai_api_version: str = ""
|
||||
|
||||
@root_validator()
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
"""Validate that api key and python package exists in environment."""
|
||||
try:
|
||||
import openai
|
||||
|
||||
values["client"] = openai.Completion
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import openai python package. "
|
||||
"Please install it with `pip install openai`."
|
||||
)
|
||||
if values["streaming"] and values["n"] > 1:
|
||||
raise ValueError("Cannot stream results when n > 1.")
|
||||
if values["streaming"] and values["best_of"] > 1:
|
||||
raise ValueError("Cannot stream results when best_of > 1.")
|
||||
return values
|
||||
|
||||
@property
|
||||
def _invocation_params(self) -> Dict[str, Any]:
|
||||
return {**super()._invocation_params, **{
|
||||
"api_type": self.openai_api_type,
|
||||
"api_base": self.openai_api_base,
|
||||
"api_version": self.openai_api_version,
|
||||
"api_key": self.openai_api_key,
|
||||
"organization": self.openai_organization if self.openai_organization else None,
|
||||
}}
|
||||
|
||||
@property
|
||||
def _identifying_params(self) -> Mapping[str, Any]:
|
||||
return {**super()._identifying_params, **{
|
||||
"api_type": self.openai_api_type,
|
||||
"api_base": self.openai_api_base,
|
||||
"api_version": self.openai_api_version,
|
||||
"api_key": self.openai_api_key,
|
||||
"organization": self.openai_organization if self.openai_organization else None,
|
||||
}}
|
||||
|
||||
@handle_llm_exceptions
|
||||
def generate(
|
||||
self, prompts: List[str], stop: Optional[List[str]] = None
|
||||
) -> LLMResult:
|
||||
return super().generate(prompts, stop)
|
||||
|
||||
@handle_llm_exceptions_async
|
||||
async def agenerate(
|
||||
self, prompts: List[str], stop: Optional[List[str]] = None
|
||||
) -> LLMResult:
|
||||
return await super().agenerate(prompts, stop)
|
||||
@ -1,18 +1,23 @@
|
||||
import redis
|
||||
|
||||
from redis.connection import SSLConnection, Connection
|
||||
|
||||
redis_client = redis.Redis()
|
||||
|
||||
|
||||
def init_app(app):
|
||||
connection_class = Connection
|
||||
if app.config.get('REDIS_USE_SSL', False):
|
||||
connection_class = SSLConnection
|
||||
|
||||
redis_client.connection_pool = redis.ConnectionPool(**{
|
||||
'host': app.config.get('REDIS_HOST', 'localhost'),
|
||||
'port': app.config.get('REDIS_PORT', 6379),
|
||||
'username': app.config.get('REDIS_USERNAME', None),
|
||||
'password': app.config.get('REDIS_PASSWORD', None),
|
||||
'db': app.config.get('REDIS_DB', 0),
|
||||
'encoding': 'utf-8',
|
||||
'encoding_errors': 'strict',
|
||||
'decode_responses': False
|
||||
})
|
||||
}, connection_class=connection_class)
|
||||
|
||||
app.extensions['redis'] = redis_client
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
import click
|
||||
from celery import shared_task
|
||||
from llama_index.data_structs.node_v2 import DocumentRelationship, Node
|
||||
from core.index.vector_index import VectorIndex
|
||||
from extensions.ext_database import db
|
||||
from models.dataset import DocumentSegment, Document, Dataset
|
||||
|
||||
|
||||
@shared_task
|
||||
def deal_dataset_vector_index_task(dataset_id: str, action: str):
|
||||
"""
|
||||
Async deal dataset from index
|
||||
:param dataset_id: dataset_id
|
||||
:param action: action
|
||||
Usage: deal_dataset_vector_index_task.delay(dataset_id, action)
|
||||
"""
|
||||
logging.info(click.style('Start deal dataset vector index: {}'.format(dataset_id), fg='green'))
|
||||
start_at = time.perf_counter()
|
||||
|
||||
try:
|
||||
dataset = Dataset.query.filter_by(
|
||||
id=dataset_id
|
||||
).first()
|
||||
if not dataset:
|
||||
raise Exception('Dataset not found')
|
||||
documents = Document.query.filter_by(dataset_id=dataset_id).all()
|
||||
if documents:
|
||||
vector_index = VectorIndex(dataset=dataset)
|
||||
for document in documents:
|
||||
# delete from vector index
|
||||
if action == "remove":
|
||||
vector_index.del_doc(document.id)
|
||||
elif action == "add":
|
||||
segments = db.session.query(DocumentSegment).filter(
|
||||
DocumentSegment.document_id == document.id,
|
||||
DocumentSegment.enabled == True
|
||||
) .order_by(DocumentSegment.position.asc()).all()
|
||||
|
||||
nodes = []
|
||||
previous_node = None
|
||||
for segment in segments:
|
||||
relationships = {
|
||||
DocumentRelationship.SOURCE: document.id
|
||||
}
|
||||
|
||||
if previous_node:
|
||||
relationships[DocumentRelationship.PREVIOUS] = previous_node.doc_id
|
||||
|
||||
previous_node.relationships[DocumentRelationship.NEXT] = segment.index_node_id
|
||||
|
||||
node = Node(
|
||||
doc_id=segment.index_node_id,
|
||||
doc_hash=segment.index_node_hash,
|
||||
text=segment.content,
|
||||
extra_info=None,
|
||||
node_info=None,
|
||||
relationships=relationships
|
||||
)
|
||||
|
||||
previous_node = node
|
||||
nodes.append(node)
|
||||
# save vector index
|
||||
vector_index.add_nodes(
|
||||
nodes=nodes,
|
||||
duplicate_check=True
|
||||
)
|
||||
|
||||
end_at = time.perf_counter()
|
||||
logging.info(
|
||||
click.style('Deal dataset vector index: {} latency: {}'.format(dataset_id, end_at - start_at), fg='green'))
|
||||
except Exception:
|
||||
logging.exception("Deal dataset vector index failed")
|
||||
@ -1,117 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# npm
|
||||
package-lock.json
|
||||
|
||||
# yarn
|
||||
.pnp.cjs
|
||||
.pnp.loader.mjs
|
||||
.yarn/
|
||||
yarn.lock
|
||||
.yarnrc.yml
|
||||
|
||||
# pmpm
|
||||
pnpm-lock.yaml
|
||||
@ -1 +0,0 @@
|
||||
# Mock Server
|
||||
@ -1,551 +0,0 @@
|
||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||
|
||||
function randomString (length) {
|
||||
let result = ''
|
||||
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]
|
||||
return result
|
||||
}
|
||||
|
||||
// https://www.notion.so/55773516a0194781ae211792a44a3663?pvs=4
|
||||
const VirtualData = new Array(10).fill().map((_, index) => {
|
||||
const date = new Date(Date.now() - index * 24 * 60 * 60 * 1000)
|
||||
return {
|
||||
date: `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`,
|
||||
conversation_count: Math.floor(Math.random() * 10) + index,
|
||||
terminal_count: Math.floor(Math.random() * 10) + index,
|
||||
token_count: Math.floor(Math.random() * 10) + index,
|
||||
total_price: Math.floor(Math.random() * 10) + index,
|
||||
}
|
||||
})
|
||||
|
||||
const registerAPI = function (app) {
|
||||
const apps = [{
|
||||
id: '1',
|
||||
name: 'chat app',
|
||||
mode: 'chat',
|
||||
description: 'description01',
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
api_rph: 3600,
|
||||
is_demo: false,
|
||||
model_config: {
|
||||
provider: 'OPENAI',
|
||||
model_id: 'gpt-3.5-turbo',
|
||||
configs: {
|
||||
prompt_template: '你是我的解梦小助手,请参考 {{book}} 回答我有关梦境的问题。在回答前请称呼我为 {{myName}}。',
|
||||
prompt_variables: [
|
||||
{
|
||||
key: 'book',
|
||||
name: '书',
|
||||
value: '《梦境解析》',
|
||||
type: 'string',
|
||||
description: '请具体说下书名'
|
||||
},
|
||||
{
|
||||
key: 'myName',
|
||||
name: 'your name',
|
||||
value: 'Book',
|
||||
type: 'string',
|
||||
description: 'please tell me your name'
|
||||
}
|
||||
],
|
||||
completion_params: {
|
||||
max_token: 16,
|
||||
temperature: 1, // 0-2
|
||||
top_p: 1,
|
||||
presence_penalty: 1, // -2-2
|
||||
frequency_penalty: 1, // -2-2
|
||||
}
|
||||
}
|
||||
},
|
||||
site: {
|
||||
access_token: '1000',
|
||||
title: 'site 01',
|
||||
author: 'John',
|
||||
default_language: 'zh-Hans-CN',
|
||||
customize_domain: 'http://customize_domain',
|
||||
theme: 'theme',
|
||||
customize_token_strategy: 'must',
|
||||
prompt_public: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'completion app',
|
||||
mode: 'completion', // genertation text
|
||||
description: 'description 02', // genertation text
|
||||
enable_site: false,
|
||||
enable_api: false,
|
||||
api_rpm: 60,
|
||||
api_rph: 3600,
|
||||
is_demo: false,
|
||||
model_config: {
|
||||
provider: 'OPENAI',
|
||||
model_id: 'text-davinci-003',
|
||||
configs: {
|
||||
prompt_template: '你是我的翻译小助手,请把以下内容 {{langA}} 翻译成 {{langB}},以下的内容:',
|
||||
prompt_variables: [
|
||||
{
|
||||
key: 'langA',
|
||||
name: '原始语音',
|
||||
value: '中文',
|
||||
type: 'string',
|
||||
description: '这是中文格式的原始语音'
|
||||
},
|
||||
{
|
||||
key: 'langB',
|
||||
name: '目标语言',
|
||||
value: '英语',
|
||||
type: 'string',
|
||||
description: '这是英语格式的目标语言'
|
||||
}
|
||||
],
|
||||
completion_params: {
|
||||
max_token: 16,
|
||||
temperature: 1, // 0-2
|
||||
top_p: 1,
|
||||
presence_penalty: 1, // -2-2
|
||||
frequency_penalty: 1, // -2-2
|
||||
}
|
||||
}
|
||||
},
|
||||
site: {
|
||||
access_token: '2000',
|
||||
title: 'site 02',
|
||||
author: 'Mark',
|
||||
default_language: 'en-US',
|
||||
customize_domain: 'http://customize_domain',
|
||||
theme: 'theme',
|
||||
customize_token_strategy: 'must',
|
||||
prompt_public: false
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const apikeys = [{
|
||||
id: '111121312313132',
|
||||
token: 'sk-DEFGHJKMNPQRSTWXYZabcdefhijk1234',
|
||||
last_used_at: '1679212138000',
|
||||
created_at: '1673316000000'
|
||||
}, {
|
||||
id: '43441242131223123',
|
||||
token: 'sk-EEFGHJKMNPQRSTWXYZabcdefhijk5678',
|
||||
last_used_at: '1679212721000',
|
||||
created_at: '1679212731000'
|
||||
}]
|
||||
|
||||
// create app
|
||||
app.post('/apps', async (req, res) => {
|
||||
apps.push({
|
||||
id: apps.length + 1 + '',
|
||||
...req.body,
|
||||
|
||||
})
|
||||
res.send({
|
||||
result: 'success'
|
||||
})
|
||||
})
|
||||
|
||||
// app list
|
||||
app.get('/apps', async (req, res) => {
|
||||
res.send({
|
||||
data: apps
|
||||
})
|
||||
})
|
||||
|
||||
// app detail
|
||||
app.get('/apps/:id', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id) || apps[0]
|
||||
res.send(item)
|
||||
})
|
||||
|
||||
// update app name
|
||||
app.post('/apps/:id/name', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
item.name = req.body.name
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// update app site-enable status
|
||||
app.post('/apps/:id/site-enable', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.enable_site = req.body.enable_site
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// update app api-enable status
|
||||
app.post('/apps/:id/api-enable', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.enable_api = req.body.enable_api
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// update app rate-limit
|
||||
app.post('/apps/:id/rate-limit', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.api_rpm = req.body.api_rpm
|
||||
item.api_rph = req.body.api_rph
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// update app url including code
|
||||
app.post('/apps/:id/site/access-token-reset', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.site.access_token = randomString(12)
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// update app config
|
||||
app.post('/apps/:id/site', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.name = req.body.title
|
||||
item.description = req.body.description
|
||||
item.prompt_public = req.body.prompt_public
|
||||
item.default_language = req.body.default_language
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
// get statistics daily-conversations
|
||||
app.get('/apps/:id/statistics/daily-conversations', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
if (item) {
|
||||
res.send({
|
||||
data: VirtualData
|
||||
})
|
||||
} else {
|
||||
res.send({
|
||||
data: []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// get statistics daily-end-users
|
||||
app.get('/apps/:id/statistics/daily-end-users', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
if (item) {
|
||||
res.send({
|
||||
data: VirtualData
|
||||
})
|
||||
} else {
|
||||
res.send({
|
||||
data: []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// get statistics token-costs
|
||||
app.get('/apps/:id/statistics/token-costs', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
if (item) {
|
||||
res.send({
|
||||
data: VirtualData
|
||||
})
|
||||
} else {
|
||||
res.send({
|
||||
data: []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// update app model config
|
||||
app.post('/apps/:id/model-config', async (req, res) => {
|
||||
const item = apps.find(item => item.id === req.params.id)
|
||||
console.log(item)
|
||||
item.model_config = req.body
|
||||
res.send(item || null)
|
||||
})
|
||||
|
||||
|
||||
// get api keys list
|
||||
app.get('/apps/:id/api-keys', async (req, res) => {
|
||||
res.send({
|
||||
data: apikeys
|
||||
})
|
||||
})
|
||||
|
||||
// del api key
|
||||
app.delete('/apps/:id/api-keys/:api_key_id', async (req, res) => {
|
||||
res.send({
|
||||
result: 'success'
|
||||
})
|
||||
})
|
||||
|
||||
// create api key
|
||||
app.post('/apps/:id/api-keys', async (req, res) => {
|
||||
res.send({
|
||||
id: 'e2424241313131',
|
||||
token: 'sk-GEFGHJKMNPQRSTWXYZabcdefhijk0124',
|
||||
created_at: '1679216688962'
|
||||
})
|
||||
})
|
||||
|
||||
// get completion-conversations
|
||||
app.get('/apps/:id/completion-conversations', async (req, res) => {
|
||||
const data = {
|
||||
data: [{
|
||||
id: 1,
|
||||
from_end_user_id: 'user 1',
|
||||
summary: 'summary1',
|
||||
created_at: '2023-10-11',
|
||||
annotated: true,
|
||||
message_count: 100,
|
||||
user_feedback_stats: {
|
||||
like: 4, dislike: 5
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 1, dislike: 2
|
||||
},
|
||||
message: {
|
||||
message: 'message1',
|
||||
query: 'question1',
|
||||
answer: 'answer1'
|
||||
}
|
||||
}, {
|
||||
id: 12,
|
||||
from_end_user_id: 'user 2',
|
||||
summary: 'summary2',
|
||||
created_at: '2023-10-01',
|
||||
annotated: false,
|
||||
message_count: 10,
|
||||
user_feedback_stats: {
|
||||
like: 2, dislike: 20
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 12, dislike: 21
|
||||
},
|
||||
message: {
|
||||
message: 'message2',
|
||||
query: 'question2',
|
||||
answer: 'answer2'
|
||||
}
|
||||
}, {
|
||||
id: 13,
|
||||
from_end_user_id: 'user 3',
|
||||
summary: 'summary3',
|
||||
created_at: '2023-10-11',
|
||||
annotated: false,
|
||||
message_count: 20,
|
||||
user_feedback_stats: {
|
||||
like: 2, dislike: 0
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 0, dislike: 21
|
||||
},
|
||||
message: {
|
||||
message: 'message3',
|
||||
query: 'question3',
|
||||
answer: 'answer3'
|
||||
}
|
||||
}],
|
||||
total: 200
|
||||
}
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
// get chat-conversations
|
||||
app.get('/apps/:id/chat-conversations', async (req, res) => {
|
||||
const data = {
|
||||
data: [{
|
||||
id: 1,
|
||||
from_end_user_id: 'user 1',
|
||||
summary: 'summary1',
|
||||
created_at: '2023-10-11',
|
||||
read_at: '2023-10-12',
|
||||
annotated: true,
|
||||
message_count: 100,
|
||||
user_feedback_stats: {
|
||||
like: 4, dislike: 5
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 1, dislike: 2
|
||||
},
|
||||
message: {
|
||||
message: 'message1',
|
||||
query: 'question1',
|
||||
answer: 'answer1'
|
||||
}
|
||||
}, {
|
||||
id: 12,
|
||||
from_end_user_id: 'user 2',
|
||||
summary: 'summary2',
|
||||
created_at: '2023-10-01',
|
||||
annotated: false,
|
||||
message_count: 10,
|
||||
user_feedback_stats: {
|
||||
like: 2, dislike: 20
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 12, dislike: 21
|
||||
},
|
||||
message: {
|
||||
message: 'message2',
|
||||
query: 'question2',
|
||||
answer: 'answer2'
|
||||
}
|
||||
}, {
|
||||
id: 13,
|
||||
from_end_user_id: 'user 3',
|
||||
summary: 'summary3',
|
||||
created_at: '2023-10-11',
|
||||
annotated: false,
|
||||
message_count: 20,
|
||||
user_feedback_stats: {
|
||||
like: 2, dislike: 0
|
||||
},
|
||||
admin_feedback_stats: {
|
||||
like: 0, dislike: 21
|
||||
},
|
||||
message: {
|
||||
message: 'message3',
|
||||
query: 'question3',
|
||||
answer: 'answer3'
|
||||
}
|
||||
}],
|
||||
total: 200
|
||||
}
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
// get completion-conversation detail
|
||||
app.get('/apps/:id/completion-conversations/:cid', async (req, res) => {
|
||||
const data =
|
||||
{
|
||||
id: 1,
|
||||
from_end_user_id: 'user 1',
|
||||
summary: 'summary1',
|
||||
created_at: '2023-10-11',
|
||||
annotated: true,
|
||||
message: {
|
||||
message: 'question1',
|
||||
// query: 'question1',
|
||||
answer: 'answer1',
|
||||
annotation: {
|
||||
content: '这是一段纠正的内容'
|
||||
}
|
||||
},
|
||||
model_config: {
|
||||
provider: 'openai',
|
||||
model_id: 'model_id',
|
||||
configs: {
|
||||
prompt_template: '你是我的翻译小助手,请把以下内容 {{langA}} 翻译成 {{langB}},以下的内容:{{content}}'
|
||||
}
|
||||
}
|
||||
}
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
// get chat-conversation detail
|
||||
app.get('/apps/:id/chat-conversations/:cid', async (req, res) => {
|
||||
const data =
|
||||
{
|
||||
id: 1,
|
||||
from_end_user_id: 'user 1',
|
||||
summary: 'summary1',
|
||||
created_at: '2023-10-11',
|
||||
annotated: true,
|
||||
message: {
|
||||
message: 'question1',
|
||||
// query: 'question1',
|
||||
answer: 'answer1',
|
||||
created_at: '2023-08-09 13:00',
|
||||
provider_response_latency: 130,
|
||||
message_tokens: 230
|
||||
},
|
||||
model_config: {
|
||||
provider: 'openai',
|
||||
model_id: 'model_id',
|
||||
configs: {
|
||||
prompt_template: '你是我的翻译小助手,请把以下内容 {{langA}} 翻译成 {{langB}},以下的内容:{{content}}'
|
||||
}
|
||||
}
|
||||
}
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
// get chat-conversation message list
|
||||
app.get('/apps/:id/chat-messages', async (req, res) => {
|
||||
const data = {
|
||||
data: [{
|
||||
id: 1,
|
||||
created_at: '2023-10-11 07:09',
|
||||
message: '请说说人为什么会做梦?' + req.query.conversation_id,
|
||||
answer: '梦境通常是个人内心深处的反映,很难确定每个人梦境的确切含义,因为它们可能会受到梦境者的文化背景、生活经验和情感状态等多种因素的影响。',
|
||||
provider_response_latency: 450,
|
||||
answer_tokens: 200,
|
||||
annotation: {
|
||||
content: 'string',
|
||||
account: {
|
||||
id: 'string',
|
||||
name: 'string',
|
||||
email: 'string'
|
||||
}
|
||||
},
|
||||
feedbacks: {
|
||||
rating: 'like',
|
||||
content: 'string',
|
||||
from_source: 'log'
|
||||
}
|
||||
}, {
|
||||
id: 2,
|
||||
created_at: '2023-10-11 8:23',
|
||||
message: '夜里经常做梦会影响次日的精神状态吗?',
|
||||
answer: '总之,这个梦境可能与梦境者的个人经历和情感状态有关,但在一般情况下,它可能表示一种强烈的情感反应,包括愤怒、不满和对于正义和自由的渴望。',
|
||||
provider_response_latency: 400,
|
||||
answer_tokens: 250,
|
||||
annotation: {
|
||||
content: 'string',
|
||||
account: {
|
||||
id: 'string',
|
||||
name: 'string',
|
||||
email: 'string'
|
||||
}
|
||||
},
|
||||
// feedbacks: {
|
||||
// rating: 'like',
|
||||
// content: 'string',
|
||||
// from_source: 'log'
|
||||
// }
|
||||
}, {
|
||||
id: 3,
|
||||
created_at: '2023-10-11 10:20',
|
||||
message: '梦见在山上手撕鬼子,大师解解梦',
|
||||
answer: '但是,一般来说,“手撕鬼子”这个场景可能是梦境者对于过去历史上的战争、侵略以及对于自己国家和族群的保护与维护的情感反应。在梦中,你可能会感到自己充满力量和勇气,去对抗那些看似强大的侵略者。',
|
||||
provider_response_latency: 288,
|
||||
answer_tokens: 100,
|
||||
annotation: {
|
||||
content: 'string',
|
||||
account: {
|
||||
id: 'string',
|
||||
name: 'string',
|
||||
email: 'string'
|
||||
}
|
||||
},
|
||||
feedbacks: {
|
||||
rating: 'dislike',
|
||||
content: 'string',
|
||||
from_source: 'log'
|
||||
}
|
||||
}],
|
||||
limit: 20,
|
||||
has_more: true
|
||||
}
|
||||
res.send(data)
|
||||
})
|
||||
|
||||
app.post('/apps/:id/annotations', async (req, res) => {
|
||||
res.send({ result: 'success' })
|
||||
})
|
||||
|
||||
app.post('/apps/:id/feedbacks', async (req, res) => {
|
||||
res.send({ result: 'success' })
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
module.exports = registerAPI
|
||||
@ -1,38 +0,0 @@
|
||||
|
||||
const registerAPI = function (app) {
|
||||
app.post('/login', async (req, res) => {
|
||||
res.send({
|
||||
result: 'success'
|
||||
})
|
||||
})
|
||||
|
||||
// get user info
|
||||
app.get('/account/profile', async (req, res) => {
|
||||
res.send({
|
||||
id: '11122222',
|
||||
name: 'Joel',
|
||||
email: 'iamjoel007@gmail.com'
|
||||
})
|
||||
})
|
||||
|
||||
// logout
|
||||
app.get('/logout', async (req, res) => {
|
||||
res.send({
|
||||
result: 'success'
|
||||
})
|
||||
})
|
||||
|
||||
// Langgenius version
|
||||
app.get('/version', async (req, res) => {
|
||||
res.send({
|
||||
current_version: 'v1.0.0',
|
||||
latest_version: 'v1.0.0',
|
||||
upgradeable: true,
|
||||
compatible_upgrade: true
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
module.exports = registerAPI
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
const registerAPI = function (app) {
|
||||
app.get('/demo', async (req, res) => {
|
||||
res.send({
|
||||
des: 'get res'
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/demo', async (req, res) => {
|
||||
res.send({
|
||||
des: 'post res'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = registerAPI
|
||||
@ -1,42 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const bodyParser = require('body-parser')
|
||||
var cors = require('cors')
|
||||
|
||||
const commonAPI = require('./api/common')
|
||||
const demoAPI = require('./api/demo')
|
||||
const appsApi = require('./api/apps')
|
||||
const debugAPI = require('./api/debug')
|
||||
const datasetsAPI = require('./api/datasets')
|
||||
|
||||
const port = 3001
|
||||
|
||||
app.use(bodyParser.json()) // for parsing application/json
|
||||
app.use(bodyParser.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
|
||||
|
||||
const corsOptions = {
|
||||
origin: true,
|
||||
credentials: true,
|
||||
}
|
||||
app.use(cors(corsOptions)) // for cross origin
|
||||
app.options('*', cors(corsOptions)) // include before other routes
|
||||
|
||||
|
||||
demoAPI(app)
|
||||
commonAPI(app)
|
||||
appsApi(app)
|
||||
debugAPI(app)
|
||||
datasetsAPI(app)
|
||||
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('rootpath')
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Mock run on port ${port}`)
|
||||
})
|
||||
|
||||
const sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon node app.js",
|
||||
"start": "node app.js",
|
||||
"tcp": "node tcp.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "4.18.2",
|
||||
"express-jwt": "8.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "2.0.21"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
# For production release, change this to PRODUCTION
|
||||
NEXT_PUBLIC_DEPLOY_ENV=DEVELOPMENT
|
||||
# The deployment edition, SELF_HOSTED or CLOUD
|
||||
NEXT_PUBLIC_EDITION=SELF_HOSTED
|
||||
# The base URL of console application, refers to the Console base URL of WEB service if console domain is
|
||||
# different from api or web app domain.
|
||||
# example: http://cloud.dify.ai/console/api
|
||||
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
|
||||
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
|
||||
# console or api domain.
|
||||
# example: http://udify.app/api
|
||||
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
|
||||
@ -1,3 +0,0 @@
|
||||
export async function GET(_request: Request) {
|
||||
return new Response('Hello, Next.js!')
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { t } from 'i18next'
|
||||
import s from './style.module.css'
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
|
||||
type ICopyBtnProps = {
|
||||
value: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const CopyBtn = ({
|
||||
value,
|
||||
className,
|
||||
}: ICopyBtnProps) => {
|
||||
const [isCopied, setIsCopied] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<Tooltip
|
||||
selector="copy-btn-tooltip"
|
||||
content={(isCopied ? t('appApi.copied') : t('appApi.copy')) as string}
|
||||
className='z-10'
|
||||
>
|
||||
<div
|
||||
className={`box-border p-0.5 flex items-center justify-center rounded-md bg-white cursor-pointer`}
|
||||
style={{
|
||||
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)'
|
||||
}}
|
||||
onClick={() => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
}}
|
||||
>
|
||||
<div className={`w-6 h-6 hover:bg-gray-50 ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyBtn
|
||||
@ -0,0 +1,15 @@
|
||||
.copyIcon {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/copy.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.copyIcon:hover {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/copy-hover.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.copyIcon.copied {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/copied.svg);
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useState, FC, ChangeEvent } from 'react'
|
||||
import data from '@emoji-mart/data'
|
||||
import { init, SearchIndex } from 'emoji-mart'
|
||||
import cn from 'classnames'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Button from '@/app/components/base/button'
|
||||
import s from './style.module.css'
|
||||
import {
|
||||
MagnifyingGlassIcon
|
||||
} from '@heroicons/react/24/outline'
|
||||
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'em-emoji': React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLElement>,
|
||||
HTMLElement
|
||||
>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init({ data })
|
||||
|
||||
async function search(value: string) {
|
||||
const emojis = await SearchIndex.search(value) || []
|
||||
|
||||
const results = emojis.map((emoji: any) => {
|
||||
return emoji.skins[0].native
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
const backgroundColors = [
|
||||
'#FFEAD5',
|
||||
'#E4FBCC',
|
||||
'#D3F8DF',
|
||||
'#E0F2FE',
|
||||
|
||||
'#E0EAFF',
|
||||
'#EFF1F5',
|
||||
'#FBE8FF',
|
||||
'#FCE7F6',
|
||||
|
||||
'#FEF7C3',
|
||||
'#E6F4D7',
|
||||
'#D5F5F6',
|
||||
'#D1E9FF',
|
||||
|
||||
'#D1E0FF',
|
||||
'#D5D9EB',
|
||||
'#ECE9FE',
|
||||
'#FFE4E8',
|
||||
]
|
||||
interface IEmojiPickerProps {
|
||||
isModal?: boolean
|
||||
onSelect?: (emoji: string, background: string) => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const EmojiPicker: FC<IEmojiPickerProps> = ({
|
||||
isModal = true,
|
||||
onSelect,
|
||||
onClose
|
||||
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { categories } = data as any
|
||||
const [selectedEmoji, setSelectedEmoji] = useState('')
|
||||
const [selectedBackground, setSelectedBackground] = useState(backgroundColors[0])
|
||||
|
||||
const [searchedEmojis, setSearchedEmojis] = useState([])
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
|
||||
return isModal ? <Modal
|
||||
onClose={() => { }}
|
||||
isShow
|
||||
closable={false}
|
||||
wrapperClassName='!z-40'
|
||||
className={cn(s.container, '!w-[362px] !p-0')}
|
||||
>
|
||||
<div className='flex flex-col items-center w-full p-3'>
|
||||
<div className="relative w-full">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<MagnifyingGlassIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
id="search"
|
||||
className='block w-full h-10 px-3 pl-10 text-sm font-normal bg-gray-100 rounded-lg'
|
||||
placeholder="Search emojis..."
|
||||
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.value === '') {
|
||||
setIsSearching(false)
|
||||
return
|
||||
} else {
|
||||
setIsSearching(true)
|
||||
const emojis = await search(e.target.value)
|
||||
setSearchedEmojis(emojis)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className='m-0 mb-3' />
|
||||
|
||||
<div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
|
||||
{isSearching && <>
|
||||
<div key={`category-search`} className='flex flex-col'>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{searchedEmojis.map((emoji: string, index: number) => {
|
||||
return <div
|
||||
key={`emoji-search-${index}`}
|
||||
className='inline-flex w-10 h-10 rounded-lg items-center justify-center'
|
||||
onClick={() => {
|
||||
setSelectedEmoji(emoji)
|
||||
}}
|
||||
>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
|
||||
<em-emoji id={emoji} />
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
|
||||
{categories.map((category: any, index: number) => {
|
||||
return <div key={`category-${index}`} className='flex flex-col'>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{category.emojis.map((emoji: string, index: number) => {
|
||||
return <div
|
||||
key={`emoji-${index}`}
|
||||
className='inline-flex w-10 h-10 rounded-lg items-center justify-center'
|
||||
onClick={() => {
|
||||
setSelectedEmoji(emoji)
|
||||
}}
|
||||
>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
|
||||
<em-emoji id={emoji} />
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Color Select */}
|
||||
<div className={cn('flex flex-col p-3 ', selectedEmoji == '' ? 'opacity-25' : '')}>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{backgroundColors.map((color) => {
|
||||
return <div
|
||||
key={color}
|
||||
className={
|
||||
cn(
|
||||
'cursor-pointer',
|
||||
`hover:ring-1 ring-offset-1`,
|
||||
'inline-flex w-10 h-10 rounded-lg items-center justify-center',
|
||||
color === selectedBackground ? `ring-1 ring-gray-300` : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
setSelectedBackground(color)
|
||||
}}
|
||||
>
|
||||
<div className={cn(
|
||||
'w-8 h-8 p-1 flex items-center justify-center rounded-lg',
|
||||
)
|
||||
} style={{ background: color }}>
|
||||
{selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Divider className='m-0' />
|
||||
<div className='w-full flex items-center justify-center p-3 gap-2'>
|
||||
<Button type="default" className='w-full' onClick={() => {
|
||||
onClose && onClose()
|
||||
}}>
|
||||
{t('app.emoji.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={selectedEmoji == ''}
|
||||
type="primary"
|
||||
className='w-full'
|
||||
onClick={() => {
|
||||
onSelect && onSelect(selectedEmoji, selectedBackground)
|
||||
}}>
|
||||
{t('app.emoji.ok')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal> : <>
|
||||
</>
|
||||
}
|
||||
export default EmojiPicker
|
||||
@ -0,0 +1,12 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 362px;
|
||||
max-height: 552px;
|
||||
|
||||
border: 0.5px solid #EAECF0;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
@ -1,222 +1,94 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import type { Provider } from '@/models/common'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { debounce } from 'lodash-es'
|
||||
import ProviderInput from '../provider-input'
|
||||
import Link from 'next/link'
|
||||
import useSWR from 'swr'
|
||||
import { ArrowTopRightOnSquareIcon, PencilIcon } from '@heroicons/react/24/outline'
|
||||
import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
||||
import Button from '@/app/components/base/button'
|
||||
import s from './index.module.css'
|
||||
import classNames from 'classnames'
|
||||
import { fetchTenantInfo, validateProviderKey, updateProviderAIKey } from '@/service/common'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Indicator from '../../../indicator'
|
||||
import I18n from '@/context/i18n'
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
||||
import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken'
|
||||
import {
|
||||
ValidatedErrorIcon,
|
||||
ValidatedSuccessIcon,
|
||||
ValidatingTip,
|
||||
ValidatedExceedOnOpenaiTip,
|
||||
ValidatedErrorOnOpenaiTip
|
||||
} from '../provider-input/Validate'
|
||||
|
||||
type IStatusType = 'normal' | 'verified' | 'error' | 'error-api-key-exceed-bill'
|
||||
|
||||
type TInputWithStatusProps = {
|
||||
value: string
|
||||
onChange: (v: string) => void
|
||||
onValidating: (validating: boolean) => void
|
||||
verifiedStatus: IStatusType
|
||||
onVerified: (verified: IStatusType) => void
|
||||
}
|
||||
const InputWithStatus = ({
|
||||
value,
|
||||
onChange,
|
||||
onValidating,
|
||||
verifiedStatus,
|
||||
onVerified
|
||||
}: TInputWithStatusProps) => {
|
||||
const { t } = useTranslation()
|
||||
const validateKey = useRef(debounce(async (token: string) => {
|
||||
if (!token) return
|
||||
onValidating(true)
|
||||
try {
|
||||
const res = await validateProviderKey({ url: '/workspaces/current/providers/openai/token-validate', body: { token } })
|
||||
onVerified(res.result === 'success' ? 'verified' : 'error')
|
||||
} catch (e: any) {
|
||||
if (e.status === 400) {
|
||||
e.json().then(({ code }: any) => {
|
||||
if (code === 'provider_request_failed') {
|
||||
onVerified('error-api-key-exceed-bill')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
onVerified('error')
|
||||
}
|
||||
} finally {
|
||||
onValidating(false)
|
||||
}
|
||||
}, 500))
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value
|
||||
onChange(inputValue)
|
||||
if (!inputValue) {
|
||||
onVerified('normal')
|
||||
}
|
||||
validateKey.current(inputValue)
|
||||
}
|
||||
return (
|
||||
<div className={classNames('flex items-center h-9 px-3 bg-white border border-gray-300 rounded-lg', s.input)}>
|
||||
<input
|
||||
value={value}
|
||||
placeholder={t('common.provider.enterYourKey') || ''}
|
||||
className='w-full h-9 mr-2 appearance-none outline-none bg-transparent text-xs'
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{
|
||||
verifiedStatus === 'error' && <ExclamationCircleIcon className='w-4 h-4 text-[#D92D20]' />
|
||||
}
|
||||
{
|
||||
verifiedStatus === 'verified' && <CheckCircleIcon className='w-4 h-4 text-[#039855]' />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
interface IOpenaiProviderProps {
|
||||
provider: Provider
|
||||
onValidatedStatus: (status?: ValidatedStatus) => void
|
||||
onTokenChange: (token: string) => void
|
||||
}
|
||||
|
||||
const OpenaiProvider = () => {
|
||||
const OpenaiProvider = ({
|
||||
provider,
|
||||
onValidatedStatus,
|
||||
onTokenChange
|
||||
}: IOpenaiProviderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const { data: userInfo, mutate } = useSWR({ url: '/info' }, fetchTenantInfo)
|
||||
const [inputValue, setInputValue] = useState<string>('')
|
||||
const [validating, setValidating] = useState(false)
|
||||
const [editStatus, setEditStatus] = useState<IStatusType>('normal')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [invalidStatus, setInvalidStatus] = useState(false)
|
||||
const { notify } = useContext(ToastContext)
|
||||
const provider = userInfo?.providers?.find(({ provider }) => provider === 'openai')
|
||||
|
||||
const handleReset = () => {
|
||||
setInputValue('')
|
||||
setValidating(false)
|
||||
setEditStatus('normal')
|
||||
setLoading(false)
|
||||
setEditing(false)
|
||||
const [token, setToken] = useState(provider.token as string || '')
|
||||
const [ validating, validatedStatus, setValidatedStatus, validate ] = useValidateToken(provider.provider_name)
|
||||
const handleFocus = () => {
|
||||
if (token === provider.token) {
|
||||
setToken('')
|
||||
onTokenChange('')
|
||||
setValidatedStatus(undefined)
|
||||
}
|
||||
}
|
||||
const handleSave = async () => {
|
||||
if (editStatus === 'verified') {
|
||||
try {
|
||||
setLoading(true)
|
||||
await updateProviderAIKey({ url: '/workspaces/current/providers/openai/token', body: { token: inputValue ?? '' } })
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
} catch (e) {
|
||||
notify({ type: 'error', message: t('common.provider.saveFailed') })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
handleReset()
|
||||
mutate()
|
||||
const handleChange = (v: string) => {
|
||||
setToken(v)
|
||||
onTokenChange(v)
|
||||
validate(v, {
|
||||
beforeValidating: () => {
|
||||
if (!v) {
|
||||
setValidatedStatus(undefined)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
if (provider && !provider.token_is_valid && provider.token_is_set) {
|
||||
setInvalidStatus(true)
|
||||
if (typeof onValidatedStatus === 'function') {
|
||||
onValidatedStatus(validatedStatus)
|
||||
}
|
||||
}, [userInfo])
|
||||
}, [validatedStatus])
|
||||
|
||||
const showInvalidStatus = invalidStatus && !editing
|
||||
const renderErrorMessage = () => {
|
||||
const getValidatedIcon = () => {
|
||||
if (validatedStatus === ValidatedStatus.Error || validatedStatus === ValidatedStatus.Exceed) {
|
||||
return <ValidatedErrorIcon />
|
||||
}
|
||||
if (validatedStatus === ValidatedStatus.Success) {
|
||||
return <ValidatedSuccessIcon />
|
||||
}
|
||||
}
|
||||
const getValidatedTip = () => {
|
||||
if (validating) {
|
||||
return (
|
||||
<div className={`mt-2 text-primary-600 text-xs font-normal`}>
|
||||
{t('common.provider.validating')}
|
||||
</div>
|
||||
)
|
||||
return <ValidatingTip />
|
||||
}
|
||||
if (editStatus === 'error-api-key-exceed-bill') {
|
||||
return (
|
||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
||||
{t('common.provider.apiKeyExceedBill')}
|
||||
<Link
|
||||
className='underline'
|
||||
href="https://platform.openai.com/account/api-keys"
|
||||
target={'_blank'}>
|
||||
{locale === 'en' ? 'this link' : '这篇文档'}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
if (validatedStatus === ValidatedStatus.Exceed) {
|
||||
return <ValidatedExceedOnOpenaiTip />
|
||||
}
|
||||
if (showInvalidStatus || editStatus === 'error') {
|
||||
return (
|
||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
||||
{t('common.provider.invalidKey')}
|
||||
</div>
|
||||
)
|
||||
if (validatedStatus === ValidatedStatus.Error) {
|
||||
return <ValidatedErrorOnOpenaiTip />
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='px-4 pt-3 pb-4'>
|
||||
<div className='flex items-center mb-2 h-6'>
|
||||
<div className='grow text-[13px] text-gray-800 font-medium'>
|
||||
{t('common.provider.apiKey')}
|
||||
</div>
|
||||
{
|
||||
provider && !editing && (
|
||||
<div
|
||||
className='
|
||||
flex items-center h-6 px-2 rounded-md border border-gray-200
|
||||
text-xs font-medium text-gray-700 cursor-pointer
|
||||
'
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
<PencilIcon className='mr-1 w-3 h-3 text-gray-500' />
|
||||
{t('common.operation.edit')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(inputValue || editing) && (
|
||||
<>
|
||||
<Button
|
||||
className={classNames('mr-1', s.button)}
|
||||
loading={loading}
|
||||
onClick={handleReset}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
className={classNames(s.button)}
|
||||
loading={loading}
|
||||
onClick={handleSave}>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
(!provider || (provider && editing)) && (
|
||||
<InputWithStatus
|
||||
value={inputValue}
|
||||
onChange={v => setInputValue(v)}
|
||||
verifiedStatus={editStatus}
|
||||
onVerified={v => setEditStatus(v)}
|
||||
onValidating={v => setValidating(v)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
(provider && !editing) && (
|
||||
<div className={classNames('flex justify-between items-center bg-white px-3 h-9 rounded-lg text-gray-800 text-xs font-medium', s.input)}>
|
||||
sk-0C...skuA
|
||||
<Indicator color={(provider.token_is_set && provider.token_is_valid) ? 'green' : 'orange'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{renderErrorMessage()}
|
||||
<Link className="inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit" href="https://platform.openai.com/account/api-keys" target={'_blank'}>
|
||||
{t('appOverview.welcome.getKeyTip')}
|
||||
<ArrowTopRightOnSquareIcon className='w-3 h-3 ml-1 text-primary-600' aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
<ProviderInput
|
||||
value={token}
|
||||
name={t('common.provider.apiKey')}
|
||||
placeholder={t('common.provider.enterYourKey')}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
validatedIcon={getValidatedIcon()}
|
||||
validatedTip={getValidatedTip()}
|
||||
/>
|
||||
<Link className="inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit" href="https://platform.openai.com/account/api-keys" target={'_blank'}>
|
||||
{t('appOverview.welcome.getKeyTip')}
|
||||
<ArrowTopRightOnSquareIcon className='w-3 h-3 ml-1 text-primary-600' aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
import type { Provider } from '@/models/common'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ProviderValidateTokenInput } from '../provider-input'
|
||||
import Link from 'next/link'
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
||||
import { ValidatedStatus } from '../provider-input/useValidateToken'
|
||||
|
||||
interface IOpenaiProviderProps {
|
||||
provider: Provider
|
||||
onValidatedStatus: (status?: ValidatedStatus) => void
|
||||
onTokenChange: (token: string) => void
|
||||
}
|
||||
|
||||
const OpenaiProvider = ({
|
||||
provider,
|
||||
onValidatedStatus,
|
||||
onTokenChange
|
||||
}: IOpenaiProviderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [token, setToken] = useState(provider.token as string || '')
|
||||
const handleFocus = () => {
|
||||
if (token === provider.token) {
|
||||
setToken('')
|
||||
onTokenChange('')
|
||||
}
|
||||
}
|
||||
const handleChange = (v: string) => {
|
||||
setToken(v)
|
||||
onTokenChange(v)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='px-4 pt-3 pb-4'>
|
||||
<ProviderValidateTokenInput
|
||||
value={token}
|
||||
name={t('common.provider.apiKey')}
|
||||
placeholder={t('common.provider.enterYourKey')}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onValidatedStatus={onValidatedStatus}
|
||||
providerName={provider.provider_name}
|
||||
/>
|
||||
<Link className="inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit" href="https://platform.openai.com/account/api-keys" target={'_blank'}>
|
||||
{t('appOverview.welcome.getKeyTip')}
|
||||
<ArrowTopRightOnSquareIcon className='w-3 h-3 ml-1 text-primary-600' aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenaiProvider
|
||||
@ -0,0 +1,59 @@
|
||||
import Link from 'next/link'
|
||||
import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import I18n from '@/context/i18n'
|
||||
|
||||
export const ValidatedErrorIcon = () => {
|
||||
return <ExclamationCircleIcon className='w-4 h-4 text-[#D92D20]' />
|
||||
}
|
||||
|
||||
export const ValidatedSuccessIcon = () => {
|
||||
return <CheckCircleIcon className='w-4 h-4 text-[#039855]' />
|
||||
}
|
||||
|
||||
export const ValidatingTip = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className={`mt-2 text-primary-600 text-xs font-normal`}>
|
||||
{t('common.provider.validating')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ValidatedExceedOnOpenaiTip = () => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
return (
|
||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
||||
{t('common.provider.apiKeyExceedBill')}
|
||||
<Link
|
||||
className='underline'
|
||||
href="https://platform.openai.com/account/api-keys"
|
||||
target={'_blank'}>
|
||||
{locale === 'en' ? 'this link' : '这篇文档'}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ValidatedErrorOnOpenaiTip = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
||||
{t('common.provider.invalidKey')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ValidatedErrorOnAzureOpenaiTip = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
||||
{t('common.provider.invalidApiKey')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue