add model market feature
@ -14,7 +14,7 @@ type AdapterProps struct {
|
||||
}
|
||||
|
||||
func HandleRequest(props *AdapterProps, hook globals.Hook) error {
|
||||
instance := NewChatInstanceFromConfig(props.Model)
|
||||
instance := NewChatInstanceFromConfig()
|
||||
return instance.CreateStreamChatRequest(&ChatProps{
|
||||
Model: instance.FormatModel(props.Model),
|
||||
Message: instance.FormatMessage(props.Message),
|
||||
|
@ -1,7 +1,6 @@
|
||||
package oneapi
|
||||
|
||||
import (
|
||||
"chat/globals"
|
||||
"chat/utils"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
@ -39,14 +38,7 @@ func NewChatInstance(endpoint, apiKey string) *ChatInstance {
|
||||
}
|
||||
}
|
||||
|
||||
func NewChatInstanceFromConfig(model string) *ChatInstance {
|
||||
if model == globals.Claude2100k {
|
||||
return NewChatInstance(
|
||||
viper.GetString("oneapi.claude.endpoint"),
|
||||
viper.GetString("oneapi.claude.apikey"),
|
||||
)
|
||||
}
|
||||
|
||||
func NewChatInstanceFromConfig() *ChatInstance {
|
||||
return NewChatInstance(
|
||||
viper.GetString("oneapi.endpoint"),
|
||||
viper.GetString("oneapi.apikey"),
|
||||
|
@ -44,6 +44,7 @@
|
||||
"react-router-dom": "^6.17.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"sort-by": "^1.2.0",
|
||||
|
89
app/pnpm-lock.yaml
generated
@ -104,6 +104,9 @@ dependencies:
|
||||
rehype-katex:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
remark-breaks:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
remark-gfm:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
@ -2853,6 +2856,12 @@ packages:
|
||||
'@types/unist': 2.0.9
|
||||
dev: false
|
||||
|
||||
/@types/mdast@4.0.3:
|
||||
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.9
|
||||
dev: false
|
||||
|
||||
/@types/ms@0.7.33:
|
||||
resolution: {integrity: sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==}
|
||||
dev: false
|
||||
@ -2904,6 +2913,10 @@ packages:
|
||||
resolution: {integrity: sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==}
|
||||
dev: false
|
||||
|
||||
/@types/unist@3.0.2:
|
||||
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
|
||||
dev: false
|
||||
|
||||
/@types/use-sync-external-store@0.0.3:
|
||||
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
|
||||
dev: false
|
||||
@ -3719,6 +3732,12 @@ packages:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
dev: false
|
||||
|
||||
/devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
dev: false
|
||||
|
||||
/didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
||||
@ -5068,6 +5087,15 @@ packages:
|
||||
unist-util-visit-parents: 5.1.3
|
||||
dev: false
|
||||
|
||||
/mdast-util-find-and-replace@3.0.1:
|
||||
resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
escape-string-regexp: 5.0.0
|
||||
unist-util-is: 6.0.0
|
||||
unist-util-visit-parents: 6.0.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-from-markdown@1.3.1:
|
||||
resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==}
|
||||
dependencies:
|
||||
@ -5151,6 +5179,13 @@ packages:
|
||||
mdast-util-to-markdown: 1.5.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-newline-to-break@2.0.0:
|
||||
resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
mdast-util-find-and-replace: 3.0.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-phrasing@3.0.1:
|
||||
resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==}
|
||||
dependencies:
|
||||
@ -6166,6 +6201,14 @@ packages:
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: true
|
||||
|
||||
/remark-breaks@4.0.0:
|
||||
resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
mdast-util-newline-to-break: 2.0.0
|
||||
unified: 11.0.4
|
||||
dev: false
|
||||
|
||||
/remark-gfm@3.0.1:
|
||||
resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
|
||||
dependencies:
|
||||
@ -6820,6 +6863,18 @@ packages:
|
||||
vfile: 5.3.7
|
||||
dev: false
|
||||
|
||||
/unified@11.0.4:
|
||||
resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
bail: 2.0.2
|
||||
devlop: 1.1.0
|
||||
extend: 3.0.2
|
||||
is-plain-obj: 4.1.0
|
||||
trough: 2.1.0
|
||||
vfile: 6.0.1
|
||||
dev: false
|
||||
|
||||
/unique-string@2.0.0:
|
||||
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
|
||||
engines: {node: '>=8'}
|
||||
@ -6844,6 +6899,12 @@ packages:
|
||||
'@types/unist': 2.0.9
|
||||
dev: false
|
||||
|
||||
/unist-util-is@6.0.0:
|
||||
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
dev: false
|
||||
|
||||
/unist-util-position@4.0.4:
|
||||
resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==}
|
||||
dependencies:
|
||||
@ -6863,6 +6924,12 @@ packages:
|
||||
'@types/unist': 2.0.9
|
||||
dev: false
|
||||
|
||||
/unist-util-stringify-position@4.0.0:
|
||||
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
dev: false
|
||||
|
||||
/unist-util-visit-parents@5.1.3:
|
||||
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
|
||||
dependencies:
|
||||
@ -6870,6 +6937,13 @@ packages:
|
||||
unist-util-is: 5.2.1
|
||||
dev: false
|
||||
|
||||
/unist-util-visit-parents@6.0.1:
|
||||
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
unist-util-is: 6.0.0
|
||||
dev: false
|
||||
|
||||
/unist-util-visit@4.1.2:
|
||||
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
|
||||
dependencies:
|
||||
@ -6972,6 +7046,13 @@ packages:
|
||||
unist-util-stringify-position: 3.0.3
|
||||
dev: false
|
||||
|
||||
/vfile-message@4.0.2:
|
||||
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
unist-util-stringify-position: 4.0.0
|
||||
dev: false
|
||||
|
||||
/vfile@5.3.7:
|
||||
resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==}
|
||||
dependencies:
|
||||
@ -6981,6 +7062,14 @@ packages:
|
||||
vfile-message: 3.1.4
|
||||
dev: false
|
||||
|
||||
/vfile@6.0.1:
|
||||
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
dev: false
|
||||
|
||||
/vite-plugin-html@3.2.0(vite@4.5.0):
|
||||
resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==}
|
||||
peerDependencies:
|
||||
|
BIN
app/public/icons/chatglm.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
app/public/icons/claude.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
app/public/icons/claude100k.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
app/public/icons/dalle.jpeg
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/public/icons/gpt35turbo.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
app/public/icons/gpt35turbo16k.webp
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app/public/icons/gpt4.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
app/public/icons/gpt432k.webp
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
app/public/icons/gpt4dalle.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
app/public/icons/gpt4v.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
app/public/icons/hunyuan.png
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
app/public/icons/llama2.webp
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
app/public/icons/llamacode.webp
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
app/public/icons/midjourney.jpg
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
app/public/icons/newbing.jpg
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
app/public/icons/palm2.webp
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/public/icons/sparkdesk.jpg
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
app/public/icons/stablediffusion.jpeg
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
app/public/icons/tongyi.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -50,6 +50,8 @@
|
||||
--chat-bot-background: rgba(88, 166, 255, .04);
|
||||
--chat-bot-border: rgba(88, 166, 255, .12);
|
||||
--chat-bot-border-hover: rgba(88, 166, 255, .24);
|
||||
|
||||
--model-card-background: rgba(222,214,200,.2);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@ -97,6 +99,8 @@
|
||||
--chat-bot-background: rgba(88, 166, 255, .1);
|
||||
--chat-bot-border: rgba(88, 166, 255, .2);
|
||||
--chat-bot-border-hover: rgba(88, 166, 255, .25);
|
||||
|
||||
--model-card-background: rgba(152,153,165,.05);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,237 @@
|
||||
background: hsl(var(--background-container));
|
||||
}
|
||||
|
||||
.model-market {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 1rem 3.5rem;
|
||||
height: max-content;
|
||||
max-height: calc(100vh - 56px);
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1rem;
|
||||
transform: translateY(-50%);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1rem;
|
||||
transform: translateY(-50%);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
opacity: 0;
|
||||
transition: 0.25s;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
opacity: .8;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-box {
|
||||
padding: 0 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.model-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.model-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
flex-wrap: wrap;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 0.5rem;
|
||||
border: 1px solid hsl(var(--border-hover));
|
||||
border-radius: var(--radius);
|
||||
transition: 0.25s;
|
||||
cursor: pointer;
|
||||
animation: fadein 0.25s forwards ease-in-out;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
width: calc(50% - 1rem);
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; transform: translateY(2.5rem); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: hsl(var(--border-active));
|
||||
padding: 1rem 1.5rem 1rem 2rem;
|
||||
|
||||
&:before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: hsl(var(--border-active));
|
||||
|
||||
&:before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background: var(--model-card-background);
|
||||
transition: 0.5s;
|
||||
z-index: -1;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.model-avatar {
|
||||
border-radius: var(--radius);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.model-action {
|
||||
button {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
animation: rotate 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
||||
}
|
||||
}
|
||||
|
||||
.model-name {
|
||||
&.pro {
|
||||
// gold color gradient
|
||||
background: linear-gradient(to right, hsl(45, 100%, 70%) 0%, hsl(46, 100%, 58%) 50%, hsl(46, 100%, 50%) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.model-tag {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.25rem;
|
||||
transform: translateY(0.25rem);
|
||||
|
||||
& > .tag-item {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.25rem;
|
||||
padding: 0.125rem 0.45rem;
|
||||
background: hsl(var(--input));
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.market-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
.close-action {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.market-footer {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: .5rem;
|
||||
user-select: none;
|
||||
padding: 0 1rem;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: hsl(var(--text-secondary));
|
||||
transition: 0.25s;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: hsl(var(--text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
color: hsl(var(--text));
|
||||
font-weight: bold !important;
|
||||
@ -33,12 +264,21 @@
|
||||
margin: 0;
|
||||
background: var(--background-sidebar);
|
||||
transition: 0.2s ease-in-out;
|
||||
transition-property: width, background, box-shadow;
|
||||
transition-property: width, background, box-shadow, border-right, opacity;
|
||||
border-right: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
&.open {
|
||||
width: 260px;
|
||||
border-right: 1px solid hsl(var(--border));
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
|
@ -3,6 +3,7 @@ import { atomOneDark as style } from "react-syntax-highlighter/dist/esm/styles/h
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import { parseFile } from "./plugins/file.tsx";
|
||||
import "@/assets/markdown/all.less";
|
||||
@ -57,7 +58,7 @@ function Markdown({ children, className }: MarkdownProps) {
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
remarkPlugins={[remarkMath, remarkGfm, remarkBreaks]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
className={`markdown-body ${className}`}
|
||||
children={children}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { mobile } from "@/utils/device.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Badge } from "./ui/badge.tsx";
|
||||
|
||||
export type SelectItemBadgeProps = {
|
||||
@ -19,6 +19,7 @@ export type SelectItemProps = {
|
||||
value: string;
|
||||
badge?: SelectItemBadgeProps;
|
||||
tag?: any;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
type SelectGroupProps = {
|
||||
@ -34,7 +35,8 @@ type SelectGroupProps = {
|
||||
|
||||
function GroupSelectItem(props: SelectItemProps) {
|
||||
return (
|
||||
<div className={`mr-1`}>
|
||||
<div className={`mr-1 flex flex-row items-center align-center`}>
|
||||
{props.icon && <div className={`mr-1`}>{props.icon}</div>}
|
||||
{props.value}
|
||||
{props.badge && (
|
||||
<Badge
|
||||
|
@ -82,15 +82,6 @@ function ChatSpace() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div className={`space-footer`}>
|
||||
<p>
|
||||
<Link className={`h-3 w-3 mr-1`} />
|
||||
<a
|
||||
href={`https://docs.chatnio.net/ai-mo-xing-ji-ji-fei`}
|
||||
target={`_blank`}
|
||||
>
|
||||
{t("pricing")}
|
||||
</a>
|
||||
</p>
|
||||
{cn && (
|
||||
<p>
|
||||
请您遵守
|
||||
|
@ -22,6 +22,20 @@ import SendButton from "@/components/home/assemblies/SendButton.tsx";
|
||||
import ChatInput from "@/components/home/assemblies/ChatInput.tsx";
|
||||
import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx";
|
||||
|
||||
type InterfaceProps = {
|
||||
setInstance: (instance: HTMLElement | null) => void;
|
||||
};
|
||||
|
||||
function Interface({ setInstance }: InterfaceProps) {
|
||||
const messages = useSelector(selectMessages);
|
||||
|
||||
return messages.length > 0 ? (
|
||||
<ChatInterface setTarget={setInstance} />
|
||||
) : (
|
||||
<ChatSpace />
|
||||
);
|
||||
}
|
||||
|
||||
function ChatWrapper() {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@ -32,7 +46,6 @@ function ChatWrapper() {
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
const model = useSelector(selectModel);
|
||||
const web = useSelector(selectWeb);
|
||||
const messages = useSelector(selectMessages);
|
||||
const target = useRef(null);
|
||||
const context = useSelector(contextSelector);
|
||||
const align = useSelector(alignSelector);
|
||||
@ -114,11 +127,7 @@ function ChatWrapper() {
|
||||
return (
|
||||
<div className={`chat-container`}>
|
||||
<div className={`chat-wrapper`}>
|
||||
{messages.length > 0 ? (
|
||||
<ChatInterface setTarget={setInstance} />
|
||||
) : (
|
||||
<ChatSpace />
|
||||
)}
|
||||
<Interface setInstance={setInstance} />
|
||||
<ScrollAction target={instance} />
|
||||
<div className={`chat-input`}>
|
||||
<div className={`input-wrapper`}>
|
||||
|
@ -13,6 +13,7 @@ import { useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ConversationInstance } from "@/conversation/types.ts";
|
||||
import { useState } from "react";
|
||||
import {closeMarket} from "@/store/chat.ts";
|
||||
|
||||
type ConversationSegmentProps = {
|
||||
conversation: ConversationInstance;
|
||||
@ -44,6 +45,7 @@ function ConversationSegment({
|
||||
return;
|
||||
await toggleConversation(dispatch, conversation.id);
|
||||
if (mobile) dispatch(setMenu(false));
|
||||
dispatch(closeMarket());
|
||||
}}
|
||||
>
|
||||
<MessageSquare className={`h-4 w-4 mr-1`} />
|
||||
|
@ -6,7 +6,12 @@ import {
|
||||
studentModels,
|
||||
supportModels,
|
||||
} from "@/conf.ts";
|
||||
import { selectModel, setModel } from "@/store/chat.ts";
|
||||
import {
|
||||
openMarket,
|
||||
selectModel,
|
||||
selectModelList,
|
||||
setModel,
|
||||
} from "@/store/chat.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectAuthenticated } from "@/store/auth.ts";
|
||||
@ -16,6 +21,8 @@ import { modelEvent } from "@/events/model.ts";
|
||||
import { isSubscribedSelector } from "@/store/subscription.ts";
|
||||
import { teenagerSelector } from "@/store/package.ts";
|
||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||
import { useMemo } from "react";
|
||||
import { Sparkles } from "lucide-react";
|
||||
|
||||
function GetModel(name: string): Model {
|
||||
return supportModels.find((model) => model.id === name) as Model;
|
||||
@ -25,6 +32,34 @@ type ModelSelectorProps = {
|
||||
side?: "left" | "right" | "top" | "bottom";
|
||||
};
|
||||
|
||||
function filterModel(model: Model, subscription: boolean, student: boolean) {
|
||||
if (subscription && planModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "plus" },
|
||||
} as SelectItemProps;
|
||||
} else if (student && studentModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "student" },
|
||||
} as SelectItemProps;
|
||||
} else if (expensiveModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "expensive" },
|
||||
} as SelectItemProps;
|
||||
}
|
||||
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: model.free && { variant: "default", name: "free" },
|
||||
} as SelectItemProps;
|
||||
}
|
||||
|
||||
function ModelFinder(props: ModelSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
@ -34,6 +69,7 @@ function ModelFinder(props: ModelSelectorProps) {
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
const subscription = useSelector(isSubscribedSelector);
|
||||
const student = useSelector(teenagerSelector);
|
||||
const list = useSelector(selectModelList);
|
||||
|
||||
modelEvent.bind((target: string) => {
|
||||
if (supportModels.find((m) => m.id === target)) {
|
||||
@ -43,42 +79,33 @@ function ModelFinder(props: ModelSelectorProps) {
|
||||
}
|
||||
});
|
||||
|
||||
const list = supportModels.map((model: Model): SelectItemProps => {
|
||||
if (subscription && planModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "plus" },
|
||||
} as SelectItemProps;
|
||||
} else if (student && studentModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "student" },
|
||||
} as SelectItemProps;
|
||||
} else if (expensiveModels.includes(model.id)) {
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: { variant: "gold", name: "expensive" },
|
||||
} as SelectItemProps;
|
||||
}
|
||||
|
||||
return {
|
||||
name: model.id,
|
||||
value: model.name,
|
||||
badge: model.free && { variant: "default", name: "free" },
|
||||
} as SelectItemProps;
|
||||
});
|
||||
const models = useMemo(() => {
|
||||
const raw = supportModels.filter((model) => list.includes(model.id));
|
||||
return [
|
||||
...raw.map(
|
||||
(model: Model): SelectItemProps =>
|
||||
filterModel(model, subscription, student),
|
||||
),
|
||||
{
|
||||
icon: <Sparkles size={16} />,
|
||||
name: "market",
|
||||
value: t("market.model"),
|
||||
},
|
||||
];
|
||||
}, [supportModels, subscription, student]);
|
||||
|
||||
return (
|
||||
<SelectGroup
|
||||
current={list.find((item) => item.name === model) as SelectItemProps}
|
||||
list={list}
|
||||
current={models.find((item) => item.name === model) as SelectItemProps}
|
||||
list={models}
|
||||
maxElements={5}
|
||||
side={props.side}
|
||||
classNameMobile={`model-select-group`}
|
||||
onChange={(value: string) => {
|
||||
if (value === "market") {
|
||||
dispatch(openMarket());
|
||||
return;
|
||||
}
|
||||
const model = GetModel(value);
|
||||
console.debug(`[model] select model: ${model.name} (id: ${model.id})`);
|
||||
|
||||
|
204
app/src/components/home/ModelMarket.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import {ChevronLeft, ChevronRight, Link, Plus, Search, Trash2, X} from "lucide-react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {modelAvatars, modelPricingLink, planModels, studentModels, supportModels} from "@/conf.ts";
|
||||
import { splitList } from "@/utils/base.ts";
|
||||
import { Model } from "@/conversation/types.ts";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {addModelList, closeMarket, removeModelList, selectModel, selectModelList, setModel} from "@/store/chat.ts";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import {isSubscribedSelector} from "@/store/subscription.ts";
|
||||
import {teenagerSelector} from "@/store/package.ts";
|
||||
|
||||
type SearchBarProps = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
function SearchBar({ value, onChange }: SearchBarProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`search-bar`}>
|
||||
<Search size={16} className={`search-icon`} />
|
||||
<Input
|
||||
placeholder={t("market.search")}
|
||||
className={`rounded-full input-box`}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
<X
|
||||
size={16}
|
||||
className={`clear-icon ${value.length > 0 ? "active" : ""}`}
|
||||
onClick={() => onChange("")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ModelProps = {
|
||||
model: Model;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
function ModelItem({ model, className, style }: ModelProps) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const list = useSelector(selectModelList);
|
||||
const current = useSelector(selectModel);
|
||||
|
||||
const subscription = useSelector(isSubscribedSelector);
|
||||
const student = useSelector(teenagerSelector);
|
||||
|
||||
const state = useMemo(() => {
|
||||
if (current === model.id) return 0;
|
||||
if (list.includes(model.id)) return 1;
|
||||
return 2;
|
||||
}, [model, current, list]);
|
||||
|
||||
const pro = useMemo(() => {
|
||||
if (subscription && planModels.includes(model.id)) return true;
|
||||
if (student && studentModels.includes(model.id)) return true;
|
||||
return false;
|
||||
}, [model, subscription, student]);
|
||||
|
||||
const avatar = useMemo(() => {
|
||||
const source = modelAvatars[model.id] || modelAvatars[supportModels[0].id];
|
||||
return `/icons/${source}`;
|
||||
}, [model]);
|
||||
|
||||
return (
|
||||
<div className={`model-item ${className}`} style={style} onClick={() => {
|
||||
dispatch(addModelList(model.id));
|
||||
dispatch(setModel(model.id));
|
||||
dispatch(closeMarket());
|
||||
}}>
|
||||
<img className={`model-avatar`} src={avatar} alt={model.name} />
|
||||
<div className={`model-info`}>
|
||||
<p className={`model-name ${pro ? 'pro' : ''}`}>{model.name}</p>
|
||||
<div className={`model-tag`}>
|
||||
{model.tag &&
|
||||
model.tag.map((tag, index) => {
|
||||
return (
|
||||
<span className={`tag-item`} key={index}>
|
||||
{t(`tag.${tag}`)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`model-action`}>
|
||||
<Button size={`icon`} variant={`ghost`} className={`scale-90`} onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (state === 0) dispatch(closeMarket());
|
||||
else if (state === 1) dispatch(removeModelList(model.id));
|
||||
else dispatch(addModelList(model.id));
|
||||
}}>
|
||||
{
|
||||
state === 0 ? (
|
||||
<ChevronRight className={`h-4 w-4`} />
|
||||
) : (
|
||||
state === 1 ? (
|
||||
<Trash2 className={`w-4 h-4`} />
|
||||
) : (
|
||||
<Plus className={`w-4 h-4`} />
|
||||
)
|
||||
)
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type MarketPlaceProps = {
|
||||
search: string;
|
||||
};
|
||||
|
||||
function MarketPlace({ search }: MarketPlaceProps) {
|
||||
const { t } = useTranslation();
|
||||
const select = useSelector(selectModel);
|
||||
|
||||
const arr = useMemo(() => {
|
||||
if (search.length === 0) return supportModels;
|
||||
// fuzzy search
|
||||
const raw = splitList(search.toLowerCase(), [" ", ",", ";", "-"]);
|
||||
return supportModels.filter((model) => {
|
||||
const name = model.name.toLowerCase();
|
||||
const tag = (model.tag || []).join(" ").toLowerCase();
|
||||
const tag_translated = (model.tag || [])
|
||||
.map((item) => t(`tag.${item}`))
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
return raw.every(
|
||||
(item) =>
|
||||
name.includes(item) ||
|
||||
tag.includes(item) ||
|
||||
tag_translated.includes(item),
|
||||
);
|
||||
});
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<div className={`model-list`}>
|
||||
{arr.map((model, index) => (
|
||||
<ModelItem
|
||||
model={model}
|
||||
key={index}
|
||||
className={`${select === model.id ? "active" : ""}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MarketHeader() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div className={`market-header`}>
|
||||
<Button size={`icon`} variant={`ghost`} className={`close-action`} onClick={() => {
|
||||
dispatch(closeMarket());
|
||||
}}>
|
||||
<ChevronLeft className={`h-4 w-4`} />
|
||||
</Button>
|
||||
<p className={`title select-none text-center text-primary font-bold`}>
|
||||
{t("market.explore")}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MarketFooter() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`market-footer`}>
|
||||
<a href={modelPricingLink} target={`_blank`}>
|
||||
<Link size={14} className={`mr-1`} />
|
||||
{t("pricing")}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelMarket() {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
return (
|
||||
<div className={`model-market`}>
|
||||
<MarketHeader />
|
||||
<SearchBar value={search} onChange={setSearch} />
|
||||
<MarketPlace search={search} />
|
||||
<MarketFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelMarket;
|
@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
||||
import { selectCurrent, selectHistory } from "@/store/chat.ts";
|
||||
import {closeMarket, selectCurrent, selectHistory} from "@/store/chat.ts";
|
||||
import { useRef, useState } from "react";
|
||||
import { ConversationInstance } from "@/conversation/types.ts";
|
||||
import { useToast } from "@/components/ui/use-toast.ts";
|
||||
@ -72,6 +72,7 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
|
||||
onClick={async () => {
|
||||
await toggleConversation(dispatch, -1);
|
||||
if (mobile) dispatch(setMenu(false));
|
||||
dispatch(closeMarket());
|
||||
}}
|
||||
>
|
||||
<Plus className={`h-4 w-4`} />
|
||||
|
319
app/src/conf.ts
@ -8,7 +8,7 @@ import {
|
||||
} from "@/utils/env.ts";
|
||||
import { getMemory } from "@/utils/memory.ts";
|
||||
|
||||
export const version = "3.6.23";
|
||||
export const version = "3.6.24";
|
||||
export const dev: boolean = getDev();
|
||||
export const deploy: boolean = true;
|
||||
export let rest_api: string = getRestApi(deploy);
|
||||
@ -18,46 +18,173 @@ export const tokenField = getTokenField(deploy);
|
||||
|
||||
export const supportModels: Model[] = [
|
||||
// openai models
|
||||
{ id: "gpt-3.5-turbo-0613", name: "GPT-3.5", free: true, auth: false },
|
||||
{ id: "gpt-3.5-turbo-16k-0613", name: "GPT-3.5-16k", free: true, auth: true },
|
||||
{ id: "gpt-3.5-turbo-1106", name: "GPT-3.5 1106", free: true, auth: false },
|
||||
{ id: "gpt-4-0613", name: "GPT-4", free: false, auth: true },
|
||||
{ id: "gpt-4-1106-preview", name: "GPT-4 Turbo", free: false, auth: true },
|
||||
{
|
||||
id: "gpt-3.5-turbo-0613",
|
||||
name: "GPT-3.5",
|
||||
free: true,
|
||||
auth: false,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-16k-0613",
|
||||
name: "GPT-3.5-16k",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "official", "high-context"],
|
||||
},
|
||||
{
|
||||
id: "gpt-3.5-turbo-1106",
|
||||
name: "GPT-3.5 1106",
|
||||
free: true,
|
||||
auth: false,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-0613",
|
||||
name: "GPT-4",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-1106-preview",
|
||||
name: "GPT-4 Turbo",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-context"],
|
||||
},
|
||||
|
||||
// anthropic models
|
||||
{ id: "claude-1-100k", name: "Claude-2", free: true, auth: true },
|
||||
{ id: "claude-2", name: "Claude-2-100k", free: false, auth: true },
|
||||
{
|
||||
id: "claude-1-100k",
|
||||
name: "Claude",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free"],
|
||||
},
|
||||
{
|
||||
id: "claude-2",
|
||||
name: "Claude 100k",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-context"],
|
||||
},
|
||||
|
||||
// spark desk
|
||||
{ id: "spark-desk-v3", name: "讯飞星火 V3", free: true, auth: true },
|
||||
{ id: "spark-desk-v2", name: "讯飞星火 V2", free: true, auth: true },
|
||||
{ id: "spark-desk-v1.5", name: "讯飞星火 V1.5", free: true, auth: true },
|
||||
{
|
||||
id: "spark-desk-v3",
|
||||
name: "讯飞星火 V3",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "spark-desk-v2",
|
||||
name: "讯飞星火 V2",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
{
|
||||
id: "spark-desk-v1.5",
|
||||
name: "讯飞星火 V1.5",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "official"],
|
||||
},
|
||||
|
||||
// dashscope models
|
||||
{ id: "qwen-plus-net", name: "通义千问 Plus X", free: false, auth: true },
|
||||
{ id: "qwen-plus", name: "通义千问 Plus", free: false, auth: true },
|
||||
{ id: "qwen-turbo-net", name: "通义千问 Turbo X", free: false, auth: true },
|
||||
{ id: "qwen-turbo", name: "通义千问 Turbo", free: false, auth: true },
|
||||
{
|
||||
id: "qwen-plus-net",
|
||||
name: "通义千问 Plus Net",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-quality", "web"],
|
||||
},
|
||||
{
|
||||
id: "qwen-plus",
|
||||
name: "通义千问 Plus",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-quality"],
|
||||
},
|
||||
{
|
||||
id: "qwen-turbo-net",
|
||||
name: "通义千问 Turbo Net",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "web"],
|
||||
},
|
||||
{
|
||||
id: "qwen-turbo",
|
||||
name: "通义千问 Turbo",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// huyuan models
|
||||
{ id: "hunyuan", name: "腾讯混元 Pro", free: false, auth: true },
|
||||
{
|
||||
id: "hunyuan",
|
||||
name: "腾讯混元 Pro",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official"],
|
||||
},
|
||||
|
||||
// zhipu models
|
||||
{
|
||||
id: "zhipu-chatglm-turbo",
|
||||
name: "ChatGLM Turbo 32k",
|
||||
name: "ChatGLM Turbo",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-context"],
|
||||
},
|
||||
|
||||
// llama models
|
||||
{ id: "llama-2-70b", name: "LLaMa-2 70B", free: false, auth: true },
|
||||
{ id: "llama-2-13b", name: "LLaMa-2 13B", free: false, auth: true },
|
||||
{ id: "llama-2-7b", name: "LLaMa-2 7B", free: false, auth: true },
|
||||
{
|
||||
id: "llama-2-70b",
|
||||
name: "LLaMa-2 70B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "llama-2-13b",
|
||||
name: "LLaMa-2 13B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "llama-2-7b",
|
||||
name: "LLaMa-2 7B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
|
||||
{ id: "code-llama-34b", name: "Code LLaMa 34B", free: false, auth: true },
|
||||
{ id: "code-llama-13b", name: "Code LLaMa 13B", free: false, auth: true },
|
||||
{ id: "code-llama-7b", name: "Code LLaMa 7B", free: false, auth: true },
|
||||
{
|
||||
id: "code-llama-34b",
|
||||
name: "Code LLaMa 34B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "code-llama-13b",
|
||||
name: "Code LLaMa 13B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "code-llama-7b",
|
||||
name: "Code LLaMa 7B",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable"],
|
||||
},
|
||||
|
||||
// drawing models
|
||||
{
|
||||
@ -65,28 +192,110 @@ export const supportModels: Model[] = [
|
||||
name: "Stable Diffusion XL",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["open-source", "unstable", "image-generation"],
|
||||
},
|
||||
|
||||
// new bing
|
||||
{ id: "bing-creative", name: "New Bing", free: true, auth: true },
|
||||
{
|
||||
id: "bing-creative",
|
||||
name: "New Bing",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "unstable", "web"],
|
||||
},
|
||||
|
||||
// google palm2
|
||||
{ id: "chat-bison-001", name: "Palm2", free: true, auth: true },
|
||||
{
|
||||
id: "chat-bison-001",
|
||||
name: "Google PaLM2",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "english-model"],
|
||||
},
|
||||
|
||||
// dalle models
|
||||
{ id: "dall-e-3", name: "DALLE 3", free: false, auth: true },
|
||||
{ id: "dall-e-2", name: "DALLE 2", free: true, auth: true },
|
||||
{
|
||||
id: "dall-e-3",
|
||||
name: "DALLE 3",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-price", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "dall-e-2",
|
||||
name: "DALLE 2",
|
||||
free: true,
|
||||
auth: true,
|
||||
tag: ["free", "official", "image-generation"],
|
||||
},
|
||||
|
||||
{ id: "midjourney", name: "Midjourney", free: false, auth: true },
|
||||
{ id: "midjourney-fast", name: "Midjourney Fast", free: false, auth: true },
|
||||
{ id: "midjourney-turbo", name: "Midjourney Turbo", free: false, auth: true },
|
||||
{
|
||||
id: "midjourney",
|
||||
name: "Midjourney Queue",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "midjourney-fast",
|
||||
name: "Midjourney",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "fast", "image-generation"],
|
||||
},
|
||||
{
|
||||
id: "midjourney-turbo",
|
||||
name: "Midjourney Turbo",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "fast", "image-generation"],
|
||||
},
|
||||
|
||||
// reverse models
|
||||
{ id: "gpt-4-v", name: "GPT-4 Vision", free: false, auth: true },
|
||||
{ id: "gpt-4-dalle", name: "GPT-4 DALLE", free: false, auth: true },
|
||||
{
|
||||
id: "gpt-4-v",
|
||||
name: "GPT-4 Vision",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "unstable", "multi-modal"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-dalle",
|
||||
name: "GPT-4 DALLE",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "unstable", "image-generation"],
|
||||
},
|
||||
|
||||
// high price models
|
||||
{ id: "gpt-4-32k-0613", name: "GPT-4-32k", free: false, auth: true },
|
||||
{
|
||||
id: "gpt-4-32k-0613",
|
||||
name: "GPT-4-32k",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-quality", "high-price"],
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultModels = [
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
|
||||
"gpt-4-v",
|
||||
"gpt-4-dalle",
|
||||
|
||||
"claude-1-100k",
|
||||
"claude-2",
|
||||
|
||||
"spark-desk-v3",
|
||||
"qwen-plus",
|
||||
"hunyuan",
|
||||
"zhipu-chatglm-turbo",
|
||||
|
||||
"dall-e-2",
|
||||
"midjourney-fast",
|
||||
"stable-diffusion",
|
||||
];
|
||||
|
||||
export const largeContextModels = [
|
||||
@ -101,7 +310,7 @@ export const largeContextModels = [
|
||||
"zhipu-chatglm-turbo",
|
||||
];
|
||||
|
||||
export const studentModels = ["claude-1-100k", "claude-2-100k", "claude-2"];
|
||||
export const studentModels = ["claude-2-100k", "claude-2"];
|
||||
|
||||
export const planModels = [
|
||||
"gpt-4-0613",
|
||||
@ -110,7 +319,6 @@ export const planModels = [
|
||||
"gpt-4-all",
|
||||
"gpt-4-dalle",
|
||||
"claude-2",
|
||||
"claude-1-100k",
|
||||
"claude-2-100k",
|
||||
];
|
||||
|
||||
@ -120,6 +328,45 @@ export const expensiveModels = [
|
||||
"gpt-4-32k-0613",
|
||||
];
|
||||
|
||||
export const modelAvatars: Record<string, string> = {
|
||||
"gpt-3.5-turbo-0613": "gpt35turbo.png",
|
||||
"gpt-3.5-turbo-16k-0613": "gpt35turbo16k.webp",
|
||||
"gpt-3.5-turbo-1106": "gpt35turbo16k.webp",
|
||||
"gpt-4-0613": "gpt4.png",
|
||||
"gpt-4-1106-preview": "gpt432k.webp",
|
||||
"gpt-4-all": "gpt4.png",
|
||||
"gpt-4-32k-0613": "gpt432k.webp",
|
||||
"gpt-4-v": "gpt4v.png",
|
||||
"gpt-4-dalle": "gpt4dalle.png",
|
||||
"claude-1-100k": "claude.png",
|
||||
"claude-2": "claude100k.png",
|
||||
"stable-diffusion": "stablediffusion.jpeg",
|
||||
"llama-2-70b": "llama2.webp",
|
||||
"llama-2-13b": "llama2.webp",
|
||||
"llama-2-7b": "llama2.webp",
|
||||
"code-llama-34b": "llamacode.webp",
|
||||
"code-llama-13b": "llamacode.webp",
|
||||
"code-llama-7b": "llamacode.webp",
|
||||
"dall-e-3": "dalle.jpeg",
|
||||
"dall-e-2": "dalle.jpeg",
|
||||
midjourney: "midjourney.jpg",
|
||||
"midjourney-fast": "midjourney.jpg",
|
||||
"midjourney-turbo": "midjourney.jpg",
|
||||
"bing-creative": "newbing.jpg",
|
||||
"chat-bison-001": "palm2.webp",
|
||||
"zhipu-chatglm-turbo": "chatglm.png",
|
||||
"qwen-plus-net": "tongyi.png",
|
||||
"qwen-plus": "tongyi.png",
|
||||
"qwen-turbo-net": "tongyi.png",
|
||||
"qwen-turbo": "tongyi.png",
|
||||
"spark-desk-v3": "sparkdesk.jpg",
|
||||
"spark-desk-v2": "sparkdesk.jpg",
|
||||
"spark-desk-v1.5": "sparkdesk.jpg",
|
||||
hunyuan: "hunyuan.png",
|
||||
};
|
||||
|
||||
export const modelPricingLink = "https://docs.chatnio.net/ai-mo-xing-ji-ji-fei";
|
||||
|
||||
export function login() {
|
||||
location.href = `https://deeptrain.net/login?app=${dev ? "dev" : "chatnio"}`;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ export type Model = {
|
||||
name: string;
|
||||
free: boolean;
|
||||
auth: boolean;
|
||||
tag?: string[];
|
||||
};
|
||||
|
||||
export type Id = number;
|
||||
|
@ -55,7 +55,7 @@ import { useEffectAsync } from "@/utils/hook.ts";
|
||||
import { selectAuthenticated } from "@/store/auth.ts";
|
||||
|
||||
function calc_prize(month: number): number {
|
||||
const base = 32 * month;
|
||||
const base = 56 * month;
|
||||
if (month >= 36) {
|
||||
return base * 0.7;
|
||||
} else if (month >= 12) {
|
||||
|
@ -27,9 +27,28 @@ const resources = {
|
||||
"request-failed": "请求失败,请检查您的网络并重试。",
|
||||
close: "关闭",
|
||||
edit: "编辑",
|
||||
pricing: "模型定价表",
|
||||
pricing: "更多计费详情参见模型定价表",
|
||||
true: "是",
|
||||
false: "否",
|
||||
tag: {
|
||||
free: "免费",
|
||||
official: "官方",
|
||||
unstable: "不稳定",
|
||||
web: "联网",
|
||||
"high-quality": "高质量",
|
||||
"high-context": "高上下文",
|
||||
"high-price": "高定价",
|
||||
"open-source": "开源",
|
||||
"image-generation": "绘图",
|
||||
"multi-modal": "多模态",
|
||||
fast: "快速",
|
||||
"english-model": "英文模型",
|
||||
},
|
||||
market: {
|
||||
model: "探索更多模型",
|
||||
explore: "探索",
|
||||
search: "搜索模型名称或者简介",
|
||||
},
|
||||
conversation: {
|
||||
title: "对话",
|
||||
empty: "空空如也",
|
||||
@ -105,14 +124,14 @@ const resources = {
|
||||
free: "免费版",
|
||||
"free-price": "永久免费",
|
||||
pro: "专业版",
|
||||
"pro-price": "32 元/月",
|
||||
"pro-price": "56 元/月",
|
||||
"free-gpt3": "GPT-3.5 (16k) 永久免费",
|
||||
"free-dalle": "DALL·E 2 绘图永久免费",
|
||||
"free-web": "联网搜索功能",
|
||||
"free-conversation": "对话存储记录",
|
||||
"free-api": "API 调用",
|
||||
"pro-gpt4": "GPT-4 每日请求 100 次",
|
||||
"pro-gpt4-desc": "(包含 GPT 4 Turbo, GPT 4V, DALL·E 3)",
|
||||
"pro-gpt4-desc": "(包含 GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
|
||||
"pro-claude": "Claude 100k 每日请求 100 次",
|
||||
"pro-service": "优先服务支持",
|
||||
"pro-thread": "并发数提升",
|
||||
@ -315,7 +334,7 @@ const resources = {
|
||||
"Request failed. Please check your network and try again.",
|
||||
close: "Close",
|
||||
edit: "Edit",
|
||||
pricing: "Model Pricing",
|
||||
pricing: "See model pricing for more details",
|
||||
true: "Yes",
|
||||
false: "No",
|
||||
conversation: {
|
||||
@ -398,14 +417,14 @@ const resources = {
|
||||
free: "Free",
|
||||
"free-price": "Free Forever",
|
||||
pro: "Pro",
|
||||
"pro-price": "32 CNY/Month",
|
||||
"pro-price": "56 CNY/Month",
|
||||
"free-gpt3": "GPT-3.5 (16k) Free Forever",
|
||||
"free-dalle": "DALL·E 2 Free Forever",
|
||||
"free-web": "web searching feature",
|
||||
"free-conversation": "conversation storage",
|
||||
"free-api": "API calls",
|
||||
"pro-gpt4": "GPT-4 100 requests per day",
|
||||
"pro-gpt4-desc": "(including GPT 4 Turbo, GPT 4V, DALL·E 3)",
|
||||
"pro-gpt4-desc": "(including GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
|
||||
"pro-claude": "Claude 100k 100 requests per day",
|
||||
"pro-service": "Priority Service Support",
|
||||
"pro-thread": "Concurrency Increase",
|
||||
@ -618,7 +637,8 @@ const resources = {
|
||||
"Ошибка запроса. Пожалуйста, проверьте свою сеть и попробуйте еще раз.",
|
||||
close: "Закрыть",
|
||||
edit: "Редактировать",
|
||||
pricing: "Тарифы моделей",
|
||||
pricing:
|
||||
"См. ценообразование моделей для получения дополнительной информации",
|
||||
true: "Да",
|
||||
false: "Нет",
|
||||
conversation: {
|
||||
@ -702,14 +722,14 @@ const resources = {
|
||||
free: "Бесплатно",
|
||||
"free-price": "Бесплатно навсегда",
|
||||
pro: "Профессиональный",
|
||||
"pro-price": "32 CNY/месяц",
|
||||
"pro-price": "56 CNY/месяц",
|
||||
"free-gpt3": "GPT-3.5 (16k) бесплатно навсегда",
|
||||
"free-dalle": "DALE·E 2 бесплатно навсегда",
|
||||
"free-web": "веб-поиск",
|
||||
"free-conversation": "хранение разговоров",
|
||||
"free-api": "API вызовы",
|
||||
"pro-gpt4": "GPT-4 100 запросов в день",
|
||||
"pro-gpt4-desc": "(включая GPT 4 Turbo, GPT 4V, DALL·E 3)",
|
||||
"pro-gpt4-desc": "(включая GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
|
||||
"pro-claude": "Claude 100k 100 запросов в день",
|
||||
"pro-service": "Приоритетная служба поддержки",
|
||||
"pro-thread": "Увеличение параллелизма",
|
||||
|
@ -2,12 +2,17 @@ import "@/assets/pages/home.less";
|
||||
import "@/assets/pages/chat.less";
|
||||
import ChatWrapper from "@/components/home/ChatWrapper.tsx";
|
||||
import SideBar from "@/components/home/SideBar.tsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectMarket } from "@/store/chat.ts";
|
||||
import ModelMarket from "@/components/home/ModelMarket.tsx";
|
||||
|
||||
function Home() {
|
||||
const market = useSelector(selectMarket);
|
||||
|
||||
return (
|
||||
<div className={`main`}>
|
||||
<SideBar />
|
||||
<ChatWrapper />
|
||||
{market ? <ModelMarket /> : <ChatWrapper />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InvitationTable from "@/components/admin/InvitationTable.tsx";
|
||||
import UserTable from "@/components/admin/UserTable.tsx";
|
||||
import {mobile} from "@/utils/device.ts";
|
||||
import { mobile } from "@/utils/device.ts";
|
||||
|
||||
function Users() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`user-interface ${mobile ? 'mobile' : ''}`}>
|
||||
<div className={`user-interface ${mobile ? "mobile" : ""}`}>
|
||||
<Card>
|
||||
<CardHeader className={`select-none`}>
|
||||
<CardTitle>{t("admin.users")}</CardTitle>
|
||||
|
@ -3,7 +3,7 @@ import { ConversationInstance, Model } from "@/conversation/types.ts";
|
||||
import { Message } from "@/conversation/types.ts";
|
||||
import { insertStart } from "@/utils/base.ts";
|
||||
import { RootState } from "./index.ts";
|
||||
import { supportModels } from "@/conf.ts";
|
||||
import { defaultModels, supportModels } from "@/conf.ts";
|
||||
import { getMemory, setMemory } from "@/utils/memory.ts";
|
||||
|
||||
type initialStateType = {
|
||||
@ -12,13 +12,25 @@ type initialStateType = {
|
||||
model: string;
|
||||
web: boolean;
|
||||
current: number;
|
||||
model_list: string[];
|
||||
market: boolean;
|
||||
};
|
||||
|
||||
function InModel(model: string): boolean {
|
||||
return (
|
||||
model.length > 0 &&
|
||||
supportModels.filter((item: Model) => item.id === model).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function GetModel(model: string | undefined | null): string {
|
||||
return model &&
|
||||
supportModels.filter((item: Model) => item.id === model).length
|
||||
? model
|
||||
: supportModels[0].id;
|
||||
return model && InModel(model) ? model : supportModels[0].id;
|
||||
}
|
||||
|
||||
function GetModelList(models: string | undefined | null): string[] {
|
||||
return models && models.length
|
||||
? models.split(",").filter((item) => InModel(item))
|
||||
: defaultModels;
|
||||
}
|
||||
|
||||
const chatSlice = createSlice({
|
||||
@ -29,6 +41,8 @@ const chatSlice = createSlice({
|
||||
model: GetModel(getMemory("model")),
|
||||
web: getMemory("web") === "true",
|
||||
current: -1,
|
||||
model_list: GetModelList(getMemory("model_list")),
|
||||
market: false,
|
||||
} as initialStateType,
|
||||
reducers: {
|
||||
setHistory: (state, action) => {
|
||||
@ -69,6 +83,33 @@ const chatSlice = createSlice({
|
||||
setMessage: (state, action) => {
|
||||
state.messages[state.messages.length - 1] = action.payload as Message;
|
||||
},
|
||||
setModelList: (state, action) => {
|
||||
setMemory("model_list", action.payload as string);
|
||||
state.model_list = action.payload as string[];
|
||||
},
|
||||
addModelList: (state, action) => {
|
||||
const model = action.payload as string;
|
||||
if (InModel(model) && !state.model_list.includes(model)) {
|
||||
state.model_list.push(model);
|
||||
setMemory("model_list", state.model_list.join(","));
|
||||
}
|
||||
},
|
||||
removeModelList: (state, action) => {
|
||||
const model = action.payload as string;
|
||||
if (InModel(model) && state.model_list.includes(model)) {
|
||||
state.model_list = state.model_list.filter((item) => item !== model);
|
||||
setMemory("model_list", state.model_list.join(","));
|
||||
}
|
||||
},
|
||||
setMarket: (state, action) => {
|
||||
state.market = action.payload as boolean;
|
||||
},
|
||||
openMarket: (state) => {
|
||||
state.market = true;
|
||||
},
|
||||
closeMarket: (state) => {
|
||||
state.market = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -82,6 +123,12 @@ export const {
|
||||
setWeb,
|
||||
addMessage,
|
||||
setMessage,
|
||||
setModelList,
|
||||
addModelList,
|
||||
removeModelList,
|
||||
setMarket,
|
||||
openMarket,
|
||||
closeMarket,
|
||||
} = chatSlice.actions;
|
||||
export const selectHistory = (state: RootState): ConversationInstance[] =>
|
||||
state.chat.history;
|
||||
@ -90,5 +137,8 @@ export const selectMessages = (state: RootState): Message[] =>
|
||||
export const selectModel = (state: RootState): string => state.chat.model;
|
||||
export const selectWeb = (state: RootState): boolean => state.chat.web;
|
||||
export const selectCurrent = (state: RootState): number => state.chat.current;
|
||||
export const selectModelList = (state: RootState): string[] =>
|
||||
state.chat.model_list;
|
||||
export const selectMarket = (state: RootState): boolean => state.chat.market;
|
||||
|
||||
export default chatSlice.reducer;
|
||||
|
@ -34,3 +34,11 @@ export function getNumber(value: string, supportNegative = true): string {
|
||||
export function parseNumber(value: string): number {
|
||||
return parseFloat(getNumber(value));
|
||||
}
|
||||
|
||||
export function splitList(value: string, separators: string[]): string[] {
|
||||
const result: string[] = [];
|
||||
for (const item of value.split(new RegExp(separators.join("|"), "g"))) {
|
||||
if (item) result.push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func CountSubscriptionPrize(month int) float32 {
|
||||
base := 32 * float32(month)
|
||||
base := 56 * float32(month)
|
||||
if month >= 36 {
|
||||
return base * 0.7
|
||||
} else if month >= 12 {
|
||||
|