This view is limited to 50 files because it contains too many changes.  See the raw diff here.
Files changed (50) hide show
  1. .dockerignore +0 -3
  2. .env +38 -86
  3. .env.template +105 -223
  4. .eslintrc.cjs +0 -1
  5. .github/release.yml +0 -16
  6. .github/workflows/build-image.yml +0 -125
  7. .github/workflows/deploy-release.yml +7 -8
  8. .github/workflows/deploy-staging.yml +2 -2
  9. .github/workflows/lint-and-test.yml +1 -1
  10. .gitignore +3 -4
  11. .vscode/settings.json +1 -1
  12. Dockerfile +9 -65
  13. PRIVACY.md +4 -10
  14. PROMPTS.md +0 -24
  15. README.md +47 -441
  16. .env.ci → conf/.env.ci +0 -0
  17. entrypoint.sh +0 -19
  18. package-lock.json +0 -0
  19. package.json +12 -34
  20. scripts/populate.ts +0 -269
  21. scripts/updateProdEnv.ts +4 -12
  22. src/ambient.d.ts +0 -4
  23. src/app.d.ts +0 -5
  24. src/hooks.server.ts +31 -131
  25. src/lib/actions/clickOutside.ts +0 -18
  26. src/lib/assistantStats/refresh-assistants-counts.ts +0 -90
  27. src/lib/buildPrompt.ts +78 -19
  28. src/lib/components/AnnouncementBanner.svelte +1 -1
  29. src/lib/components/AssistantSettings.svelte +0 -593
  30. src/lib/components/ContinueBtn.svelte +0 -13
  31. src/lib/components/CopyToClipBoardBtn.svelte +7 -7
  32. src/lib/components/DisclaimerModal.svelte +33 -28
  33. src/lib/components/ExpandNavigation.svelte +0 -14
  34. src/lib/components/HoverTooltip.svelte +0 -12
  35. src/lib/components/LoginModal.svelte +16 -21
  36. src/lib/components/MobileNav.svelte +8 -11
  37. src/lib/components/Modal.svelte +1 -4
  38. src/lib/components/ModelCardMetadata.svelte +2 -8
  39. src/lib/components/ModelsModal.svelte +153 -0
  40. src/lib/components/NavConversationItem.svelte +9 -25
  41. src/lib/components/NavMenu.svelte +37 -81
  42. src/lib/components/OpenWebSearchResults.svelte +41 -47
  43. src/lib/components/Pagination.svelte +0 -94
  44. src/lib/components/PaginationArrow.svelte +0 -23
  45. src/lib/components/SettingsModal.svelte +132 -0
  46. src/lib/components/Switch.svelte +3 -1
  47. src/lib/components/SystemPromptModal.svelte +1 -1
  48. src/lib/components/TokensCounter.svelte +0 -44
  49. src/lib/components/WebSearchToggle.svelte +2 -2
  50. src/lib/components/chat/AssistantIntroduction.svelte +0 -160
.dockerignore CHANGED
@@ -6,6 +6,3 @@ LICENSE
6
  README.md
7
  node_modules/
8
  .svelte-kit/
9
- .env*
10
- !.env
11
- .env.local
 
6
  README.md
7
  node_modules/
8
  .svelte-kit/
 
 
 
.env CHANGED
@@ -6,42 +6,28 @@ MONGODB_DB_NAME=chat-ui
6
  MONGODB_DIRECT_CONNECTION=false
7
 
8
  COOKIE_NAME=hf-chat
9
- HF_TOKEN=#hf_<token> from https://huggingface.co/settings/token
10
  HF_API_ROOT=https://api-inference.huggingface.co/models
11
-
12
  OPENAI_API_KEY=#your openai api key here
13
- ANTHROPIC_API_KEY=#your anthropic api key here
14
- CLOUDFLARE_ACCOUNT_ID=#your cloudflare account id here
15
- CLOUDFLARE_API_TOKEN=#your cloudflare api token here
16
- COHERE_API_TOKEN=#your cohere api token here
17
-
18
- HF_ACCESS_TOKEN=#LEGACY! Use HF_TOKEN instead
19
 
20
  # used to activate search with web functionality. disabled if none are defined. choose one of the following:
21
  YDC_API_KEY=#your docs.you.com api key here
22
  SERPER_API_KEY=#your serper.dev api key here
23
  SERPAPI_KEY=#your serpapi key here
24
- SERPSTACK_API_KEY=#your serpstack api key here
25
  USE_LOCAL_WEBSEARCH=#set to true to parse google results yourself, overrides other API keys
26
- SEARXNG_QUERY_URL=# where '<query>' will be replaced with query keywords see https://docs.searxng.org/dev/search_api.html eg https://searxng.yourdomain.com/search?q=<query>&engines=duckduckgo,google&format=json
27
-
28
- WEBSEARCH_ALLOWLIST=`[]` # if it's defined, allow websites from only this list.
29
- WEBSEARCH_BLOCKLIST=`[]` # if it's defined, block websites from this list.
30
 
31
  # Parameters to enable open id login
32
  OPENID_CONFIG=`{
33
  "PROVIDER_URL": "",
34
  "CLIENT_ID": "",
35
  "CLIENT_SECRET": "",
36
- "SCOPES": "",
37
- "NAME_CLAIM": ""
38
  }`
39
 
40
  # /!\ legacy openid settings, prefer the config above
41
  OPENID_CLIENT_ID=
42
  OPENID_CLIENT_SECRET=
43
  OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
44
- OPENID_NAME_CLAIM="name" # Change to "username" for some providers that do not provide name
45
  OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
46
  OPENID_TOLERANCE=
47
  OPENID_RESOURCE=
@@ -54,105 +40,71 @@ CA_PATH=#
54
  CLIENT_KEY_PASSWORD=#
55
  REJECT_UNAUTHORIZED=true
56
 
57
- TEXT_EMBEDDING_MODELS = `[
58
- {
59
- "name": "Xenova/gte-small",
60
- "displayName": "Xenova/gte-small",
61
- "description": "Local embedding model running on the server.",
62
- "chunkCharLength": 512,
63
- "endpoints": [
64
- { "type": "transformersjs" }
65
- ]
66
- }
67
- ]`
68
-
69
  # 'name', 'userMessageToken', 'assistantMessageToken' are required
70
  MODELS=`[
71
- {
72
- "name": "mistralai/Mistral-7B-Instruct-v0.1",
73
- "displayName": "mistralai/Mistral-7B-Instruct-v0.1",
74
- "description": "Mistral 7B is a new Apache 2.0 model, released by Mistral AI that outperforms Llama2 13B in benchmarks.",
75
- "websiteUrl": "https://mistral.ai/news/announcing-mistral-7b/",
76
- "preprompt": "",
77
- "chatPromptTemplate" : "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s>{{/ifAssistant}}{{/each}}",
78
- "parameters": {
79
- "temperature": 0.1,
80
- "top_p": 0.95,
81
- "repetition_penalty": 1.2,
82
- "top_k": 50,
83
- "truncate": 3072,
84
- "max_new_tokens": 1024,
85
- "stop": ["</s>"]
86
- },
87
- "promptExamples": [
88
- {
89
- "title": "Write an email from bullet list",
90
- "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
91
- }, {
92
- "title": "Code a snake game",
93
- "prompt": "Code a basic snake game in python, give explanations for each step."
94
- }, {
95
- "title": "Assist in a task",
96
- "prompt": "How do I make a delicious lemon cheesecake?"
97
- }
98
- ]
99
  }
 
100
  ]`
101
-
102
  OLD_MODELS=`[]`# any removed models, `{ name: string, displayName?: string, id?: string }`
103
- TASK_MODEL= # name of the model used for tasks such as summarizing title, creating query, etc.
104
 
105
  PUBLIC_ORIGIN=#https://huggingface.co
106
  PUBLIC_SHARE_PREFIX=#https://hf.co/chat
107
  PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
108
- PUBLIC_PLAUSIBLE_SCRIPT_URL=#/js/script.js / Leave empty to disable
109
  PUBLIC_ANNOUNCEMENT_BANNERS=`[
110
  {
111
- "title": "Code Llama 70B is available! 🦙",
112
- "linkTitle": "try it",
113
- "linkHref": "https://huggingface.co/chat?model=codellama/CodeLlama-70b-Instruct-hf"
114
  }
115
  ]`
116
 
117
- PUBLIC_APPLE_APP_ID=#1234567890 / Leave empty to disable
118
-
119
  PARQUET_EXPORT_DATASET=
120
  PARQUET_EXPORT_HF_TOKEN=
121
- ADMIN_API_SECRET=# secret to admin API calls, like computing usage stats or exporting parquet data
122
 
123
- PARQUET_EXPORT_SECRET=#DEPRECATED, use ADMIN_API_SECRET instead
124
-
125
- RATE_LIMIT= # /!\ Legacy definition of messages per minute. Use USAGE_LIMITS.messagesPerMinute instead
126
  MESSAGES_BEFORE_LOGIN=# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
127
 
128
- APP_BASE="" # base path of the app, e.g. /chat, left blank as default
129
  PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
130
  PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
131
  PUBLIC_APP_COLOR=blue # can be any of tailwind colors: https://tailwindcss.com/docs/customizing-colors#default-color-palette
132
  PUBLIC_APP_DESCRIPTION=# description used throughout the app (if not set, a default one will be used)
133
  PUBLIC_APP_DATA_SHARING=#set to 1 to enable options & text regarding data sharing
134
  PUBLIC_APP_DISCLAIMER=#set to 1 to show a disclaimer on login page
135
- PUBLIC_APP_DISCLAIMER_MESSAGE="Disclaimer: AI is an area of active research with known problems such as biased generation and misinformation. Do not use this application for high-stakes decisions or advice. Do not insert your personal data, especially sensitive, like health data."
136
  LLM_SUMMERIZATION=true
137
 
138
- EXPOSE_API=true
139
  # PUBLIC_APP_NAME=HuggingChat
140
  # PUBLIC_APP_ASSETS=huggingchat
141
  # PUBLIC_APP_COLOR=yellow
142
  # PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
143
  # PUBLIC_APP_DATA_SHARING=1
144
- # PUBLIC_APP_DISCLAIMER=1
145
-
146
- ENABLE_ASSISTANTS=false #set to true to enable assistants feature
147
- ENABLE_ASSISTANTS_RAG=false # /!\ This will let users specify arbitrary URLs that the server will then request. Make sure you have the proper firewall rules in place.
148
- REQUIRE_FEATURED_ASSISTANTS=false
149
- ENABLE_LOCAL_FETCH=false #set to true to disable the blocklist for local fetches. Only enable this if you have the proper firewall rules to prevent SSRF attacks and understand the implications.
150
- ALTERNATIVE_REDIRECT_URLS=`[]` #valide alternative redirect URL for OAuth
151
- WEBHOOK_URL_REPORT_ASSISTANT=#provide webhook url to get notified when an assistant gets reported
152
-
153
- ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to use the app
154
-
155
- USAGE_LIMITS=`{}`
156
- ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls
157
- METRICS_PORT=
158
- LOG_LEVEL=info
 
6
  MONGODB_DIRECT_CONNECTION=false
7
 
8
  COOKIE_NAME=hf-chat
9
+ HF_ACCESS_TOKEN=#hf_<token> from from https://huggingface.co/settings/token
10
  HF_API_ROOT=https://api-inference.huggingface.co/models
 
11
  OPENAI_API_KEY=#your openai api key here
 
 
 
 
 
 
12
 
13
  # used to activate search with web functionality. disabled if none are defined. choose one of the following:
14
  YDC_API_KEY=#your docs.you.com api key here
15
  SERPER_API_KEY=#your serper.dev api key here
16
  SERPAPI_KEY=#your serpapi key here
 
17
  USE_LOCAL_WEBSEARCH=#set to true to parse google results yourself, overrides other API keys
 
 
 
 
18
 
19
  # Parameters to enable open id login
20
  OPENID_CONFIG=`{
21
  "PROVIDER_URL": "",
22
  "CLIENT_ID": "",
23
  "CLIENT_SECRET": "",
24
+ "SCOPES": ""
 
25
  }`
26
 
27
  # /!\ legacy openid settings, prefer the config above
28
  OPENID_CLIENT_ID=
29
  OPENID_CLIENT_SECRET=
30
  OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
 
31
  OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
32
  OPENID_TOLERANCE=
33
  OPENID_RESOURCE=
 
40
  CLIENT_KEY_PASSWORD=#
41
  REJECT_UNAUTHORIZED=true
42
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  # 'name', 'userMessageToken', 'assistantMessageToken' are required
44
  MODELS=`[
45
+ {
46
+ "name": "OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5",
47
+ "datasetName": "OpenAssistant/oasst1",
48
+ "description": "A good alternative to ChatGPT",
49
+ "websiteUrl": "https://open-assistant.io",
50
+ "userMessageToken": "<|prompter|>",
51
+ "assistantMessageToken": "<|assistant|>",
52
+ "messageEndToken": "</s>",
53
+ "preprompt": "Below are a series of dialogues between various people and an AI assistant. The AI tries to be helpful, polite, honest, sophisticated, emotionally aware, and humble-but-knowledgeable. The assistant is happy to help with almost anything, and will do its best to understand exactly what is needed. It also tries to avoid giving false or misleading information, and it caveats when it isn't entirely sure about the right answer. That said, the assistant is practical and really does its best, and doesn't let caution get too much in the way of being useful.\n-----\n",
54
+ "promptExamples": [
55
+ {
56
+ "title": "Write an email from bullet list",
57
+ "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
58
+ }, {
59
+ "title": "Code a snake game",
60
+ "prompt": "Code a basic snake game in python, give explanations for each step."
61
+ }, {
62
+ "title": "Assist in a task",
63
+ "prompt": "How do I make a delicious lemon cheesecake?"
64
+ }
65
+ ],
66
+ "parameters": {
67
+ "temperature": 0.9,
68
+ "top_p": 0.95,
69
+ "repetition_penalty": 1.2,
70
+ "top_k": 50,
71
+ "truncate": 1000,
72
+ "max_new_tokens": 1024
73
  }
74
+ }
75
  ]`
 
76
  OLD_MODELS=`[]`# any removed models, `{ name: string, displayName?: string, id?: string }`
77
+ TASK_MODEL='' # name of the model used for tasks such as summarizing title, creating query, etc.
78
 
79
  PUBLIC_ORIGIN=#https://huggingface.co
80
  PUBLIC_SHARE_PREFIX=#https://hf.co/chat
81
  PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
 
82
  PUBLIC_ANNOUNCEMENT_BANNERS=`[
83
  {
84
+ "title": "Llama v2 is live on HuggingChat! 🦙",
85
+ "linkTitle": "Announcement",
86
+ "linkHref": "https://huggingface.co/blog/llama2"
87
  }
88
  ]`
89
 
 
 
90
  PARQUET_EXPORT_DATASET=
91
  PARQUET_EXPORT_HF_TOKEN=
92
+ PARQUET_EXPORT_SECRET=
93
 
94
+ RATE_LIMIT= # requests per minute
 
 
95
  MESSAGES_BEFORE_LOGIN=# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
96
 
 
97
  PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
98
  PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
99
  PUBLIC_APP_COLOR=blue # can be any of tailwind colors: https://tailwindcss.com/docs/customizing-colors#default-color-palette
100
  PUBLIC_APP_DESCRIPTION=# description used throughout the app (if not set, a default one will be used)
101
  PUBLIC_APP_DATA_SHARING=#set to 1 to enable options & text regarding data sharing
102
  PUBLIC_APP_DISCLAIMER=#set to 1 to show a disclaimer on login page
 
103
  LLM_SUMMERIZATION=true
104
 
 
105
  # PUBLIC_APP_NAME=HuggingChat
106
  # PUBLIC_APP_ASSETS=huggingchat
107
  # PUBLIC_APP_COLOR=yellow
108
  # PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
109
  # PUBLIC_APP_DATA_SHARING=1
110
+ # PUBLIC_APP_DISCLAIMER=1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.env.template CHANGED
@@ -1,121 +1,16 @@
1
  # template used in production for HuggingChat.
2
 
3
  MODELS=`[
4
- {
5
- "name" : "CohereForAI/c4ai-command-r-plus",
6
- "tokenizer": "Xenova/c4ai-command-r-v01-tokenizer",
7
- "description": "Command R+ is Cohere's latest LLM and is the first open weight model to beat GPT4 in the Chatbot Arena!",
8
- "modelUrl": "https://huggingface.co/CohereForAI/c4ai-command-r-plus",
9
- "websiteUrl": "https://docs.cohere.com/docs/command-r-plus",
10
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/cohere-logo.png",
11
- "parameters": {
12
- "stop": ["<|END_OF_TURN_TOKEN|>"],
13
- "truncate" : 28672,
14
- "max_new_tokens" : 4096,
15
- "temperature" : 0.3
16
- },
17
- "promptExamples" : [
18
- {
19
- "title": "Write an email from bullet list",
20
- "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
21
- }, {
22
- "title": "Code a snake game",
23
- "prompt": "Code a basic snake game in python, give explanations for each step."
24
- }, {
25
- "title": "Assist in a task",
26
- "prompt": "How do I make a delicious lemon cheesecake?"
27
- }
28
- ]
29
- },
30
- {
31
- "name" : "meta-llama/Meta-Llama-3-70B-Instruct",
32
- "description": "Generation over generation, Meta Llama 3 demonstrates state-of-the-art performance on a wide range of industry benchmarks and offers new capabilities, including improved reasoning.",
33
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/meta-logo.png",
34
- "modelUrl": "https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct",
35
- "websiteUrl": "https://llama.meta.com/llama3/",
36
- "tokenizer" : "philschmid/meta-llama-3-tokenizer",
37
- "promptExamples" : [
38
- {
39
- "title": "Write an email from bullet list",
40
- "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
41
- }, {
42
- "title": "Code a snake game",
43
- "prompt": "Code a basic snake game in python, give explanations for each step."
44
- }, {
45
- "title": "Assist in a task",
46
- "prompt": "How do I make a delicious lemon cheesecake?"
47
- }
48
- ],
49
- "parameters": {
50
- "stop": ["<|eot_id|>"],
51
- "truncate": 6144,
52
- "max_new_tokens": 2047
53
- }
54
- },
55
- {
56
- "name" : "HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
57
- "tokenizer": "HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
58
- "description": "Zephyr 141B-A35B is a fine-tuned version of Mistral 8x22B, trained using ORPO, a novel alignment algorithm.",
59
- "modelUrl": "https://huggingface.co/HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
60
- "websiteUrl": "https://huggingface.co/HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
61
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/zephyr-logo.png",
62
- "parameters": {
63
- "truncate" : 24576,
64
- "max_new_tokens" : 8192,
65
- },
66
- "preprompt" : "You are Zephyr, an assistant developed by KAIST AI, Argilla, and Hugging Face. You should give concise responses to very simple questions, but provide thorough responses to more complex and open-ended questions. You are happy to help with writing, analysis, question answering, math, coding, and all sorts of other tasks.",
67
- "promptExamples" : [
68
- {
69
- "title": "Write a poem",
70
- "prompt": "Write a poem to help me remember the first 10 elements on the periodic table, giving each element its own line."
71
- }, {
72
- "title": "Code a snake game",
73
- "prompt": "Code a basic snake game in python, give explanations for each step."
74
- }, {
75
- "title": "Assist in a task",
76
- "prompt": "How do I make a delicious lemon cheesecake?"
77
- }
78
- ]
79
- },
80
- {
81
- "name" : "mistralai/Mixtral-8x7B-Instruct-v0.1",
82
- "description" : "The latest MoE model from Mistral AI! 8x7B and outperforms Llama 2 70B in most benchmarks.",
83
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/mistral-logo.png",
84
- "websiteUrl" : "https://mistral.ai/news/mixtral-of-experts/",
85
- "modelUrl": "https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1",
86
- "tokenizer": "mistralai/Mixtral-8x7B-Instruct-v0.1",
87
- "preprompt" : "",
88
- "chatPromptTemplate": "<s> {{#each messages}}{{#ifUser}}[INST]{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}} {{content}}</s> {{/ifAssistant}}{{/each}}",
89
- "parameters" : {
90
- "temperature" : 0.6,
91
- "top_p" : 0.95,
92
- "repetition_penalty" : 1.2,
93
- "top_k" : 50,
94
- "truncate" : 24576,
95
- "max_new_tokens" : 8192,
96
- "stop" : ["</s>"]
97
- },
98
- "promptExamples" : [
99
- {
100
- "title": "Write an email from bullet list",
101
- "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
102
- }, {
103
- "title": "Code a snake game",
104
- "prompt": "Code a basic snake game in python, give explanations for each step."
105
- }, {
106
- "title": "Assist in a task",
107
- "prompt": "How do I make a delicious lemon cheesecake?"
108
- }
109
- ]
110
- },
111
- {
112
- "name" : "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
113
- "description" : "Nous Hermes 2 Mixtral 8x7B DPO is the new flagship Nous Research model trained over the Mixtral 8x7B MoE LLM.",
114
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/nous-logo.png",
115
- "websiteUrl" : "https://nousresearch.com/",
116
- "modelUrl": "https://huggingface.co/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
117
- "tokenizer": "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
118
- "chatPromptTemplate" : "{{#if @root.preprompt}}<|im_start|>system\n{{@root.preprompt}}<|im_end|>\n{{/if}}{{#each messages}}{{#ifUser}}<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n{{/ifUser}}{{#ifAssistant}}{{content}}<|im_end|>\n{{/ifAssistant}}{{/each}}",
119
  "promptExamples": [
120
  {
121
  "title": "Write an email from bullet list",
@@ -129,63 +24,63 @@ MODELS=`[
129
  }
130
  ],
131
  "parameters": {
132
- "temperature": 0.7,
133
  "top_p": 0.95,
134
- "repetition_penalty": 1,
135
  "top_k": 50,
136
- "truncate": 24576,
137
- "max_new_tokens": 2048,
138
- "stop": ["<|im_end|>"]
139
  }
140
  },
141
- {
142
- "name" : "google/gemma-1.1-7b-it",
143
- "description": "Gemma 7B 1.1 is the latest release in the Gemma family of lightweight models built by Google, trained using a novel RLHF method.",
144
- "websiteUrl" : "https://blog.google/technology/developers/gemma-open-models/",
145
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/google-logo.png",
146
- "modelUrl": "https://huggingface.co/google/gemma-1.1-7b-it",
147
- "preprompt": "",
148
- "chatPromptTemplate" : "{{#each messages}}{{#ifUser}}<start_of_turn>user\n{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}<end_of_turn>\n<start_of_turn>model\n{{/ifUser}}{{#ifAssistant}}{{content}}<end_of_turn>\n{{/ifAssistant}}{{/each}}",
149
- "promptExamples": [
150
- {
151
- "title": "Write an email from bullet list",
152
- "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
153
- }, {
154
- "title": "Code a snake game",
155
- "prompt": "Code a basic snake game in python, give explanations for each step."
156
- }, {
157
- "title": "Assist in a task",
158
- "prompt": "How do I make a delicious lemon cheesecake?"
159
- }
160
- ],
161
- "parameters": {
162
- "do_sample": true,
163
- "truncate": 7168,
164
- "max_new_tokens": 1024,
165
- "stop" : ["<end_of_turn>"]
166
- }
167
- },
168
-
169
  {
170
- "name": "mistralai/Mistral-7B-Instruct-v0.2",
171
- "displayName": "mistralai/Mistral-7B-Instruct-v0.2",
172
- "description": "Mistral 7B is a new Apache 2.0 model, released by Mistral AI that outperforms Llama2 13B in benchmarks.",
173
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/mistral-logo.png",
174
- "websiteUrl": "https://mistral.ai/news/announcing-mistral-7b/",
175
- "modelUrl": "https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2",
176
- "tokenizer": "mistralai/Mistral-7B-Instruct-v0.2",
177
- "preprompt": "",
178
- "chatPromptTemplate" : "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s>{{/ifAssistant}}{{/each}}",
 
179
  "parameters": {
180
- "temperature": 0.3,
181
  "top_p": 0.95,
182
  "repetition_penalty": 1.2,
183
  "top_k": 50,
184
- "truncate": 3072,
185
- "max_new_tokens": 1024,
186
- "stop": ["</s>"]
187
  },
188
- "promptExamples": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  {
190
  "title": "Write an email from bullet list",
191
  "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
@@ -199,18 +94,20 @@ MODELS=`[
199
  ]
200
  },
201
  {
202
- "name": "microsoft/Phi-3-mini-4k-instruct",
203
- "tokenizer": "microsoft/Phi-3-mini-4k-instruct",
204
- "description" : "Phi-3 Mini-4K-Instruct is a 3.8B parameters, lightweight, state-of-the-art open model built upon datasets used for Phi-2.",
205
- "logoUrl": "https://huggingface.co/datasets/huggingchat/models-logo/resolve/main/microsoft-logo.png",
206
- "modelUrl": "https://huggingface.co/microsoft/Phi-3-mini-4k-instruct",
207
- "websiteUrl": "https://azure.microsoft.com/en-us/blog/introducing-phi-3-redefining-whats-possible-with-slms/",
208
  "preprompt": "",
209
- "chatPromptTemplate": "<s>{{preprompt}}{{#each messages}}{{#ifUser}}<|user|>\n{{content}}<|end|>\n<|assistant|>\n{{/ifUser}}{{#ifAssistant}}{{content}}<|end|>\n{{/ifAssistant}}{{/each}}",
210
  "parameters": {
211
- "stop": ["<|end|>", "<|endoftext|>", "<|assistant|>"],
 
 
 
 
212
  "max_new_tokens": 1024,
213
- "truncate": 3071
214
  },
215
  "promptExamples": [
216
  {
@@ -225,47 +122,41 @@ MODELS=`[
225
  }
226
  ]
227
  },
228
- {
229
- "name": "meta-llama/Meta-Llama-3-8B-Instruct",
230
- "tokenizer" : "philschmid/meta-llama-3-tokenizer",
231
- "parameters": {
232
- "temperature": 0.1,
233
- "stop": ["<|eot_id|>"],
234
- "truncate": 1024,
235
- },
236
- "unlisted": true
237
- }
238
- ]`
239
-
240
- OLD_MODELS=`[
241
- {"name":"bigcode/starcoder"},
242
- {"name":"OpenAssistant/oasst-sft-6-llama-30b-xor"},
243
- {"name":"HuggingFaceH4/zephyr-7b-alpha"},
244
- {"name":"openchat/openchat_3.5"},
245
- {"name":"openchat/openchat-3.5-1210"},
246
- {"name": "tiiuae/falcon-180B-chat"},
247
- {"name": "codellama/CodeLlama-34b-Instruct-hf"},
248
- {"name": "google/gemma-7b-it"},
249
- {"name": "meta-llama/Llama-2-70b-chat-hf"},
250
- {"name": "codellama/CodeLlama-70b-Instruct-hf"},
251
- {"name": "openchat/openchat-3.5-0106"}
252
- ]`
253
-
254
- TASK_MODEL='meta-llama/Meta-Llama-3-8B-Instruct'
255
-
256
- TEXT_EMBEDDING_MODELS = `[
257
- {
258
- "name": "bge-base-en-v1-5-sxa",
259
- "displayName": "bge-base-en-v1-5-sxa",
260
- "chunkCharLength": 512,
261
- "endpoints": [
262
- { "type": "tei",
263
- "url" : "https://huggingchat-tei.hf.space/"
264
  }
265
  ]
266
  }
267
  ]`
268
 
 
 
 
 
269
 
270
  APP_BASE="/chat"
271
  PUBLIC_ORIGIN=https://huggingface.co
@@ -276,23 +167,14 @@ PUBLIC_APP_NAME=HuggingChat
276
  PUBLIC_APP_ASSETS=huggingchat
277
  PUBLIC_APP_COLOR=yellow
278
  PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
279
- PUBLIC_APP_DISCLAIMER_MESSAGE="Disclaimer: AI is an area of active research with known problems such as biased generation and misinformation. Do not use this application for high-stakes decisions or advice."
280
- PUBLIC_APP_DATA_SHARING=0
281
  PUBLIC_APP_DISCLAIMER=1
282
 
283
- PUBLIC_PLAUSIBLE_SCRIPT_URL="/js/script.js"
284
- PUBLIC_APPLE_APP_ID=6476778843
285
- # Not part of the .env but set as other variables in the space
286
- # ADDRESS_HEADER=X-Forwarded-For
287
- # XFF_DEPTH=2
288
 
289
- ENABLE_ASSISTANTS=true
290
- ENABLE_ASSISTANTS_RAG=true
291
- REQUIRE_FEATURED_ASSISTANTS=true
292
- EXPOSE_API=true
293
 
294
- ALTERNATIVE_REDIRECT_URLS=`[
295
- huggingchat://login/callback
296
- ]`
297
-
298
- WEBSEARCH_BLOCKLIST=`["youtube.com", "twitter.com"]`
 
1
  # template used in production for HuggingChat.
2
 
3
  MODELS=`[
4
+ {
5
+ "name": "meta-llama/Llama-2-70b-chat-hf",
6
+ "description": "The latest and biggest model from Meta, fine-tuned for chat.",
7
+ "websiteUrl": "https://ai.meta.com/llama/",
8
+ "userMessageToken": "",
9
+ "userMessageEndToken": " [/INST] ",
10
+ "assistantMessageToken": "",
11
+ "assistantMessageEndToken": " </s><s>[INST] ",
12
+ "preprompt": " ",
13
+ "chatPromptTemplate" : "<s>[INST] <<SYS>>\n{{preprompt}}\n<</SYS>>\n\n{{#each messages}}{{#ifUser}}{{content}} [/INST] {{/ifUser}}{{#ifAssistant}}{{content}} </s><s>[INST] {{/ifAssistant}}{{/each}}",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  "promptExamples": [
15
  {
16
  "title": "Write an email from bullet list",
 
24
  }
25
  ],
26
  "parameters": {
27
+ "temperature": 0.1,
28
  "top_p": 0.95,
29
+ "repetition_penalty": 1.2,
30
  "top_k": 50,
31
+ "truncate": 3072,
32
+ "max_new_tokens": 1024
 
33
  }
34
  },
35
+ {
36
+ "name": "codellama/CodeLlama-34b-Instruct-hf",
37
+ "displayName": "codellama/CodeLlama-34b-Instruct-hf",
38
+ "description": "Code Llama, a state of the art code model from Meta.",
39
+ "websiteUrl": "https://about.fb.com/news/2023/08/code-llama-ai-for-coding/",
40
+ "userMessageToken": "",
41
+ "userMessageEndToken": " [/INST] ",
42
+ "assistantMessageToken": "",
43
+ "assistantMessageEndToken": " </s><s>[INST] ",
44
+ "preprompt": " ",
45
+ "chatPromptTemplate" : "<s>[INST] <<SYS>>\n{{preprompt}}\n<</SYS>>\n\n{{#each messages}}{{#ifUser}}{{content}} [/INST] {{/ifUser}}{{#ifAssistant}}{{content}} </s><s>[INST] {{/ifAssistant}}{{/each}}",
46
+ "promptExamples": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  {
48
+ "title": "Fibonacci in Python",
49
+ "prompt": "Write a python function to calculate the nth fibonacci number."
50
+ }, {
51
+ "title": "JavaScript promises",
52
+ "prompt": "How can I wait for multiple JavaScript promises to fulfill before doing something with their values?"
53
+ }, {
54
+ "title": "Rust filesystem",
55
+ "prompt": "How can I load a file from disk in Rust?"
56
+ }
57
+ ],
58
  "parameters": {
59
+ "temperature": 0.1,
60
  "top_p": 0.95,
61
  "repetition_penalty": 1.2,
62
  "top_k": 50,
63
+ "truncate": 4096,
64
+ "max_new_tokens": 4096
65
+ }
66
  },
67
+ {
68
+ "name": "tiiuae/falcon-180B-chat",
69
+ "displayName": "tiiuae/falcon-180B-chat",
70
+ "description": "Falcon-180B is a 180B parameters causal decoder-only model built by TII and trained on 3,500B tokens.",
71
+ "websiteUrl": "https://www.tii.ae/news/technology-innovation-institute-introduces-worlds-most-powerful-open-llm-falcon-180b",
72
+ "preprompt": " ",
73
+ "chatPromptTemplate": "System: {{preprompt}}\nUser:{{#each messages}}{{#ifUser}}{{content}}\nFalcon:{{/ifUser}}{{#ifAssistant}}{{content}}\nUser:{{/ifAssistant}}{{/each}}",
74
+ "parameters": {
75
+ "temperature": 0.1,
76
+ "top_p": 0.95,
77
+ "repetition_penalty": 1.2,
78
+ "top_k": 50,
79
+ "truncate": 1024,
80
+ "max_new_tokens": 1024,
81
+ "stop": ["User:"]
82
+ },
83
+ "promptExamples": [
84
  {
85
  "title": "Write an email from bullet list",
86
  "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
 
94
  ]
95
  },
96
  {
97
+ "name": "mistralai/Mistral-7B-Instruct-v0.1",
98
+ "displayName": "mistralai/Mistral-7B-Instruct-v0.1",
99
+ "description": "Mistral 7B is a new Apache 2.0 model, released by Mistral AI that outperforms Llama2 13B in benchmarks.",
100
+ "websiteUrl": "https://mistral.ai/news/announcing-mistral-7b/",
 
 
101
  "preprompt": "",
102
+ "chatPromptTemplate" : "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s>{{/ifAssistant}}{{/each}}",
103
  "parameters": {
104
+ "temperature": 0.1,
105
+ "top_p": 0.95,
106
+ "repetition_penalty": 1.2,
107
+ "top_k": 50,
108
+ "truncate": 3072,
109
  "max_new_tokens": 1024,
110
+ "stop": ["</s>"]
111
  },
112
  "promptExamples": [
113
  {
 
122
  }
123
  ]
124
  },
125
+ {
126
+ "name": "openchat/openchat_3.5",
127
+ "displayName": "openchat/openchat_3.5",
128
+ "description": "OpenChat 3.5 is the #1 model on MT-Bench, with only 7B parameters.",
129
+ "websiteUrl": "https://huggingface.co/openchat/openchat_3.5",
130
+ "preprompt": "",
131
+ "chatPromptTemplate" : "<s>{{#each messages}}{{#ifUser}}GPT4 Correct User: {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}<|end_of_turn|>GPT4 Correct Assistant:{{/ifUser}}{{#ifAssistant}}{{content}}<|end_of_turn|>{{/ifAssistant}}{{/each}}",
132
+ "parameters": {
133
+ "temperature": 0.6,
134
+ "top_p": 0.95,
135
+ "repetition_penalty": 1.2,
136
+ "top_k": 50,
137
+ "truncate": 6016,
138
+ "max_new_tokens": 2048,
139
+ "stop": ["<|end_of_turn|>"]
140
+ },
141
+ "promptExamples": [
142
+ {
143
+ "title": "Write an email from bullet list",
144
+ "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
145
+ }, {
146
+ "title": "Code a snake game",
147
+ "prompt": "Code a basic snake game in python, give explanations for each step."
148
+ }, {
149
+ "title": "Assist in a task",
150
+ "prompt": "How do I make a delicious lemon cheesecake?"
 
 
 
 
 
 
 
 
 
 
151
  }
152
  ]
153
  }
154
  ]`
155
 
156
+ OLD_MODELS=`[{"name":"bigcode/starcoder"}, {"name":"OpenAssistant/oasst-sft-6-llama-30b-xor"}, {"name":"HuggingFaceH4/zephyr-7b-alpha"}]`
157
+
158
+ TASK_MODEL='mistralai/Mistral-7B-Instruct-v0.1'
159
+
160
 
161
  APP_BASE="/chat"
162
  PUBLIC_ORIGIN=https://huggingface.co
 
167
  PUBLIC_APP_ASSETS=huggingchat
168
  PUBLIC_APP_COLOR=yellow
169
  PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
170
+ PUBLIC_APP_DATA_SHARING=1
 
171
  PUBLIC_APP_DISCLAIMER=1
172
 
173
+ RATE_LIMIT=16
174
+ MESSAGES_BEFORE_LOGIN=5# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
 
 
 
175
 
176
+ PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL
 
 
 
177
 
178
+ # Not part of the .env but set as other variables in the space
179
+ # ADDRESS_HEADER=X-Forwarded-For
180
+ # XFF_DEPTH=2
 
 
.eslintrc.cjs CHANGED
@@ -34,7 +34,6 @@ module.exports = {
34
  argsIgnorePattern: "^_",
35
  },
36
  ],
37
- "object-shorthand": ["error", "always"],
38
  },
39
  env: {
40
  browser: true,
 
34
  argsIgnorePattern: "^_",
35
  },
36
  ],
 
37
  },
38
  env: {
39
  browser: true,
.github/release.yml DELETED
@@ -1,16 +0,0 @@
1
- changelog:
2
- exclude:
3
- labels:
4
- - huggingchat
5
- - CI/CD
6
- - documentation
7
- categories:
8
- - title: Features
9
- labels:
10
- - enhancement
11
- - title: Bugfixes
12
- labels:
13
- - bug
14
- - title: Other changes
15
- labels:
16
- - "*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/workflows/build-image.yml DELETED
@@ -1,125 +0,0 @@
1
- name: Build and Publish Image
2
-
3
- on:
4
- push:
5
- branches:
6
- - "main"
7
- pull_request:
8
- branches:
9
- - "*"
10
- paths:
11
- - "Dockerfile"
12
- - "entrypoint.sh"
13
- workflow_dispatch:
14
- release:
15
- types: [published, edited]
16
-
17
- jobs:
18
- build-and-publish-image-with-db:
19
- runs-on: ubuntu-latest
20
- steps:
21
- - name: Checkout
22
- uses: actions/checkout@v4
23
-
24
- - name: Extract package version
25
- id: package-version
26
- run: |
27
- VERSION=$(jq -r .version package.json)
28
- echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
29
- MAJOR=$(echo $VERSION | cut -d '.' -f1)
30
- echo "MAJOR=$MAJOR" >> $GITHUB_OUTPUT
31
- MINOR=$(echo $VERSION | cut -d '.' -f1).$(echo $VERSION | cut -d '.' -f2)
32
- echo "MINOR=$MINOR" >> $GITHUB_OUTPUT
33
-
34
- - name: Docker metadata
35
- id: meta
36
- uses: docker/metadata-action@v5
37
- with:
38
- images: |
39
- ghcr.io/huggingface/chat-ui-db
40
- tags: |
41
- type=raw,value=${{ steps.package-version.outputs.VERSION }},enable=${{github.event_name == 'release'}}
42
- type=raw,value=${{ steps.package-version.outputs.MAJOR }},enable=${{github.event_name == 'release'}}
43
- type=raw,value=${{ steps.package-version.outputs.MINOR }},enable=${{github.event_name == 'release'}}
44
- type=raw,value=latest,enable={{is_default_branch}}
45
- type=sha,enable={{is_default_branch}}
46
-
47
- - name: Set up QEMU
48
- uses: docker/setup-qemu-action@v3
49
-
50
- - name: Set up Docker Buildx
51
- uses: docker/setup-buildx-action@v3
52
-
53
- - name: Login to GitHub Container Registry
54
- if: github.event_name != 'pull_request'
55
- uses: docker/login-action@v3
56
- with:
57
- registry: ghcr.io
58
- username: ${{ github.repository_owner }}
59
- password: ${{ secrets.GITHUB_TOKEN }}
60
-
61
- - name: Build and Publish Docker Image with DB
62
- uses: docker/build-push-action@v5
63
- with:
64
- context: .
65
- file: Dockerfile
66
- push: ${{ github.event_name != 'pull_request' }}
67
- tags: ${{ steps.meta.outputs.tags }}
68
- labels: ${{ steps.meta.outputs.labels }}
69
- platforms: linux/amd64,linux/arm64
70
- build-args: |
71
- INCLUDE_DB=true
72
- build-and-publish-image-nodb:
73
- runs-on: ubuntu-latest
74
- steps:
75
- - name: Checkout
76
- uses: actions/checkout@v4
77
-
78
- - name: Extract package version
79
- id: package-version
80
- run: |
81
- VERSION=$(jq -r .version package.json)
82
- echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
83
- MAJOR=$(echo $VERSION | cut -d '.' -f1)
84
- echo "MAJOR=$MAJOR" >> $GITHUB_OUTPUT
85
- MINOR=$(echo $VERSION | cut -d '.' -f1).$(echo $VERSION | cut -d '.' -f2)
86
- echo "MINOR=$MINOR" >> $GITHUB_OUTPUT
87
-
88
- - name: Docker metadata
89
- id: meta
90
- uses: docker/metadata-action@v5
91
- with:
92
- images: |
93
- ghcr.io/huggingface/chat-ui
94
- tags: |
95
- type=raw,value=${{ steps.package-version.outputs.VERSION }},enable=${{github.event_name == 'release'}}
96
- type=raw,value=${{ steps.package-version.outputs.MAJOR }},enable=${{github.event_name == 'release'}}
97
- type=raw,value=${{ steps.package-version.outputs.MINOR }},enable=${{github.event_name == 'release'}}
98
- type=raw,value=latest,enable={{is_default_branch}}
99
- type=sha,enable={{is_default_branch}}
100
-
101
- - name: Set up QEMU
102
- uses: docker/setup-qemu-action@v3
103
-
104
- - name: Set up Docker Buildx
105
- uses: docker/setup-buildx-action@v3
106
-
107
- - name: Login to GitHub Container Registry
108
- if: github.event_name != 'pull_request'
109
- uses: docker/login-action@v3
110
- with:
111
- registry: ghcr.io
112
- username: ${{ github.repository_owner }}
113
- password: ${{ secrets.GITHUB_TOKEN }}
114
-
115
- - name: Build and Publish Docker Image without DB
116
- uses: docker/build-push-action@v5
117
- with:
118
- context: .
119
- file: Dockerfile
120
- push: ${{ github.event_name != 'pull_request' }}
121
- tags: ${{ steps.meta.outputs.tags }}
122
- labels: ${{ steps.meta.outputs.labels }}
123
- platforms: linux/amd64,linux/arm64
124
- build-args: |
125
- INCLUDE_DB=false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/workflows/deploy-release.yml CHANGED
@@ -1,6 +1,9 @@
1
  name: Deploy to production
2
  on:
3
- # run this workflow manually from the Actions tab
 
 
 
4
  workflow_dispatch:
5
 
6
  jobs:
@@ -21,11 +24,7 @@ jobs:
21
  SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
22
  OPENID_CONFIG: ${{ secrets.OPENID_CONFIG }}
23
  MONGODB_URL: ${{ secrets.MONGODB_URL }}
24
- HF_DEPLOYMENT_TOKEN: ${{ secrets.HF_DEPLOYMENT_TOKEN }}
25
- WEBHOOK_URL_REPORT_ASSISTANT: ${{ secrets.WEBHOOK_URL_REPORT_ASSISTANT }}
26
- ADMIN_API_SECRET: ${{ secrets.ADMIN_API_SECRET }}
27
- USAGE_LIMITS: ${{ secrets.USAGE_LIMITS }}
28
- MESSAGES_BEFORE_LOGIN: ${{ secrets.MESSAGES_BEFORE_LOGIN }}
29
  run: npm run updateProdEnv
30
  sync-to-hub:
31
  runs-on: ubuntu-latest
@@ -40,5 +39,5 @@ jobs:
40
  lfs: true
41
  - name: Push to hub
42
  env:
43
- HF_DEPLOYMENT_TOKEN: ${{ secrets.HF_DEPLOYMENT_TOKEN }}
44
- run: git push https://nsarrazin:$HF_DEPLOYMENT_TOKEN@huggingface.co/spaces/huggingchat/chat-ui main
 
1
  name: Deploy to production
2
  on:
3
+ release:
4
+ types: [released]
5
+
6
+ # to run this workflow manually from the Actions tab
7
  workflow_dispatch:
8
 
9
  jobs:
 
24
  SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
25
  OPENID_CONFIG: ${{ secrets.OPENID_CONFIG }}
26
  MONGODB_URL: ${{ secrets.MONGODB_URL }}
27
+ HF_ACCESS_TOKEN: ${{ secrets.HF_ACCESS_TOKEN }}
 
 
 
 
28
  run: npm run updateProdEnv
29
  sync-to-hub:
30
  runs-on: ubuntu-latest
 
39
  lfs: true
40
  - name: Push to hub
41
  env:
42
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
43
+ run: git push https://nsarrazin:$HF_TOKEN@huggingface.co/spaces/huggingchat/chat-ui main
.github/workflows/deploy-staging.yml CHANGED
@@ -20,5 +20,5 @@ jobs:
20
  lfs: true
21
  - name: Push to hub
22
  env:
23
- HF_DEPLOYMENT_TOKEN: ${{ secrets.HF_DEPLOYMENT_TOKEN }}
24
- run: git push https://nsarrazin:$HF_DEPLOYMENT_TOKEN@huggingface.co/spaces/huggingchat/chat-ui-staging main
 
20
  lfs: true
21
  - name: Push to hub
22
  env:
23
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
24
+ run: git push https://nsarrazin:$HF_TOKEN@huggingface.co/spaces/huggingchat/chat-ui-staging main
.github/workflows/lint-and-test.yml CHANGED
@@ -53,4 +53,4 @@ jobs:
53
  steps:
54
  - uses: actions/checkout@v3
55
  - name: Build Docker image
56
- run: docker build --secret id=DOTENV_LOCAL,src=.env.ci -t chat-ui:latest .
 
53
  steps:
54
  - uses: actions/checkout@v3
55
  - name: Build Docker image
56
+ run: docker build --secret id=DOTENV_LOCAL,src=conf/.env.ci -t chat-ui:latest .
.gitignore CHANGED
@@ -5,11 +5,10 @@ node_modules
5
  /package
6
  .env
7
  .env.*
 
 
8
  vite.config.js.timestamp-*
9
  vite.config.ts.timestamp-*
10
  SECRET_CONFIG
11
  .idea
12
- !.env.ci
13
- !.env
14
- !.env.template
15
- gcp-*.json
 
5
  /package
6
  .env
7
  .env.*
8
+ !.env.example
9
+ !.env.template
10
  vite.config.js.timestamp-*
11
  vite.config.ts.timestamp-*
12
  SECRET_CONFIG
13
  .idea
14
+ !conf/.env.ci
 
 
 
.vscode/settings.json CHANGED
@@ -2,7 +2,7 @@
2
  "editor.formatOnSave": true,
3
  "editor.defaultFormatter": "esbenp.prettier-vscode",
4
  "editor.codeActionsOnSave": {
5
- "source.fixAll": "explicit"
6
  },
7
  "eslint.validate": ["javascript", "svelte"]
8
  }
 
2
  "editor.formatOnSave": true,
3
  "editor.defaultFormatter": "esbenp.prettier-vscode",
4
  "editor.codeActionsOnSave": {
5
+ "source.fixAll": true
6
  },
7
  "eslint.validate": ["javascript", "svelte"]
8
  }
Dockerfile CHANGED
@@ -1,10 +1,7 @@
1
  # syntax=docker/dockerfile:1
2
  # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
3
  # you will also find guides on how best to write your Dockerfile
4
- ARG INCLUDE_DB=false
5
-
6
- # stage that install the dependencies
7
- FROM node:20 as builder-production
8
 
9
  WORKDIR /app
10
 
@@ -15,74 +12,21 @@ RUN --mount=type=cache,target=/app/.npm \
15
 
16
  FROM builder-production as builder
17
 
18
- ARG APP_BASE=
19
- ARG PUBLIC_APP_COLOR=blue
20
-
21
  RUN --mount=type=cache,target=/app/.npm \
22
  npm set cache /app/.npm && \
23
  npm ci
24
 
25
  COPY --link --chown=1000 . .
26
 
27
- RUN npm run build
28
-
29
- # mongo image
30
- FROM mongo:latest as mongo
31
-
32
- # image to be used if INCLUDE_DB is false
33
- FROM node:20-slim as local_db_false
34
-
35
- # image to be used if INCLUDE_DB is true
36
- FROM node:20-slim as local_db_true
37
-
38
- RUN apt-get update
39
- RUN apt-get install gnupg curl -y
40
- # copy mongo from the other stage
41
- COPY --from=mongo /usr/bin/mongo* /usr/bin/
42
-
43
- ENV MONGODB_URL=mongodb://localhost:27017
44
- RUN mkdir -p /data/db
45
- RUN chown -R 1000:1000 /data/db
46
-
47
- # final image
48
- FROM local_db_${INCLUDE_DB} as final
49
-
50
- # build arg to determine if the database should be included
51
- ARG INCLUDE_DB=false
52
- ENV INCLUDE_DB=${INCLUDE_DB}
53
-
54
- # svelte requires APP_BASE at build time so it must be passed as a build arg
55
- ARG APP_BASE=
56
- # tailwind requires the primary theme to be known at build time so it must be passed as a build arg
57
- ARG PUBLIC_APP_COLOR=blue
58
-
59
-
60
- # install dotenv-cli
61
- RUN npm install -g dotenv-cli
62
-
63
- # switch to a user that works for spaces
64
- RUN userdel -r node
65
- RUN useradd -m -u 1000 user
66
- USER user
67
-
68
- ENV HOME=/home/user \
69
- PATH=/home/user/.local/bin:$PATH
70
-
71
- WORKDIR /app
72
-
73
- # add a .env.local if the user doesn't bind a volume to it
74
- RUN touch /app/.env.local
75
 
76
- # get the default config, the entrypoint script and the server script
77
- COPY --chown=1000 package.json /app/package.json
78
- COPY --chown=1000 .env /app/.env
79
- COPY --chown=1000 entrypoint.sh /app/entrypoint.sh
80
- COPY --chown=1000 gcp-*.json /app/
81
 
82
- #import the build & dependencies
83
- COPY --from=builder --chown=1000 /app/build /app/build
84
- COPY --from=builder --chown=1000 /app/node_modules /app/node_modules
85
 
86
- RUN chmod +x /app/entrypoint.sh
 
 
87
 
88
- CMD ["/bin/bash", "-c", "/app/entrypoint.sh"]
 
1
  # syntax=docker/dockerfile:1
2
  # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
3
  # you will also find guides on how best to write your Dockerfile
4
+ FROM node:19 as builder-production
 
 
 
5
 
6
  WORKDIR /app
7
 
 
12
 
13
  FROM builder-production as builder
14
 
 
 
 
15
  RUN --mount=type=cache,target=/app/.npm \
16
  npm set cache /app/.npm && \
17
  npm ci
18
 
19
  COPY --link --chown=1000 . .
20
 
21
+ RUN --mount=type=secret,id=DOTENV_LOCAL,dst=.env.local \
22
+ npm run build
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ FROM node:19-slim
 
 
 
 
25
 
26
+ RUN npm install -g pm2
 
 
27
 
28
+ COPY --from=builder-production /app/node_modules /app/node_modules
29
+ COPY --link --chown=1000 package.json /app/package.json
30
+ COPY --from=builder /app/build /app/build
31
 
32
+ CMD pm2 start /app/build/index.js -i $CPU_CORES --no-daemon
PRIVACY.md CHANGED
@@ -1,12 +1,12 @@
1
  ## Privacy
2
 
3
- > Last updated: April 15, 2024
4
 
5
  Users of HuggingChat are authenticated through their HF user account.
6
 
7
- We endorse Privacy by Design. As such, your conversations are private to you and will not be shared with anyone, including model authors, for any purpose, including for research or model training purposes.
8
 
9
- You conversation data will only be stored to let you access past conversations. You can click on the Delete icon to delete any past conversation at any moment.
10
 
11
  🗓 Please also consult huggingface.co's main privacy policy at <https://huggingface.co/privacy>. To exercise any of your legal privacy rights, please send an email to <privacy@huggingface.co>.
12
 
@@ -14,18 +14,12 @@ You conversation data will only be stored to let you access past conversations.
14
 
15
  The goal of this app is to showcase that it is now possible to build an open source alternative to ChatGPT. 💪
16
 
17
- We aim to always provide a diverse set of state of the art open LLMs, hence we rotate the available models over time.
18
-
19
- Historically, HuggingChat has been running models such as:
20
 
21
  - [Llama 2 70B](https://huggingface.co/meta-llama/Llama-2-70b-chat-hf)
22
  - [CodeLlama 35B](https://about.fb.com/news/2023/08/code-llama-ai-for-coding/)
23
  - [Falcon 180B](https://www.tii.ae/news/technology-innovation-institute-introduces-worlds-most-powerful-open-llm-falcon-180b)
24
  - [Mistral 7B](https://mistral.ai/news/announcing-mistral-7b/)
25
- - [Cohere Command R+](https://huggingface.co/chat/models/CohereForAI/c4ai-command-r-plus)
26
- - [Google Gemma 7B](https://huggingface.co/chat/models/google/gemma-1.1-7b-it)
27
-
28
- This is only a partial list. Check the [models](https://huggingface.co/chat/models/) page for up-to-date list of the best available LLMs.
29
 
30
  ## Technical details
31
 
 
1
  ## Privacy
2
 
3
+ > Last updated: October 4, 2023
4
 
5
  Users of HuggingChat are authenticated through their HF user account.
6
 
7
+ By default, your conversations may be shared with the respective models' authors to improve their training data and model over time. Model authors are the custodians of the data collected by their model, even if it's hosted on our platform.
8
 
9
+ If you disable data sharing in your settings, your conversations will not be used for any downstream usage (including for research or model training purposes), and they will only be stored to let you access past conversations. You can click on the Delete icon to delete any past conversation at any moment.
10
 
11
  🗓 Please also consult huggingface.co's main privacy policy at <https://huggingface.co/privacy>. To exercise any of your legal privacy rights, please send an email to <privacy@huggingface.co>.
12
 
 
14
 
15
  The goal of this app is to showcase that it is now possible to build an open source alternative to ChatGPT. 💪
16
 
17
+ For now (October 2023), it's running:
 
 
18
 
19
  - [Llama 2 70B](https://huggingface.co/meta-llama/Llama-2-70b-chat-hf)
20
  - [CodeLlama 35B](https://about.fb.com/news/2023/08/code-llama-ai-for-coding/)
21
  - [Falcon 180B](https://www.tii.ae/news/technology-innovation-institute-introduces-worlds-most-powerful-open-llm-falcon-180b)
22
  - [Mistral 7B](https://mistral.ai/news/announcing-mistral-7b/)
 
 
 
 
23
 
24
  ## Technical details
25
 
PROMPTS.md CHANGED
@@ -43,27 +43,3 @@ System: {{preprompt}}\nUser:{{#each messages}}{{#ifUser}}{{content}}\nFalcon:{{/
43
  ```env
44
  <s>{{#each messages}}{{#ifUser}}GPT4 User: {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}<|end_of_turn|>GPT4 Assistant: {{/ifUser}}{{#ifAssistant}}{{content}}<|end_of_turn|>{{/ifAssistant}}{{/each}}
45
  ```
46
-
47
- ## Mixtral
48
-
49
- ```env
50
- <s> {{#each messages}}{{#ifUser}}[INST]{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}} {{content}}</s> {{/ifAssistant}}{{/each}}
51
- ```
52
-
53
- ## ChatML
54
-
55
- ```env
56
- {{#if @root.preprompt}}<|im_start|>system\n{{@root.preprompt}}<|im_end|>\n{{/if}}{{#each messages}}{{#ifUser}}<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n{{/ifUser}}{{#ifAssistant}}{{content}}<|im_end|>\n{{/ifAssistant}}{{/each}}
57
- ```
58
-
59
- ## CodeLlama 70B
60
-
61
- ```env
62
- <s>{{#if @root.preprompt}}Source: system\n\n {{@root.preprompt}} <step> {{/if}}{{#each messages}}{{#ifUser}}Source: user\n\n {{content}} <step> {{/ifUser}}{{#ifAssistant}}Source: assistant\n\n {{content}} <step> {{/ifAssistant}}{{/each}}Source: assistant\nDestination: user\n\n ``
63
- ```
64
-
65
- ## Gemma
66
-
67
- ```env
68
- {{#each messages}}{{#ifUser}}<start_of_turn>user\n{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}<end_of_turn>\n<start_of_turn>model\n{{/ifUser}}{{#ifAssistant}}{{content}}<end_of_turn>\n{{/ifAssistant}}{{/each}}
69
- ```
 
43
  ```env
44
  <s>{{#each messages}}{{#ifUser}}GPT4 User: {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}}<|end_of_turn|>GPT4 Assistant: {{/ifUser}}{{#ifAssistant}}{{content}}<|end_of_turn|>{{/ifAssistant}}{{/each}}
45
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -8,8 +8,6 @@ pinned: false
8
  license: apache-2.0
9
  base_path: /chat
10
  app_port: 3000
11
- failure_strategy: rollback
12
- load_balancing_strategy: random
13
  ---
14
 
15
  # Chat UI
@@ -22,19 +20,17 @@ A chat interface using open source models, eg OpenAssistant or Llama. It is a Sv
22
  1. [Setup](#setup)
23
  2. [Launch](#launch)
24
  3. [Web Search](#web-search)
25
- 4. [Text Embedding Models](#text-embedding-models)
26
- 5. [Extra parameters](#extra-parameters)
27
- 6. [Common issues](#common-issues)
28
- 7. [Deploying to a HF Space](#deploying-to-a-hf-space)
29
- 8. [Building](#building)
30
 
31
- ## No Setup Deploy
32
 
33
  If you don't want to configure, setup, and launch your own Chat UI yourself, you can use this option as a fast deploy alternative.
34
 
35
  You can deploy your own customized Chat UI instance with any supported [LLM](https://huggingface.co/models?pipeline_tag=text-generation&sort=trending) of your choice on [Hugging Face Spaces](https://huggingface.co/spaces). To do so, use the chat-ui template [available here](https://huggingface.co/new-space?template=huggingchat/chat-ui-template).
36
 
37
- Set `HF_TOKEN` in [Space secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables) to deploy a model with gated access or a model in a private repository. It's also compatible with [Inference for PROs](https://huggingface.co/blog/inference-pro) curated list of powerful models with higher rate limits. Make sure to create your personal token first in your [User Access Tokens settings](https://huggingface.co/settings/tokens).
38
 
39
  Read the full tutorial [here](https://huggingface.co/docs/hub/spaces-sdks-docker-chatui#chatui-on-spaces).
40
 
@@ -46,7 +42,7 @@ Start by creating a `.env.local` file in the root of the repository. The bare mi
46
 
47
  ```env
48
  MONGODB_URL=<the URL to your MongoDB instance>
49
- HF_TOKEN=<your access token>
50
  ```
51
 
52
  ### Database
@@ -82,50 +78,10 @@ Chat UI features a powerful Web Search feature. It works by:
82
 
83
  1. Generating an appropriate search query from the user prompt.
84
  2. Performing web search and extracting content from webpages.
85
- 3. Creating embeddings from texts using a text embedding model.
86
  4. From these embeddings, find the ones that are closest to the user query using a vector similarity search. Specifically, we use `inner product` distance.
87
  5. Get the corresponding texts to those closest embeddings and perform [Retrieval-Augmented Generation](https://huggingface.co/papers/2005.11401) (i.e. expand user prompt by adding those texts so that an LLM can use this information).
88
 
89
- ## Text Embedding Models
90
-
91
- By default (for backward compatibility), when `TEXT_EMBEDDING_MODELS` environment variable is not defined, [transformers.js](https://huggingface.co/docs/transformers.js) embedding models will be used for embedding tasks, specifically, [Xenova/gte-small](https://huggingface.co/Xenova/gte-small) model.
92
-
93
- You can customize the embedding model by setting `TEXT_EMBEDDING_MODELS` in your `.env.local` file. For example:
94
-
95
- ```env
96
- TEXT_EMBEDDING_MODELS = `[
97
- {
98
- "name": "Xenova/gte-small",
99
- "displayName": "Xenova/gte-small",
100
- "description": "locally running embedding",
101
- "chunkCharLength": 512,
102
- "endpoints": [
103
- {"type": "transformersjs"}
104
- ]
105
- },
106
- {
107
- "name": "intfloat/e5-base-v2",
108
- "displayName": "intfloat/e5-base-v2",
109
- "description": "hosted embedding model",
110
- "chunkCharLength": 768,
111
- "preQuery": "query: ", # See https://huggingface.co/intfloat/e5-base-v2#faq
112
- "prePassage": "passage: ", # See https://huggingface.co/intfloat/e5-base-v2#faq
113
- "endpoints": [
114
- {
115
- "type": "tei",
116
- "url": "http://127.0.0.1:8080/",
117
- "authorization": "TOKEN_TYPE TOKEN" // optional authorization field. Example: "Basic VVNFUjpQQVNT"
118
- }
119
- ]
120
- }
121
- ]`
122
- ```
123
-
124
- The required fields are `name`, `chunkCharLength` and `endpoints`.
125
- Supported text embedding backends are: [`transformers.js`](https://huggingface.co/docs/transformers.js), [`TEI`](https://github.com/huggingface/text-embeddings-inference) and [`OpenAI`](https://platform.openai.com/docs/guides/embeddings). `transformers.js` models run locally as part of `chat-ui`, whereas `TEI` models run in a different environment & accessed through an API endpoint. `openai` models are accessed through the [OpenAI API](https://platform.openai.com/docs/guides/embeddings).
126
-
127
- When more than one embedding models are supplied in `.env.local` file, the first will be used by default, and the others will only be used on LLM's which configured `embeddingModel` to the name of the model.
128
-
129
  ## Extra parameters
130
 
131
  ### OpenID connect
@@ -166,9 +122,9 @@ PUBLIC_APP_DISCLAIMER=
166
 
167
  ### Web Search config
168
 
169
- You can enable the web search through an API by adding `YDC_API_KEY` ([docs.you.com](https://docs.you.com)) or `SERPER_API_KEY` ([serper.dev](https://serper.dev/)) or `SERPAPI_KEY` ([serpapi.com](https://serpapi.com/)) or `SERPSTACK_API_KEY` ([serpstack.com](https://serpstack.com/)) to your `.env.local`.
170
 
171
- You can also simply enable the local google websearch by setting `USE_LOCAL_WEBSEARCH=true` in your `.env.local` or specify a SearXNG instance by adding the query URL to `SEARXNG_QUERY_URL`.
172
 
173
  ### Custom models
174
 
@@ -177,33 +133,36 @@ You can customize the parameters passed to the model or even use a new model by
177
  ```env
178
  MODELS=`[
179
  {
180
- "name": "mistralai/Mistral-7B-Instruct-v0.2",
181
- "displayName": "mistralai/Mistral-7B-Instruct-v0.2",
182
- "description": "Mistral 7B is a new Apache 2.0 model, released by Mistral AI that outperforms Llama2 13B in benchmarks.",
183
- "websiteUrl": "https://mistral.ai/news/announcing-mistral-7b/",
184
- "preprompt": "",
185
- "chatPromptTemplate" : "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}}{{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s>{{/ifAssistant}}{{/each}}",
186
- "parameters": {
187
- "temperature": 0.3,
188
- "top_p": 0.95,
189
- "repetition_penalty": 1.2,
190
- "top_k": 50,
191
- "truncate": 3072,
192
- "max_new_tokens": 1024,
193
- "stop": ["</s>"]
194
- },
195
  "promptExamples": [
196
  {
197
  "title": "Write an email from bullet list",
198
  "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
199
  }, {
200
  "title": "Code a snake game",
201
- "prompt": "Code a basic snake game in python, give explanations for each step."
202
  }, {
203
  "title": "Assist in a task",
204
  "prompt": "How do I make a delicious lemon cheesecake?"
205
  }
206
- ]
 
 
 
 
 
 
 
 
 
207
  }
208
  ]`
209
 
@@ -228,7 +187,7 @@ The following is the default `chatPromptTemplate`, although newlines and indenti
228
 
229
  #### Multi modal model
230
 
231
- We currently only support IDEFICS as a multimodal model, hosted on TGI. You can enable it by using the following config (if you have a PRO HF Api token):
232
 
233
  ```env
234
  {
@@ -318,78 +277,6 @@ MODELS=`[{
318
  }]`
319
  ```
320
 
321
- You may also consume any model provider that provides compatible OpenAI API endpoint. For example, you may self-host [Portkey](https://github.com/Portkey-AI/gateway) gateway and experiment with Claude or GPTs offered by Azure OpenAI. Example for Claude from Anthropic:
322
-
323
- ```
324
- MODELS=`[{
325
- "name": "claude-2.1",
326
- "displayName": "Claude 2.1",
327
- "description": "Anthropic has been founded by former OpenAI researchers...",
328
- "parameters": {
329
- "temperature": 0.5,
330
- "max_new_tokens": 4096,
331
- },
332
- "endpoints": [
333
- {
334
- "type": "openai",
335
- "baseURL": "https://gateway.example.com/v1",
336
- "defaultHeaders": {
337
- "x-portkey-config": '{"provider":"anthropic","api_key":"sk-ant-abc...xyz"}'
338
- }
339
- }
340
- ]
341
- }]`
342
- ```
343
-
344
- Example for GPT 4 deployed on Azure OpenAI:
345
-
346
- ```
347
- MODELS=`[{
348
- "id": "gpt-4-1106-preview",
349
- "name": "gpt-4-1106-preview",
350
- "displayName": "gpt-4-1106-preview",
351
- "parameters": {
352
- "temperature": 0.5,
353
- "max_new_tokens": 4096,
354
- },
355
- "endpoints": [
356
- {
357
- "type": "openai",
358
- "baseURL": "https://{resource-name}.openai.azure.com/openai/deployments/{deployment-id}",
359
- "defaultHeaders": {
360
- "api-key": "{api-key}"
361
- },
362
- "defaultQuery": {
363
- "api-version": "2023-05-15"
364
- }
365
- }
366
- ]
367
- }]`
368
- ```
369
-
370
- Or try Mistral from [Deepinfra](https://deepinfra.com/mistralai/Mistral-7B-Instruct-v0.1/api?example=openai-http):
371
-
372
- > Note, apiKey can either be set custom per endpoint, or globally using `OPENAI_API_KEY` variable.
373
-
374
- ```
375
- MODELS=`[{
376
- "name": "mistral-7b",
377
- "displayName": "Mistral 7B",
378
- "description": "A 7B dense Transformer, fast-deployed and easily customisable. Small, yet powerful for a variety of use cases. Supports English and code, and a 8k context window.",
379
- "parameters": {
380
- "temperature": 0.5,
381
- "max_new_tokens": 4096,
382
- },
383
- "endpoints": [
384
- {
385
- "type": "openai",
386
- "baseURL": "https://api.deepinfra.com/v1/openai",
387
- "apiKey": "abc...xyz"
388
- }
389
- ]
390
- }]`
391
- ```
392
-
393
  ##### Llama.cpp API server
394
 
395
  chat-ui also supports the llama.cpp API server directly without the need for an adapter. You can do this using the `llamacpp` endpoint type.
@@ -401,7 +288,7 @@ If you want to run chat-ui with llama.cpp, you can do the following, using Zephy
401
  3. Add the following to your `.env.local`:
402
 
403
  ```env
404
- MODELS=`[
405
  {
406
  "name": "Local Zephyr",
407
  "chatPromptTemplate": "<|system|>\n{{preprompt}}</s>\n{{#each messages}}{{#ifUser}}<|user|>\n{{content}}</s>\n<|assistant|>\n{{/ifUser}}{{#ifAssistant}}{{content}}</s>\n{{/ifAssistant}}{{/each}}",
@@ -421,7 +308,7 @@ MODELS=`[
421
  }
422
  ]
423
  }
424
- ]`
425
  ```
426
 
427
  Start chat-ui with `npm run dev` and you should be able to chat with Zephyr locally.
@@ -437,7 +324,7 @@ ollama run mistral
437
  Then specify the endpoints like so:
438
 
439
  ```env
440
- MODELS=`[
441
  {
442
  "name": "Ollama Mistral",
443
  "chatPromptTemplate": "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s> {{/ifAssistant}}{{/each}}",
@@ -458,52 +345,7 @@ MODELS=`[
458
  }
459
  ]
460
  }
461
- ]`
462
- ```
463
-
464
- #### Anthropic
465
-
466
- We also support Anthropic models through the official SDK. You may provide your API key via the `ANTHROPIC_API_KEY` env variable, or alternatively, through the `endpoints.apiKey` as per the following example.
467
-
468
- ```
469
- MODELS=`[
470
- {
471
- "name": "claude-3-sonnet-20240229",
472
- "displayName": "Claude 3 Sonnet",
473
- "description": "Ideal balance of intelligence and speed",
474
- "parameters": {
475
- "max_new_tokens": 4096,
476
- },
477
- "endpoints": [
478
- {
479
- "type": "anthropic",
480
- // optionals
481
- "apiKey": "sk-ant-...",
482
- "baseURL": "https://api.anthropic.com",
483
- defaultHeaders: {},
484
- defaultQuery: {}
485
- }
486
- ]
487
- },
488
- {
489
- "name": "claude-3-opus-20240229",
490
- "displayName": "Claude 3 Opus",
491
- "description": "Most powerful model for highly complex tasks",
492
- "parameters": {
493
- "max_new_tokens": 4096
494
- },
495
- "endpoints": [
496
- {
497
- "type": "anthropic",
498
- // optionals
499
- "apiKey": "sk-ant-...",
500
- "baseURL": "https://api.anthropic.com",
501
- defaultHeaders: {},
502
- defaultQuery: {}
503
- }
504
- ]
505
- }
506
- ]`
507
  ```
508
 
509
  #### Amazon
@@ -530,120 +372,6 @@ You can also set `"service" : "lambda"` to use a lambda instance.
530
 
531
  You can get the `accessKey` and `secretKey` from your AWS user, under programmatic access.
532
 
533
- #### Cloudflare Workers AI
534
-
535
- You can also use Cloudflare Workers AI to run your own models with serverless inference.
536
-
537
- You will need to have a Cloudflare account, then get your [account ID](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/) as well as your [API token](https://developers.cloudflare.com/workers-ai/get-started/rest-api/#1-get-an-api-token) for Workers AI.
538
-
539
- You can either specify them directly in your `.env.local` using the `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN` variables, or you can set them directly in the endpoint config.
540
-
541
- You can find the list of models available on Cloudflare [here](https://developers.cloudflare.com/workers-ai/models/#text-generation).
542
-
543
- ```env
544
- {
545
- "name" : "nousresearch/hermes-2-pro-mistral-7b",
546
- "tokenizer": "nousresearch/hermes-2-pro-mistral-7b",
547
- "parameters": {
548
- "stop": ["<|im_end|>"]
549
- },
550
- "endpoints" : [
551
- {
552
- "type" : "cloudflare"
553
- <!-- optionally specify these
554
- "accountId": "your-account-id",
555
- "authToken": "your-api-token"
556
- -->
557
- }
558
- ]
559
- }
560
- ```
561
-
562
- > [!NOTE]
563
- > Cloudlare Workers AI currently do not support custom sampling parameters like temperature, top_p, etc.
564
-
565
- #### Cohere
566
-
567
- You can also use Cohere to run their models directly from chat-ui. You will need to have a Cohere account, then get your [API token](https://dashboard.cohere.com/api-keys). You can either specify it directly in your `.env.local` using the `COHERE_API_TOKEN` variable, or you can set it in the endpoint config.
568
-
569
- Here is an example of a Cohere model config. You can set which model you want to use by setting the `id` field to the model name.
570
-
571
- ```env
572
- {
573
- "name" : "CohereForAI/c4ai-command-r-v01",
574
- "id": "command-r",
575
- "description": "C4AI Command-R is a research release of a 35 billion parameter highly performant generative model",
576
- "endpoints": [
577
- {
578
- "type": "cohere",
579
- <!-- optionally specify these, or use COHERE_API_TOKEN
580
- "apiKey": "your-api-token"
581
- -->
582
- }
583
- ]
584
- }
585
- ```
586
-
587
- ##### Google Vertex models
588
-
589
- Chat UI can connect to the google Vertex API endpoints ([List of supported models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models)).
590
-
591
- To enable:
592
-
593
- 1. [Select](https://console.cloud.google.com/project) or [create](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project) a Google Cloud project.
594
- 1. [Enable billing for your project](https://cloud.google.com/billing/docs/how-to/modify-project).
595
- 1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).
596
- 1. [Set up authentication with a service account](https://cloud.google.com/docs/authentication/getting-started)
597
- so you can access the API from your local workstation.
598
-
599
- The service account credentials file can be imported as an environmental variable:
600
-
601
- ```env
602
- GOOGLE_APPLICATION_CREDENTIALS = clientid.json
603
- ```
604
-
605
- Make sure your docker container has access to the file and the variable is correctly set.
606
- Afterwards Google Vertex endpoints can be configured as following:
607
-
608
- ```
609
- MODELS=`[
610
- //...
611
- {
612
- "name": "gemini-1.5-pro",
613
- "displayName": "Vertex Gemini Pro 1.5",
614
- "endpoints" : [{
615
- "type": "vertex",
616
- "project": "abc-xyz",
617
- "location": "europe-west3",
618
- "model": "gemini-1.5-pro-preview-0409", // model-name
619
-
620
- // Optional
621
- "safetyThreshold": "BLOCK_MEDIUM_AND_ABOVE",
622
- "apiEndpoint": "", // alternative api endpoint url
623
- }]
624
- },
625
- ]`
626
-
627
- ```
628
-
629
- ##### LangServe
630
-
631
- LangChain applications that are deployed using LangServe can be called with the following config:
632
-
633
- ```
634
- MODELS=`[
635
- //...
636
- {
637
- "name": "summarization-chain", //model-name
638
- "endpoints" : [{
639
- "type": "langserve",
640
- "url" : "http://127.0.0.1:8100",
641
- }]
642
- },
643
- ]`
644
-
645
- ```
646
-
647
  ### Custom endpoint authorization
648
 
649
  #### Basic and Bearer
@@ -662,30 +390,28 @@ You can then add the generated information and the `authorization` parameter to
662
 
663
  ```env
664
  "endpoints": [
665
- {
666
- "url": "https://HOST:PORT",
667
- "authorization": "Basic VVNFUjpQQVNT",
668
- }
669
  ]
670
  ```
671
 
672
- Please note that if `HF_TOKEN` is also set or not empty, it will take precedence.
673
-
674
  #### Models hosted on multiple custom endpoints
675
 
676
  If the model being hosted will be available on multiple servers/instances add the `weight` parameter to your `.env.local`. The `weight` will be used to determine the probability of requesting a particular endpoint.
677
 
678
  ```env
679
  "endpoints": [
680
- {
681
- "url": "https://HOST:PORT",
682
- "weight": 1
683
- },
684
- {
685
- "url": "https://HOST:PORT",
686
- "weight": 2
687
- }
688
- ...
689
  ]
690
  ```
691
 
@@ -697,53 +423,6 @@ If you're using a certificate signed by a private CA, you will also need to add
697
 
698
  If you're using a self-signed certificate, e.g. for testing or development purposes, you can set the `REJECT_UNAUTHORIZED` parameter to `false` in your `.env.local`. This will disable certificate validation, and allow Chat UI to connect to your custom endpoint.
699
 
700
- #### Specific Embedding Model
701
-
702
- A model can use any of the embedding models defined in `.env.local`, (currently used when web searching),
703
- by default it will use the first embedding model, but it can be changed with the field `embeddingModel`:
704
-
705
- ```env
706
- TEXT_EMBEDDING_MODELS = `[
707
- {
708
- "name": "Xenova/gte-small",
709
- "chunkCharLength": 512,
710
- "endpoints": [
711
- {"type": "transformersjs"}
712
- ]
713
- },
714
- {
715
- "name": "intfloat/e5-base-v2",
716
- "chunkCharLength": 768,
717
- "endpoints": [
718
- {"type": "tei", "url": "http://127.0.0.1:8080/", "authorization": "Basic VVNFUjpQQVNT"},
719
- {"type": "tei", "url": "http://127.0.0.1:8081/"}
720
- ]
721
- }
722
- ]`
723
-
724
- MODELS=`[
725
- {
726
- "name": "Ollama Mistral",
727
- "chatPromptTemplate": "...",
728
- "embeddingModel": "intfloat/e5-base-v2"
729
- "parameters": {
730
- ...
731
- },
732
- "endpoints": [
733
- ...
734
- ]
735
- }
736
- ]`
737
- ```
738
-
739
- ## Common issues
740
-
741
- ### 403:You don't have access to this conversation
742
-
743
- Most likely you are running chat-ui over HTTP. The recommended option is to setup something like NGINX to handle HTTPS and proxy the requests to chat-ui. If you really need to run over HTTP you can add `ALLOW_INSECURE_COOKIES=true` to your `.env.local`.
744
-
745
- Make sure to set your `PUBLIC_ORIGIN` in your `.env.local` to the correct URL as well.
746
-
747
  ## Deploying to a HF Space
748
 
749
  Create a `DOTENV_LOCAL` secret to your HF space with the content of your .env.local, and they will be picked up automatically when you run.
@@ -759,76 +438,3 @@ npm run build
759
  You can preview the production build with `npm run preview`.
760
 
761
  > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
762
-
763
- ## Config changes for HuggingChat
764
-
765
- The config file for HuggingChat is stored in the `.env.template` file at the root of the repository. It is the single source of truth that is used to generate the actual `.env.local` file using our CI/CD pipeline. See [updateProdEnv](https://github.com/huggingface/chat-ui/blob/cdb33a9583f5339ade724db615347393ef48f5cd/scripts/updateProdEnv.ts) for more details.
766
-
767
- > [!TIP]
768
- > If you want to make changes to the model config used in production for HuggingChat, you should do so against `.env.template`.
769
-
770
- We currently use the following secrets for deploying HuggingChat in addition to the `.env.template` above:
771
-
772
- - `MONGODB_URL`
773
- - `HF_TOKEN`
774
- - `OPENID_CONFIG`
775
- - `SERPER_API_KEY`
776
-
777
- ### Running a copy of HuggingChat locally
778
-
779
- If you want to run an exact copy of HuggingChat locally, you will need to do the following first:
780
-
781
- 1. Create an [OAuth App on the hub](https://huggingface.co/settings/applications/new) with `openid profile email` permissions. Make sure to set the callback URL to something like `http://localhost:5173/chat/login/callback` which matches the right path for your local instance.
782
- 2. Create a [HF Token](https://huggingface.co/settings/tokens) with your Hugging Face account. You will need a Pro account to be able to access some of the larger models available through HuggingChat.
783
- 3. Create a free account with [serper.dev](https://serper.dev/) (you will get 2500 free search queries)
784
- 4. Run an instance of mongoDB, however you want. (Local or remote)
785
-
786
- You can then create a new `.env.SECRET_CONFIG` file with the following content
787
-
788
- ```env
789
- MONGODB_URL=<link to your mongo DB from step 4>
790
- HF_TOKEN=<your HF token from step 2>
791
- OPENID_CONFIG=`{
792
- PROVIDER_URL: "https://huggingface.co",
793
- CLIENT_ID: "<your client ID from step 1>",
794
- CLIENT_SECRET: "<your client secret from step 1>",
795
- }`
796
- SERPER_API_KEY=<your serper API key from step 3>
797
- MESSAGES_BEFORE_LOGIN=<can be any numerical value, or set to 0 to require login>
798
- ```
799
-
800
- You can then run `npm run updateLocalEnv` in the root of chat-ui. This will create a `.env.local` file which combines the `.env.template` and the `.env.SECRET_CONFIG` file. You can then run `npm run dev` to start your local instance of HuggingChat.
801
-
802
- ### Populate database
803
-
804
- > [!WARNING]
805
- > The `MONGODB_URL` used for this script will be fetched from `.env.local`. Make sure it's correct! The command runs directly on the database.
806
-
807
- You can populate the database using faker data using the `populate` script:
808
-
809
- ```bash
810
- npm run populate <flags here>
811
- ```
812
-
813
- At least one flag must be specified, the following flags are available:
814
-
815
- - `reset` - resets the database
816
- - `all` - populates all tables
817
- - `users` - populates the users table
818
- - `settings` - populates the settings table for existing users
819
- - `assistants` - populates the assistants table for existing users
820
- - `conversations` - populates the conversations table for existing users
821
-
822
- For example, you could use it like so:
823
-
824
- ```bash
825
- npm run populate reset
826
- ```
827
-
828
- to clear out the database. Then login in the app to create your user and run the following command:
829
-
830
- ```bash
831
- npm run populate users settings assistants conversations
832
- ```
833
-
834
- to populate the database with fake data, including fake conversations and assistants for your user.
 
8
  license: apache-2.0
9
  base_path: /chat
10
  app_port: 3000
 
 
11
  ---
12
 
13
  # Chat UI
 
20
  1. [Setup](#setup)
21
  2. [Launch](#launch)
22
  3. [Web Search](#web-search)
23
+ 4. [Extra parameters](#extra-parameters)
24
+ 5. [Deploying to a HF Space](#deploying-to-a-hf-space)
25
+ 6. [Building](#building)
 
 
26
 
27
+ ##  No Setup Deploy
28
 
29
  If you don't want to configure, setup, and launch your own Chat UI yourself, you can use this option as a fast deploy alternative.
30
 
31
  You can deploy your own customized Chat UI instance with any supported [LLM](https://huggingface.co/models?pipeline_tag=text-generation&sort=trending) of your choice on [Hugging Face Spaces](https://huggingface.co/spaces). To do so, use the chat-ui template [available here](https://huggingface.co/new-space?template=huggingchat/chat-ui-template).
32
 
33
+ Set `HUGGING_FACE_HUB_TOKEN` in [Space secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables) to deploy a model with gated access or a model in a private repository. It's also compatible with [Inference for PROs](https://huggingface.co/blog/inference-pro) curated list of powerful models with higher rate limits. Make sure to create your personal token first in your [User Access Tokens settings](https://huggingface.co/settings/tokens).
34
 
35
  Read the full tutorial [here](https://huggingface.co/docs/hub/spaces-sdks-docker-chatui#chatui-on-spaces).
36
 
 
42
 
43
  ```env
44
  MONGODB_URL=<the URL to your MongoDB instance>
45
+ HF_ACCESS_TOKEN=<your access token>
46
  ```
47
 
48
  ### Database
 
78
 
79
  1. Generating an appropriate search query from the user prompt.
80
  2. Performing web search and extracting content from webpages.
81
+ 3. Creating embeddings from texts using [transformers.js](https://huggingface.co/docs/transformers.js). Specifically, using [Xenova/gte-small](https://huggingface.co/Xenova/gte-small) model.
82
  4. From these embeddings, find the ones that are closest to the user query using a vector similarity search. Specifically, we use `inner product` distance.
83
  5. Get the corresponding texts to those closest embeddings and perform [Retrieval-Augmented Generation](https://huggingface.co/papers/2005.11401) (i.e. expand user prompt by adding those texts so that an LLM can use this information).
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  ## Extra parameters
86
 
87
  ### OpenID connect
 
122
 
123
  ### Web Search config
124
 
125
+ You can enable the web search through an API by adding `YDC_API_KEY` ([docs.you.com](https://docs.you.com)) or `SERPER_API_KEY` ([serper.dev](https://serper.dev/)) or `SERPAPI_KEY` ([serpapi.com](https://serpapi.com/)) to your `.env.local`.
126
 
127
+ You can also simply enable the local websearch by setting `USE_LOCAL_WEBSEARCH=true` in your `.env.local`.
128
 
129
  ### Custom models
130
 
 
133
  ```env
134
  MODELS=`[
135
  {
136
+ "name": "OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5",
137
+ "datasetName": "OpenAssistant/oasst1",
138
+ "description": "A good alternative to ChatGPT",
139
+ "websiteUrl": "https://open-assistant.io",
140
+ "userMessageToken": "<|prompter|>", # This does not need to be a token, can be any string
141
+ "assistantMessageToken": "<|assistant|>", # This does not need to be a token, can be any string
142
+ "userMessageEndToken": "<|endoftext|>", # Applies only to user messages. Can be any string.
143
+ "assistantMessageEndToken": "<|endoftext|>", # Applies only to assistant messages. Can be any string.
144
+ "preprompt": "Below are a series of dialogues between various people and an AI assistant. The AI tries to be helpful, polite, honest, sophisticated, emotionally aware, and humble but knowledgeable. The assistant is happy to help with almost anything and will do its best to understand exactly what is needed. It also tries to avoid giving false or misleading information, and it caveats when it isn't entirely sure about the right answer. That said, the assistant is practical and really does its best, and doesn't let caution get too much in the way of being useful.\n-----\n",
 
 
 
 
 
 
145
  "promptExamples": [
146
  {
147
  "title": "Write an email from bullet list",
148
  "prompt": "As a restaurant owner, write a professional email to the supplier to get these products every week: \n\n- Wine (x10)\n- Eggs (x24)\n- Bread (x12)"
149
  }, {
150
  "title": "Code a snake game",
151
+ "prompt": "Code a basic snake game in python and give explanations for each step."
152
  }, {
153
  "title": "Assist in a task",
154
  "prompt": "How do I make a delicious lemon cheesecake?"
155
  }
156
+ ],
157
+ "parameters": {
158
+ "temperature": 0.9,
159
+ "top_p": 0.95,
160
+ "repetition_penalty": 1.2,
161
+ "top_k": 50,
162
+ "truncate": 1000,
163
+ "max_new_tokens": 1024,
164
+ "stop": ["<|endoftext|>"] # This does not need to be tokens, can be any list of strings
165
+ }
166
  }
167
  ]`
168
 
 
187
 
188
  #### Multi modal model
189
 
190
+ We currently only support IDEFICS as a multimodal model, hosted on TGI. You can enable it by using the followin config (if you have a PRO HF Api token):
191
 
192
  ```env
193
  {
 
277
  }]`
278
  ```
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  ##### Llama.cpp API server
281
 
282
  chat-ui also supports the llama.cpp API server directly without the need for an adapter. You can do this using the `llamacpp` endpoint type.
 
288
  3. Add the following to your `.env.local`:
289
 
290
  ```env
291
+ MODELS=[
292
  {
293
  "name": "Local Zephyr",
294
  "chatPromptTemplate": "<|system|>\n{{preprompt}}</s>\n{{#each messages}}{{#ifUser}}<|user|>\n{{content}}</s>\n<|assistant|>\n{{/ifUser}}{{#ifAssistant}}{{content}}</s>\n{{/ifAssistant}}{{/each}}",
 
308
  }
309
  ]
310
  }
311
+ ]
312
  ```
313
 
314
  Start chat-ui with `npm run dev` and you should be able to chat with Zephyr locally.
 
324
  Then specify the endpoints like so:
325
 
326
  ```env
327
+ MODELS=[
328
  {
329
  "name": "Ollama Mistral",
330
  "chatPromptTemplate": "<s>{{#each messages}}{{#ifUser}}[INST] {{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}}{{content}}</s> {{/ifAssistant}}{{/each}}",
 
345
  }
346
  ]
347
  }
348
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  ```
350
 
351
  #### Amazon
 
372
 
373
  You can get the `accessKey` and `secretKey` from your AWS user, under programmatic access.
374
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  ### Custom endpoint authorization
376
 
377
  #### Basic and Bearer
 
390
 
391
  ```env
392
  "endpoints": [
393
+ {
394
+ "url": "https://HOST:PORT",
395
+ "authorization": "Basic VVNFUjpQQVNT",
396
+ }
397
  ]
398
  ```
399
 
 
 
400
  #### Models hosted on multiple custom endpoints
401
 
402
  If the model being hosted will be available on multiple servers/instances add the `weight` parameter to your `.env.local`. The `weight` will be used to determine the probability of requesting a particular endpoint.
403
 
404
  ```env
405
  "endpoints": [
406
+ {
407
+ "url": "https://HOST:PORT",
408
+ "weight": 1
409
+ }
410
+ {
411
+ "url": "https://HOST:PORT",
412
+ "weight": 2
413
+ }
414
+ ...
415
  ]
416
  ```
417
 
 
423
 
424
  If you're using a self-signed certificate, e.g. for testing or development purposes, you can set the `REJECT_UNAUTHORIZED` parameter to `false` in your `.env.local`. This will disable certificate validation, and allow Chat UI to connect to your custom endpoint.
425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  ## Deploying to a HF Space
427
 
428
  Create a `DOTENV_LOCAL` secret to your HF space with the content of your .env.local, and they will be picked up automatically when you run.
 
438
  You can preview the production build with `npm run preview`.
439
 
440
  > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.env.ci → conf/.env.ci RENAMED
File without changes
entrypoint.sh DELETED
@@ -1,19 +0,0 @@
1
- ENV_LOCAL_PATH=/app/.env.local
2
-
3
- if test -z "${DOTENV_LOCAL}" ; then
4
- if ! test -f "${ENV_LOCAL_PATH}" ; then
5
- echo "DOTENV_LOCAL was not found in the ENV variables and .env.local is not set using a bind volume. Make sure to set environment variables properly. "
6
- fi;
7
- else
8
- echo "DOTENV_LOCAL was found in the ENV variables. Creating .env.local file."
9
- cat <<< "$DOTENV_LOCAL" > ${ENV_LOCAL_PATH}
10
- fi;
11
-
12
- if [ "$INCLUDE_DB" = "true" ] ; then
13
- echo "Starting local MongoDB instance"
14
- nohup mongod &
15
- fi;
16
-
17
- export PUBLIC_VERSION=$(node -p "require('./package.json').version")
18
-
19
- dotenv -e /app/.env -c -- node /app/build/index.js -- --host 0.0.0.0 --port 3000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "chat-ui",
3
- "version": "0.8.3",
4
  "private": true,
5
  "packageManager": "npm@9.5.0",
6
  "scripts": {
@@ -13,83 +13,61 @@
13
  "format": "prettier --plugin-search-dir . --write .",
14
  "test": "MONGODB_URL=mongodb://127.0.0.1:27017/ vitest",
15
  "updateLocalEnv": "node --loader ts-node/esm scripts/updateLocalEnv.ts",
16
- "updateProdEnv": "node --loader ts-node/esm scripts/updateProdEnv.ts",
17
- "populate": "vite-node --options.transformMode.ssr='/.*/' scripts/populate.ts"
18
  },
19
  "devDependencies": {
20
- "@faker-js/faker": "^8.4.1",
21
  "@iconify-json/carbon": "^1.1.16",
22
  "@iconify-json/eos-icons": "^1.1.6",
23
- "@sveltejs/adapter-node": "^1.3.1",
24
- "@sveltejs/kit": "^1.30.4",
25
  "@tailwindcss/typography": "^0.5.9",
26
- "@types/express": "^4.17.21",
27
  "@types/jsdom": "^21.1.1",
28
- "@types/minimist": "^1.2.5",
29
  "@types/parquetjs": "^0.10.3",
30
- "@types/uuid": "^9.0.8",
31
  "@typescript-eslint/eslint-plugin": "^6.x",
32
  "@typescript-eslint/parser": "^6.x",
33
  "eslint": "^8.28.0",
34
  "eslint-config-prettier": "^8.5.0",
35
  "eslint-plugin-svelte": "^2.30.0",
36
- "minimist": "^1.2.8",
37
  "prettier": "^2.8.0",
38
  "prettier-plugin-svelte": "^2.10.1",
39
  "prettier-plugin-tailwindcss": "^0.2.7",
40
- "prom-client": "^15.1.2",
41
- "svelte": "^4.2.8",
42
- "svelte-check": "^3.6.2",
43
  "ts-node": "^10.9.1",
44
  "tslib": "^2.4.1",
45
  "typescript": "^5.0.0",
46
  "unplugin-icons": "^0.16.1",
47
- "vite": "^4.5.3",
48
- "vite-node": "^1.3.1",
49
  "vitest": "^0.31.0"
50
  },
51
  "type": "module",
52
  "dependencies": {
53
  "@huggingface/hub": "^0.5.1",
54
  "@huggingface/inference": "^2.6.3",
55
- "@iconify-json/bi": "^1.1.21",
56
- "@resvg/resvg-js": "^2.6.0",
57
- "@xenova/transformers": "^2.16.1",
58
  "autoprefixer": "^10.4.14",
59
  "browser-image-resizer": "^2.4.1",
60
  "date-fns": "^2.29.3",
61
  "dotenv": "^16.0.3",
62
- "express": "^4.19.2",
63
  "handlebars": "^4.7.8",
64
  "highlight.js": "^11.7.0",
65
  "image-size": "^1.0.2",
66
- "ip-address": "^9.0.5",
67
  "jsdom": "^22.0.0",
68
- "json5": "^2.2.3",
69
- "marked": "^12.0.1",
70
- "marked-katex-extension": "^5.0.1",
71
  "mongodb": "^5.8.0",
72
  "nanoid": "^4.0.2",
73
  "openid-client": "^5.4.2",
74
  "parquetjs": "^0.11.2",
75
- "pino": "^9.0.0",
76
- "pino-pretty": "^11.0.0",
77
  "postcss": "^8.4.31",
78
- "saslprep": "^1.0.3",
79
- "satori": "^0.10.11",
80
- "satori-html": "^0.3.2",
81
  "serpapi": "^1.1.1",
82
- "sharp": "^0.33.2",
83
  "tailwind-scrollbar": "^3.0.0",
84
- "tailwindcss": "^3.4.0",
85
- "uuid": "^9.0.1",
86
  "zod": "^3.22.3"
87
  },
88
  "optionalDependencies": {
89
- "@anthropic-ai/sdk": "^0.17.1",
90
- "@google-cloud/vertexai": "^1.1.0",
91
  "aws4fetch": "^1.0.17",
92
- "cohere-ai": "^7.9.0",
93
  "openai": "^4.14.2"
94
  }
95
  }
 
1
  {
2
  "name": "chat-ui",
3
+ "version": "0.6.0",
4
  "private": true,
5
  "packageManager": "npm@9.5.0",
6
  "scripts": {
 
13
  "format": "prettier --plugin-search-dir . --write .",
14
  "test": "MONGODB_URL=mongodb://127.0.0.1:27017/ vitest",
15
  "updateLocalEnv": "node --loader ts-node/esm scripts/updateLocalEnv.ts",
16
+ "updateProdEnv": "node --loader ts-node/esm scripts/updateProdEnv.ts"
 
17
  },
18
  "devDependencies": {
 
19
  "@iconify-json/carbon": "^1.1.16",
20
  "@iconify-json/eos-icons": "^1.1.6",
21
+ "@sveltejs/adapter-node": "^1.2.4",
22
+ "@sveltejs/kit": "^1.26.0",
23
  "@tailwindcss/typography": "^0.5.9",
 
24
  "@types/jsdom": "^21.1.1",
25
+ "@types/marked": "^4.0.8",
26
  "@types/parquetjs": "^0.10.3",
 
27
  "@typescript-eslint/eslint-plugin": "^6.x",
28
  "@typescript-eslint/parser": "^6.x",
29
  "eslint": "^8.28.0",
30
  "eslint-config-prettier": "^8.5.0",
31
  "eslint-plugin-svelte": "^2.30.0",
32
+ "marked-katex-extension": "^3.0.6",
33
  "prettier": "^2.8.0",
34
  "prettier-plugin-svelte": "^2.10.1",
35
  "prettier-plugin-tailwindcss": "^0.2.7",
36
+ "svelte": "^4.0.0",
37
+ "svelte-check": "^3.4.3",
 
38
  "ts-node": "^10.9.1",
39
  "tslib": "^2.4.1",
40
  "typescript": "^5.0.0",
41
  "unplugin-icons": "^0.16.1",
42
+ "vite": "^4.3.9",
 
43
  "vitest": "^0.31.0"
44
  },
45
  "type": "module",
46
  "dependencies": {
47
  "@huggingface/hub": "^0.5.1",
48
  "@huggingface/inference": "^2.6.3",
49
+ "@xenova/transformers": "^2.6.0",
 
 
50
  "autoprefixer": "^10.4.14",
51
  "browser-image-resizer": "^2.4.1",
52
  "date-fns": "^2.29.3",
53
  "dotenv": "^16.0.3",
 
54
  "handlebars": "^4.7.8",
55
  "highlight.js": "^11.7.0",
56
  "image-size": "^1.0.2",
 
57
  "jsdom": "^22.0.0",
58
+ "marked": "^4.3.0",
 
 
59
  "mongodb": "^5.8.0",
60
  "nanoid": "^4.0.2",
61
  "openid-client": "^5.4.2",
62
  "parquetjs": "^0.11.2",
 
 
63
  "postcss": "^8.4.31",
 
 
 
64
  "serpapi": "^1.1.1",
 
65
  "tailwind-scrollbar": "^3.0.0",
66
+ "tailwindcss": "^3.3.1",
 
67
  "zod": "^3.22.3"
68
  },
69
  "optionalDependencies": {
 
 
70
  "aws4fetch": "^1.0.17",
 
71
  "openai": "^4.14.2"
72
  }
73
  }
scripts/populate.ts DELETED
@@ -1,269 +0,0 @@
1
- import readline from "readline";
2
- import minimist from "minimist";
3
-
4
- // @ts-expect-error: vite-node makes the var available but the typescript compiler doesn't see them
5
- import { env } from "$env/dynamic/private";
6
-
7
- import { faker } from "@faker-js/faker";
8
- import { ObjectId } from "mongodb";
9
-
10
- // @ts-expect-error: vite-node makes the var available but the typescript compiler doesn't see them
11
- import { collections } from "$lib/server/database";
12
- import { models } from "../src/lib/server/models.ts";
13
- import type { User } from "../src/lib/types/User";
14
- import type { Assistant } from "../src/lib/types/Assistant";
15
- import type { Conversation } from "../src/lib/types/Conversation";
16
- import type { Settings } from "../src/lib/types/Settings";
17
- import { defaultEmbeddingModel } from "../src/lib/server/embeddingModels.ts";
18
- import { Message } from "../src/lib/types/Message.ts";
19
-
20
- import { addChildren } from "../src/lib/utils/tree/addChildren.ts";
21
- import { generateSearchTokens } from "../src/lib/utils/searchTokens.ts";
22
-
23
- const rl = readline.createInterface({
24
- input: process.stdin,
25
- output: process.stdout,
26
- });
27
-
28
- rl.on("close", function () {
29
- process.exit(0);
30
- });
31
-
32
- const possibleFlags = ["reset", "all", "users", "settings", "assistants", "conversations"];
33
- const argv = minimist(process.argv.slice(2));
34
- const flags = argv["_"].filter((flag) => possibleFlags.includes(flag));
35
-
36
- async function generateMessages(preprompt?: string): Promise<Message[]> {
37
- const isLinear = faker.datatype.boolean(0.5);
38
- const isInterrupted = faker.datatype.boolean(0.05);
39
-
40
- const messages: Message[] = [];
41
-
42
- messages.push({
43
- id: crypto.randomUUID(),
44
- from: "system",
45
- content: preprompt ?? "",
46
- createdAt: faker.date.recent({ days: 30 }),
47
- updatedAt: faker.date.recent({ days: 30 }),
48
- });
49
-
50
- let isUser = true;
51
- let lastId = messages[0].id;
52
- if (isLinear) {
53
- const convLength = faker.number.int({ min: 1, max: 25 }) * 2; // must always be even
54
-
55
- for (let i = 0; i < convLength; i++) {
56
- lastId = addChildren(
57
- {
58
- messages,
59
- rootMessageId: messages[0].id,
60
- },
61
- {
62
- from: isUser ? "user" : "assistant",
63
- content: faker.lorem.sentence({
64
- min: 10,
65
- max: isUser ? 50 : 200,
66
- }),
67
- createdAt: faker.date.recent({ days: 30 }),
68
- updatedAt: faker.date.recent({ days: 30 }),
69
- interrupted: i === convLength - 1 && isInterrupted,
70
- },
71
- lastId
72
- );
73
- isUser = !isUser;
74
- }
75
- } else {
76
- const convLength = faker.number.int({ min: 2, max: 200 });
77
-
78
- for (let i = 0; i < convLength; i++) {
79
- addChildren(
80
- {
81
- messages,
82
- rootMessageId: messages[0].id,
83
- },
84
- {
85
- from: isUser ? "user" : "assistant",
86
- content: faker.lorem.sentence({
87
- min: 10,
88
- max: isUser ? 50 : 200,
89
- }),
90
- createdAt: faker.date.recent({ days: 30 }),
91
- updatedAt: faker.date.recent({ days: 30 }),
92
- interrupted: i === convLength - 1 && isInterrupted,
93
- },
94
- faker.helpers.arrayElement([
95
- messages[0].id,
96
- ...messages.filter((m) => m.from === (isUser ? "assistant" : "user")).map((m) => m.id),
97
- ])
98
- );
99
-
100
- isUser = !isUser;
101
- }
102
- }
103
- return messages;
104
- }
105
-
106
- async function seed() {
107
- console.log("Seeding...");
108
- const modelIds = models.map((model) => model.id);
109
-
110
- if (flags.includes("reset")) {
111
- console.log("Starting reset of DB");
112
- await collections.users.deleteMany({});
113
- await collections.settings.deleteMany({});
114
- await collections.assistants.deleteMany({});
115
- await collections.conversations.deleteMany({});
116
- console.log("Reset done");
117
- }
118
-
119
- if (flags.includes("users") || flags.includes("all")) {
120
- console.log("Creating 100 new users");
121
- const newUsers: User[] = Array.from({ length: 100 }, () => ({
122
- _id: new ObjectId(),
123
- createdAt: faker.date.recent({ days: 30 }),
124
- updatedAt: faker.date.recent({ days: 30 }),
125
- username: faker.internet.userName(),
126
- name: faker.person.fullName(),
127
- hfUserId: faker.string.alphanumeric(24),
128
- avatarUrl: faker.image.avatar(),
129
- }));
130
-
131
- await collections.users.insertMany(newUsers);
132
- console.log("Done creating users.");
133
- }
134
-
135
- const users = await collections.users.find().toArray();
136
- if (flags.includes("settings") || flags.includes("all")) {
137
- console.log("Updating settings for all users");
138
- users.forEach(async (user) => {
139
- const settings: Settings = {
140
- userId: user._id,
141
- shareConversationsWithModelAuthors: faker.datatype.boolean(0.25),
142
- hideEmojiOnSidebar: faker.datatype.boolean(0.25),
143
- ethicsModalAcceptedAt: faker.date.recent({ days: 30 }),
144
- activeModel: faker.helpers.arrayElement(modelIds),
145
- createdAt: faker.date.recent({ days: 30 }),
146
- updatedAt: faker.date.recent({ days: 30 }),
147
- customPrompts: {},
148
- assistants: [],
149
- };
150
- await collections.settings.updateOne(
151
- { userId: user._id },
152
- { $set: { ...settings } },
153
- { upsert: true }
154
- );
155
- });
156
- console.log("Done updating settings.");
157
- }
158
-
159
- if (flags.includes("assistants") || flags.includes("all")) {
160
- console.log("Creating assistants for all users");
161
- await Promise.all(
162
- users.map(async (user) => {
163
- const name = faker.animal.insect();
164
- const assistants = faker.helpers.multiple<Assistant>(
165
- () => ({
166
- _id: new ObjectId(),
167
- name,
168
- createdById: user._id,
169
- createdByName: user.username,
170
- createdAt: faker.date.recent({ days: 30 }),
171
- updatedAt: faker.date.recent({ days: 30 }),
172
- userCount: faker.number.int({ min: 1, max: 100000 }),
173
- featured: faker.datatype.boolean(0.25),
174
- modelId: faker.helpers.arrayElement(modelIds),
175
- description: faker.lorem.sentence(),
176
- preprompt: faker.hacker.phrase(),
177
- exampleInputs: faker.helpers.multiple(() => faker.lorem.sentence(), {
178
- count: faker.number.int({ min: 0, max: 4 }),
179
- }),
180
- searchTokens: generateSearchTokens(name),
181
- last24HoursCount: faker.number.int({ min: 0, max: 1000 }),
182
- }),
183
- { count: faker.number.int({ min: 3, max: 10 }) }
184
- );
185
- await collections.assistants.insertMany(assistants);
186
- await collections.settings.updateOne(
187
- { userId: user._id },
188
- { $set: { assistants: assistants.map((a) => a._id.toString()) } },
189
- { upsert: true }
190
- );
191
- })
192
- );
193
- console.log("Done creating assistants.");
194
- }
195
-
196
- if (flags.includes("conversations") || flags.includes("all")) {
197
- console.log("Creating conversations for all users");
198
- await Promise.all(
199
- users.map(async (user) => {
200
- const conversations = faker.helpers.multiple(
201
- async () => {
202
- const settings = await collections.settings.findOne<Settings>({ userId: user._id });
203
-
204
- const assistantId =
205
- settings?.assistants && settings.assistants.length > 0 && faker.datatype.boolean(0.1)
206
- ? faker.helpers.arrayElement<ObjectId>(settings.assistants)
207
- : undefined;
208
-
209
- const preprompt =
210
- (assistantId
211
- ? await collections.assistants
212
- .findOne({ _id: assistantId })
213
- .then((assistant: Assistant) => assistant?.preprompt ?? "")
214
- : faker.helpers.maybe(() => faker.hacker.phrase(), { probability: 0.5 })) ?? "";
215
-
216
- const messages = await generateMessages(preprompt);
217
-
218
- const conv = {
219
- _id: new ObjectId(),
220
- userId: user._id,
221
- assistantId,
222
- preprompt,
223
- createdAt: faker.date.recent({ days: 145 }),
224
- updatedAt: faker.date.recent({ days: 145 }),
225
- model: faker.helpers.arrayElement(modelIds),
226
- title: faker.internet.emoji() + " " + faker.hacker.phrase(),
227
- embeddingModel: defaultEmbeddingModel.id,
228
- messages,
229
- rootMessageId: messages[0].id,
230
- } satisfies Conversation;
231
-
232
- return conv;
233
- },
234
- { count: faker.number.int({ min: 10, max: 200 }) }
235
- );
236
-
237
- await collections.conversations.insertMany(await Promise.all(conversations));
238
- })
239
- );
240
- console.log("Done creating conversations.");
241
- }
242
- }
243
-
244
- // run seed
245
- (async () => {
246
- try {
247
- rl.question(
248
- "You're about to run a seeding script on the following MONGODB_URL: \x1b[31m" +
249
- env.MONGODB_URL +
250
- "\x1b[0m\n\n With the following flags: \x1b[31m" +
251
- flags.join("\x1b[0m , \x1b[31m") +
252
- "\x1b[0m\n \n\n Are you sure you want to continue? (yes/no): ",
253
- async (confirm) => {
254
- if (confirm !== "yes") {
255
- console.log("Not 'yes', exiting.");
256
- rl.close();
257
- process.exit(0);
258
- }
259
- console.log("Starting seeding...");
260
- await seed();
261
- console.log("Seeding done.");
262
- rl.close();
263
- }
264
- );
265
- } catch (e) {
266
- console.error(e);
267
- process.exit(1);
268
- }
269
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/updateProdEnv.ts CHANGED
@@ -1,15 +1,11 @@
1
  import fs from "fs";
2
 
3
- const HF_DEPLOYMENT_TOKEN = process.env.HF_DEPLOYMENT_TOKEN; // token used for pushing to hub
4
 
5
  const SERPER_API_KEY = process.env.SERPER_API_KEY;
6
  const OPENID_CONFIG = process.env.OPENID_CONFIG;
7
  const MONGODB_URL = process.env.MONGODB_URL;
8
- const HF_TOKEN = process.env.HF_TOKEN ?? process.env.HF_ACCESS_TOKEN; // token used for API requests in prod
9
- const WEBHOOK_URL_REPORT_ASSISTANT = process.env.WEBHOOK_URL_REPORT_ASSISTANT; // slack webhook url used to get "report assistant" events
10
- const ADMIN_API_SECRET = process.env.ADMIN_API_SECRET;
11
- const USAGE_LIMITS = process.env.USAGE_LIMITS;
12
- const MESSAGES_BEFORE_LOGIN = process.env.MESSAGES_BEFORE_LOGIN;
13
 
14
  // Read the content of the file .env.template
15
  const PUBLIC_CONFIG = fs.readFileSync(".env.template", "utf8");
@@ -19,11 +15,7 @@ const full_config = `${PUBLIC_CONFIG}
19
  MONGODB_URL=${MONGODB_URL}
20
  OPENID_CONFIG=${OPENID_CONFIG}
21
  SERPER_API_KEY=${SERPER_API_KEY}
22
- HF_TOKEN=${HF_TOKEN}
23
- WEBHOOK_URL_REPORT_ASSISTANT=${WEBHOOK_URL_REPORT_ASSISTANT}
24
- ADMIN_API_SECRET=${ADMIN_API_SECRET}
25
- USAGE_LIMITS=${USAGE_LIMITS}
26
- MESSAGES_BEFORE_LOGIN=${MESSAGES_BEFORE_LOGIN}
27
  `;
28
 
29
  // Make an HTTP POST request to add the space secrets
@@ -35,7 +27,7 @@ fetch(`https://huggingface.co/api/spaces/huggingchat/chat-ui/secrets`, {
35
  description: `Env variable for HuggingChat. Last updated ${new Date().toISOString()}`,
36
  }),
37
  headers: {
38
- Authorization: `Bearer ${HF_DEPLOYMENT_TOKEN}`,
39
  "Content-Type": "application/json",
40
  },
41
  });
 
1
  import fs from "fs";
2
 
3
+ const HF_TOKEN = process.env.HF_TOKEN; // token used for pushing to hub
4
 
5
  const SERPER_API_KEY = process.env.SERPER_API_KEY;
6
  const OPENID_CONFIG = process.env.OPENID_CONFIG;
7
  const MONGODB_URL = process.env.MONGODB_URL;
8
+ const HF_ACCESS_TOKEN = process.env.HF_ACCESS_TOKEN; // token used for API requests in prod
 
 
 
 
9
 
10
  // Read the content of the file .env.template
11
  const PUBLIC_CONFIG = fs.readFileSync(".env.template", "utf8");
 
15
  MONGODB_URL=${MONGODB_URL}
16
  OPENID_CONFIG=${OPENID_CONFIG}
17
  SERPER_API_KEY=${SERPER_API_KEY}
18
+ HF_ACCESS_TOKEN=${HF_ACCESS_TOKEN}
 
 
 
 
19
  `;
20
 
21
  // Make an HTTP POST request to add the space secrets
 
27
  description: `Env variable for HuggingChat. Last updated ${new Date().toISOString()}`,
28
  }),
29
  headers: {
30
+ Authorization: `Bearer ${HF_TOKEN}`,
31
  "Content-Type": "application/json",
32
  },
33
  });
src/ambient.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare module "*.ttf" {
2
- const value: ArrayBuffer;
3
- export default value;
4
- }
 
 
 
 
 
src/app.d.ts CHANGED
@@ -12,11 +12,6 @@ declare global {
12
  sessionId: string;
13
  user?: User;
14
  }
15
-
16
- interface Error {
17
- message: string;
18
- errorId?: ReturnType<typeof crypto.randomUUID>;
19
- }
20
  // interface PageData {}
21
  // interface Platform {}
22
  }
 
12
  sessionId: string;
13
  user?: User;
14
  }
 
 
 
 
 
15
  // interface PageData {}
16
  // interface Platform {}
17
  }
src/hooks.server.ts CHANGED
@@ -1,73 +1,24 @@
1
- import { env } from "$env/dynamic/private";
2
- import { env as envPublic } from "$env/dynamic/public";
3
- import type { Handle, HandleServerError } from "@sveltejs/kit";
 
 
 
 
4
  import { collections } from "$lib/server/database";
5
  import { base } from "$app/paths";
6
- import { findUser, refreshSessionCookie, requiresUser } from "$lib/server/auth";
7
  import { ERROR_MESSAGES } from "$lib/stores/errors";
8
- import { sha256 } from "$lib/utils/sha256";
9
- import { addWeeks } from "date-fns";
10
- import { checkAndRunMigrations } from "$lib/migrations/migrations";
11
- import { building } from "$app/environment";
12
- import { refreshAssistantsCounts } from "$lib/assistantStats/refresh-assistants-counts";
13
- import { logger } from "$lib/server/logger";
14
- import { AbortedGenerations } from "$lib/server/abortedGenerations";
15
- import { MetricsServer } from "$lib/server/metrics";
16
-
17
- // TODO: move this code on a started server hook, instead of using a "building" flag
18
- if (!building) {
19
- await checkAndRunMigrations();
20
- if (env.ENABLE_ASSISTANTS) {
21
- refreshAssistantsCounts();
22
- }
23
-
24
- // Init metrics server
25
- MetricsServer.getInstance();
26
-
27
- // Init AbortedGenerations refresh process
28
- AbortedGenerations.getInstance();
29
- }
30
-
31
- export const handleError: HandleServerError = async ({ error, event }) => {
32
- // handle 404
33
-
34
- if (building) {
35
- throw error;
36
- }
37
-
38
- if (event.route.id === null) {
39
- return {
40
- message: `Page ${event.url.pathname} not found`,
41
- };
42
- }
43
 
44
- const errorId = crypto.randomUUID();
45
-
46
- logger.error({
47
- locals: event.locals,
48
- url: event.request.url,
49
- params: event.params,
50
- request: event.request,
51
- error,
52
- errorId,
53
- });
54
 
55
- return {
56
- message: "An error occurred",
57
- errorId,
58
- };
59
- };
60
 
61
- export const handle: Handle = async ({ event, resolve }) => {
62
- logger.debug({
63
- locals: event.locals,
64
- url: event.url.pathname,
65
- params: event.params,
66
- request: event.request,
67
- });
68
 
69
- if (event.url.pathname.startsWith(`${base}/api/`) && env.EXPOSE_API !== "true") {
70
- return new Response("API is disabled", { status: 403 });
71
  }
72
 
73
  function errorResponse(status: number, message: string) {
@@ -82,44 +33,6 @@ export const handle: Handle = async ({ event, resolve }) => {
82
  });
83
  }
84
 
85
- if (event.url.pathname.startsWith(`${base}/admin/`) || event.url.pathname === `${base}/admin`) {
86
- const ADMIN_SECRET = env.ADMIN_API_SECRET || env.PARQUET_EXPORT_SECRET;
87
-
88
- if (!ADMIN_SECRET) {
89
- return errorResponse(500, "Admin API is not configured");
90
- }
91
-
92
- if (event.request.headers.get("Authorization") !== `Bearer ${ADMIN_SECRET}`) {
93
- return errorResponse(401, "Unauthorized");
94
- }
95
- }
96
-
97
- const token = event.cookies.get(env.COOKIE_NAME);
98
-
99
- let secretSessionId: string;
100
- let sessionId: string;
101
-
102
- if (token) {
103
- secretSessionId = token;
104
- sessionId = await sha256(token);
105
-
106
- const user = await findUser(sessionId);
107
-
108
- if (user) {
109
- event.locals.user = user;
110
- }
111
- } else {
112
- // if the user doesn't have any cookie, we generate one for him
113
- secretSessionId = crypto.randomUUID();
114
- sessionId = await sha256(secretSessionId);
115
-
116
- if (await collections.sessions.findOne({ sessionId })) {
117
- return errorResponse(500, "Session ID collision");
118
- }
119
- }
120
-
121
- event.locals.sessionId = sessionId;
122
-
123
  // CSRF protection
124
  const requestContentType = event.request.headers.get("content-type")?.split(";")[0] ?? "";
125
  /** https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype */
@@ -128,36 +41,21 @@ export const handle: Handle = async ({ event, resolve }) => {
128
  "application/x-www-form-urlencoded",
129
  "text/plain",
130
  ];
 
 
131
 
132
- if (event.request.method === "POST") {
133
- refreshSessionCookie(event.cookies, event.locals.sessionId);
134
-
135
- if (nativeFormContentTypes.includes(requestContentType)) {
136
- const origin = event.request.headers.get("origin");
137
-
138
- if (!origin) {
139
- return errorResponse(403, "Non-JSON form requests need to have an origin");
140
- }
141
-
142
- const validOrigins = [
143
- new URL(event.request.url).host,
144
- ...(envPublic.PUBLIC_ORIGIN ? [new URL(envPublic.PUBLIC_ORIGIN).host] : []),
145
- ];
146
-
147
- if (!validOrigins.includes(new URL(origin).host)) {
148
- return errorResponse(403, "Invalid referer for POST request");
149
- }
150
  }
151
- }
152
 
153
- if (event.request.method === "POST") {
154
- // if the request is a POST request we refresh the cookie
155
- refreshSessionCookie(event.cookies, secretSessionId);
 
156
 
157
- await collections.sessions.updateOne(
158
- { sessionId },
159
- { $set: { updatedAt: new Date(), expiresAt: addWeeks(new Date(), 2) } }
160
- );
161
  }
162
 
163
  if (
@@ -166,9 +64,9 @@ export const handle: Handle = async ({ event, resolve }) => {
166
  !["GET", "OPTIONS", "HEAD"].includes(event.request.method)
167
  ) {
168
  if (
169
- !event.locals.user &&
170
  requiresUser &&
171
- !((env.MESSAGES_BEFORE_LOGIN ? parseInt(env.MESSAGES_BEFORE_LOGIN) : 0) > 0)
172
  ) {
173
  return errorResponse(401, ERROR_MESSAGES.authOnly);
174
  }
@@ -179,7 +77,7 @@ export const handle: Handle = async ({ event, resolve }) => {
179
  if (
180
  !requiresUser &&
181
  !event.url.pathname.startsWith(`${base}/settings`) &&
182
- !!envPublic.PUBLIC_APP_DISCLAIMER
183
  ) {
184
  const hasAcceptedEthicsModal = await collections.settings.countDocuments({
185
  sessionId: event.locals.sessionId,
@@ -192,6 +90,8 @@ export const handle: Handle = async ({ event, resolve }) => {
192
  }
193
  }
194
 
 
 
195
  let replaced = false;
196
 
197
  const response = await resolve(event, {
@@ -202,7 +102,7 @@ export const handle: Handle = async ({ event, resolve }) => {
202
  }
203
  replaced = true;
204
 
205
- return chunk.html.replace("%gaId%", envPublic.PUBLIC_GOOGLE_ANALYTICS_ID);
206
  },
207
  });
208
 
 
1
+ import { COOKIE_NAME, MESSAGES_BEFORE_LOGIN } from "$env/static/private";
2
+ import type { Handle } from "@sveltejs/kit";
3
+ import {
4
+ PUBLIC_GOOGLE_ANALYTICS_ID,
5
+ PUBLIC_ORIGIN,
6
+ PUBLIC_APP_DISCLAIMER,
7
+ } from "$env/static/public";
8
  import { collections } from "$lib/server/database";
9
  import { base } from "$app/paths";
10
+ import { refreshSessionCookie, requiresUser } from "$lib/server/auth";
11
  import { ERROR_MESSAGES } from "$lib/stores/errors";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ export const handle: Handle = async ({ event, resolve }) => {
14
+ const token = event.cookies.get(COOKIE_NAME);
 
 
 
 
 
 
 
 
15
 
16
+ event.locals.sessionId = token || crypto.randomUUID();
 
 
 
 
17
 
18
+ const user = await collections.users.findOne({ sessionId: event.locals.sessionId });
 
 
 
 
 
 
19
 
20
+ if (user) {
21
+ event.locals.user = user;
22
  }
23
 
24
  function errorResponse(status: number, message: string) {
 
33
  });
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  // CSRF protection
37
  const requestContentType = event.request.headers.get("content-type")?.split(";")[0] ?? "";
38
  /** https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype */
 
41
  "application/x-www-form-urlencoded",
42
  "text/plain",
43
  ];
44
+ if (event.request.method === "POST" && nativeFormContentTypes.includes(requestContentType)) {
45
+ const referer = event.request.headers.get("referer");
46
 
47
+ if (!referer) {
48
+ return errorResponse(403, "Non-JSON form requests need to have a referer");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
50
 
51
+ const validOrigins = [
52
+ new URL(event.request.url).origin,
53
+ ...(PUBLIC_ORIGIN ? [new URL(PUBLIC_ORIGIN).origin] : []),
54
+ ];
55
 
56
+ if (!validOrigins.includes(new URL(referer).origin)) {
57
+ return errorResponse(403, "Invalid referer for POST request");
58
+ }
 
59
  }
60
 
61
  if (
 
64
  !["GET", "OPTIONS", "HEAD"].includes(event.request.method)
65
  ) {
66
  if (
67
+ !user &&
68
  requiresUser &&
69
+ !((MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0)
70
  ) {
71
  return errorResponse(401, ERROR_MESSAGES.authOnly);
72
  }
 
77
  if (
78
  !requiresUser &&
79
  !event.url.pathname.startsWith(`${base}/settings`) &&
80
+ !!PUBLIC_APP_DISCLAIMER
81
  ) {
82
  const hasAcceptedEthicsModal = await collections.settings.countDocuments({
83
  sessionId: event.locals.sessionId,
 
90
  }
91
  }
92
 
93
+ refreshSessionCookie(event.cookies, event.locals.sessionId);
94
+
95
  let replaced = false;
96
 
97
  const response = await resolve(event, {
 
102
  }
103
  replaced = true;
104
 
105
+ return chunk.html.replace("%gaId%", PUBLIC_GOOGLE_ANALYTICS_ID);
106
  },
107
  });
108
 
src/lib/actions/clickOutside.ts DELETED
@@ -1,18 +0,0 @@
1
- export function clickOutside(element: HTMLDialogElement, callbackFunction: () => void) {
2
- function onClick(event: MouseEvent) {
3
- if (!element.contains(event.target as Node)) {
4
- callbackFunction();
5
- }
6
- }
7
-
8
- document.body.addEventListener("click", onClick);
9
-
10
- return {
11
- update(newCallbackFunction: () => void) {
12
- callbackFunction = newCallbackFunction;
13
- },
14
- destroy() {
15
- document.body.removeEventListener("click", onClick);
16
- },
17
- };
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/assistantStats/refresh-assistants-counts.ts DELETED
@@ -1,90 +0,0 @@
1
- import { Database } from "$lib/server/database";
2
- import { acquireLock, refreshLock } from "$lib/migrations/lock";
3
- import type { ObjectId } from "mongodb";
4
- import { subDays } from "date-fns";
5
- import { logger } from "$lib/server/logger";
6
-
7
- const LOCK_KEY = "assistants.count";
8
-
9
- let hasLock = false;
10
- let lockId: ObjectId | null = null;
11
-
12
- async function refreshAssistantsCountsHelper() {
13
- if (!hasLock) {
14
- return;
15
- }
16
-
17
- try {
18
- await Database.getInstance()
19
- .getClient()
20
- .withSession((session) =>
21
- session.withTransaction(async () => {
22
- await Database.getInstance()
23
- .getCollections()
24
- .assistants.aggregate([
25
- { $project: { _id: 1 } },
26
- { $set: { last24HoursCount: 0 } },
27
- {
28
- $unionWith: {
29
- coll: "assistants.stats",
30
- pipeline: [
31
- {
32
- $match: { "date.at": { $gte: subDays(new Date(), 1) }, "date.span": "hour" },
33
- },
34
- {
35
- $group: {
36
- _id: "$assistantId",
37
- last24HoursCount: { $sum: "$count" },
38
- },
39
- },
40
- ],
41
- },
42
- },
43
- {
44
- $group: {
45
- _id: "$_id",
46
- last24HoursCount: { $sum: "$last24HoursCount" },
47
- },
48
- },
49
- {
50
- $merge: {
51
- into: "assistants",
52
- on: "_id",
53
- whenMatched: "merge",
54
- whenNotMatched: "discard",
55
- },
56
- },
57
- ])
58
- .next();
59
- })
60
- );
61
- } catch (e) {
62
- logger.error("Refresh assistants counter failed!");
63
- logger.error(e);
64
- }
65
- }
66
-
67
- async function maintainLock() {
68
- if (hasLock && lockId) {
69
- hasLock = await refreshLock(LOCK_KEY, lockId);
70
-
71
- if (!hasLock) {
72
- lockId = null;
73
- }
74
- } else if (!hasLock) {
75
- lockId = (await acquireLock(LOCK_KEY)) || null;
76
- hasLock = !!lockId;
77
- }
78
-
79
- setTimeout(maintainLock, 10_000);
80
- }
81
-
82
- export function refreshAssistantsCounts() {
83
- const ONE_HOUR_MS = 3_600_000;
84
-
85
- maintainLock().then(() => {
86
- refreshAssistantsCountsHelper();
87
-
88
- setInterval(refreshAssistantsCountsHelper, ONE_HOUR_MS);
89
- });
90
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/buildPrompt.ts CHANGED
@@ -1,33 +1,92 @@
1
- import type { EndpointParameters } from "./server/endpoints/endpoints";
2
  import type { BackendModel } from "./server/models";
 
 
 
 
 
3
 
4
- type buildPromptOptions = Pick<EndpointParameters, "messages" | "preprompt" | "continueMessage"> & {
 
 
5
  model: BackendModel;
6
- };
 
 
 
 
7
 
8
  export async function buildPrompt({
9
  messages,
10
  model,
 
11
  preprompt,
12
- continueMessage,
13
  }: buildPromptOptions): Promise<string> {
14
- const filteredMessages = messages.filter((m) => m.from !== "system");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- let prompt = model
17
- .chatPromptRender({ messages: filteredMessages, preprompt })
18
- // Not super precise, but it's truncated in the model's backend anyway
19
- .split(" ")
20
- .slice(-(model.parameters?.truncate ?? 0))
21
- .join(" ");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- if (continueMessage && model.parameters?.stop) {
24
- prompt = model.parameters.stop.reduce((acc: string, curr: string) => {
25
- if (acc.endsWith(curr)) {
26
- return acc.slice(0, acc.length - curr.length);
27
- }
28
- return acc;
29
- }, prompt.trimEnd());
30
  }
31
 
32
- return prompt;
 
 
 
 
 
 
 
33
  }
 
 
1
  import type { BackendModel } from "./server/models";
2
+ import type { Message } from "./types/Message";
3
+ import { format } from "date-fns";
4
+ import type { WebSearch } from "./types/WebSearch";
5
+ import { downloadFile } from "./server/files/downloadFile";
6
+ import type { Conversation } from "./types/Conversation";
7
 
8
+ interface buildPromptOptions {
9
+ messages: Pick<Message, "from" | "content" | "files">[];
10
+ id?: Conversation["_id"];
11
  model: BackendModel;
12
+ locals?: App.Locals;
13
+ webSearch?: WebSearch;
14
+ preprompt?: string;
15
+ files?: File[];
16
+ }
17
 
18
  export async function buildPrompt({
19
  messages,
20
  model,
21
+ webSearch,
22
  preprompt,
23
+ id,
24
  }: buildPromptOptions): Promise<string> {
25
+ if (webSearch && webSearch.context) {
26
+ const lastMsg = messages.slice(-1)[0];
27
+ const messagesWithoutLastUsrMsg = messages.slice(0, -1);
28
+ const previousUserMessages = messages.filter((el) => el.from === "user").slice(0, -1);
29
+
30
+ const previousQuestions =
31
+ previousUserMessages.length > 0
32
+ ? `Previous questions: \n${previousUserMessages
33
+ .map(({ content }) => `- ${content}`)
34
+ .join("\n")}`
35
+ : "";
36
+ const currentDate = format(new Date(), "MMMM d, yyyy");
37
+ messages = [
38
+ ...messagesWithoutLastUsrMsg,
39
+ {
40
+ from: "user",
41
+ content: `I searched the web using the query: ${webSearch.searchQuery}. Today is ${currentDate} and here are the results:
42
+ =====================
43
+ ${webSearch.context}
44
+ =====================
45
+ ${previousQuestions}
46
+ Answer the question: ${lastMsg.content}
47
+ `,
48
+ },
49
+ ];
50
+ }
51
+
52
+ // section to handle potential files input
53
+ if (model.multimodal) {
54
+ messages = await Promise.all(
55
+ messages.map(async (el) => {
56
+ let content = el.content;
57
 
58
+ if (el.from === "user") {
59
+ if (el?.files && el.files.length > 0 && id) {
60
+ const markdowns = await Promise.all(
61
+ el.files.map(async (hash) => {
62
+ try {
63
+ const { content: image, mime } = await downloadFile(hash, id);
64
+ const b64 = image.toString("base64");
65
+ return `![](data:${mime};base64,${b64})})`;
66
+ } catch (e) {
67
+ console.error(e);
68
+ }
69
+ })
70
+ );
71
+ content += markdowns.join("\n ");
72
+ } else {
73
+ // if no image, append an empty white image
74
+ content +=
75
+ "\n![](data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAAQABADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/igAoAKACgD/2Q==)";
76
+ }
77
+ }
78
 
79
+ return { ...el, content };
80
+ })
81
+ );
 
 
 
 
82
  }
83
 
84
+ return (
85
+ model
86
+ .chatPromptRender({ messages, preprompt })
87
+ // Not super precise, but it's truncated in the model's backend anyway
88
+ .split(" ")
89
+ .slice(-(model.parameters?.truncate ?? 0))
90
+ .join(" ")
91
+ );
92
  }
src/lib/components/AnnouncementBanner.svelte CHANGED
@@ -5,7 +5,7 @@
5
 
6
  <div class="flex items-center rounded-xl bg-gray-100 p-1 text-sm dark:bg-gray-800 {classNames}">
7
  <span
8
- class="from-primary-300 text-primary-700 dark:from-primary-900 dark:text-primary-400 mr-2 inline-flex items-center rounded-lg bg-gradient-to-br px-2 py-1 text-xxs font-medium uppercase leading-3"
9
  >New</span
10
  >
11
  {title}
 
5
 
6
  <div class="flex items-center rounded-xl bg-gray-100 p-1 text-sm dark:bg-gray-800 {classNames}">
7
  <span
8
+ class="mr-2 inline-flex items-center rounded-lg bg-gradient-to-br from-primary-300 px-2 py-1 text-xxs font-medium uppercase leading-3 text-primary-700 dark:from-primary-900 dark:text-primary-400"
9
  >New</span
10
  >
11
  {title}
src/lib/components/AssistantSettings.svelte DELETED
@@ -1,593 +0,0 @@
1
- <script lang="ts">
2
- import type { readAndCompressImage } from "browser-image-resizer";
3
- import type { Model } from "$lib/types/Model";
4
- import type { Assistant } from "$lib/types/Assistant";
5
-
6
- import { onMount } from "svelte";
7
- import { applyAction, enhance } from "$app/forms";
8
- import { page } from "$app/stores";
9
- import { base } from "$app/paths";
10
- import CarbonPen from "~icons/carbon/pen";
11
- import CarbonUpload from "~icons/carbon/upload";
12
- import CarbonHelpFilled from "~icons/carbon/help";
13
- import CarbonSettingsAdjust from "~icons/carbon/settings-adjust";
14
-
15
- import { useSettingsStore } from "$lib/stores/settings";
16
- import { isHuggingChat } from "$lib/utils/isHuggingChat";
17
- import IconInternet from "./icons/IconInternet.svelte";
18
- import TokensCounter from "./TokensCounter.svelte";
19
- import HoverTooltip from "./HoverTooltip.svelte";
20
- import { findCurrentModel } from "$lib/utils/models";
21
-
22
- type ActionData = {
23
- error: boolean;
24
- errors: {
25
- field: string | number;
26
- message: string;
27
- }[];
28
- } | null;
29
-
30
- type AssistantFront = Omit<Assistant, "_id" | "createdById"> & { _id: string };
31
-
32
- export let form: ActionData;
33
- export let assistant: AssistantFront | undefined = undefined;
34
- export let models: Model[] = [];
35
-
36
- let files: FileList | null = null;
37
- const settings = useSettingsStore();
38
- let modelId = "";
39
- let systemPrompt = assistant?.preprompt ?? "";
40
- let dynamicPrompt = assistant?.dynamicPrompt ?? false;
41
- let showModelSettings = Object.values(assistant?.generateSettings ?? {}).some((v) => !!v);
42
-
43
- let compress: typeof readAndCompressImage | null = null;
44
-
45
- onMount(async () => {
46
- const module = await import("browser-image-resizer");
47
- compress = module.readAndCompressImage;
48
-
49
- modelId = findCurrentModel(models, assistant ? assistant.modelId : $settings.activeModel).id;
50
- });
51
-
52
- let inputMessage1 = assistant?.exampleInputs[0] ?? "";
53
- let inputMessage2 = assistant?.exampleInputs[1] ?? "";
54
- let inputMessage3 = assistant?.exampleInputs[2] ?? "";
55
- let inputMessage4 = assistant?.exampleInputs[3] ?? "";
56
-
57
- function resetErrors() {
58
- if (form) {
59
- form.errors = [];
60
- form.error = false;
61
- }
62
- }
63
-
64
- function onFilesChange(e: Event) {
65
- const inputEl = e.target as HTMLInputElement;
66
- if (inputEl.files?.length && inputEl.files[0].size > 0) {
67
- if (!inputEl.files[0].type.includes("image")) {
68
- inputEl.files = null;
69
- files = null;
70
-
71
- form = { error: true, errors: [{ field: "avatar", message: "Only images are allowed" }] };
72
- return;
73
- }
74
- files = inputEl.files;
75
- resetErrors();
76
- deleteExistingAvatar = false;
77
- }
78
- }
79
-
80
- function getError(field: string, returnForm: ActionData) {
81
- return returnForm?.errors.find((error) => error.field === field)?.message ?? "";
82
- }
83
-
84
- let deleteExistingAvatar = false;
85
-
86
- let loading = false;
87
-
88
- let ragMode: false | "links" | "domains" | "all" = assistant?.rag?.allowAllDomains
89
- ? "all"
90
- : assistant?.rag?.allowedLinks?.length ?? 0 > 0
91
- ? "links"
92
- : (assistant?.rag?.allowedDomains?.length ?? 0) > 0
93
- ? "domains"
94
- : false;
95
-
96
- const regex = /{{\s?url=(.+?)\s?}}/g;
97
- $: templateVariables = [...systemPrompt.matchAll(regex)].map((match) => match[1]);
98
- $: selectedModel = models.find((m) => m.id === modelId);
99
- </script>
100
-
101
- <form
102
- method="POST"
103
- class="relative flex h-full flex-col overflow-y-auto p-4 md:p-8"
104
- enctype="multipart/form-data"
105
- use:enhance={async ({ formData }) => {
106
- loading = true;
107
- if (files?.[0] && files[0].size > 0 && compress) {
108
- await compress(files[0], {
109
- maxWidth: 500,
110
- maxHeight: 500,
111
- quality: 1,
112
- }).then((resizedImage) => {
113
- formData.set("avatar", resizedImage);
114
- });
115
- }
116
-
117
- if (deleteExistingAvatar === true) {
118
- if (assistant?.avatar) {
119
- // if there is an avatar we explicitly removei t
120
- formData.set("avatar", "null");
121
- } else {
122
- // else we just remove it from the input
123
- formData.delete("avatar");
124
- }
125
- } else {
126
- if (files === null) {
127
- formData.delete("avatar");
128
- }
129
- }
130
-
131
- formData.delete("ragMode");
132
-
133
- if (ragMode === false || !$page.data.enableAssistantsRAG) {
134
- formData.set("ragAllowAll", "false");
135
- formData.set("ragLinkList", "");
136
- formData.set("ragDomainList", "");
137
- } else if (ragMode === "all") {
138
- formData.set("ragAllowAll", "true");
139
- formData.set("ragLinkList", "");
140
- formData.set("ragDomainList", "");
141
- } else if (ragMode === "links") {
142
- formData.set("ragAllowAll", "false");
143
- formData.set("ragDomainList", "");
144
- } else if (ragMode === "domains") {
145
- formData.set("ragAllowAll", "false");
146
- formData.set("ragLinkList", "");
147
- }
148
-
149
- return async ({ result }) => {
150
- loading = false;
151
- await applyAction(result);
152
- };
153
- }}
154
- >
155
- {#if assistant}
156
- <h2 class="text-xl font-semibold">
157
- Edit Assistant: {assistant?.name ?? "assistant"}
158
- </h2>
159
- <p class="mb-6 text-sm text-gray-500">
160
- Modifying an existing assistant will propagate the changes to all users.
161
- </p>
162
- {:else}
163
- <h2 class="text-xl font-semibold">Create new assistant</h2>
164
- <p class="mb-6 text-sm text-gray-500">
165
- Create and share your own AI Assistant. All assistants are <span
166
- class="rounded-full border px-2 py-0.5 leading-none">public</span
167
- >
168
- </p>
169
- {/if}
170
-
171
- <div class="grid h-full w-full flex-1 grid-cols-2 gap-6 text-sm max-sm:grid-cols-1">
172
- <div class="col-span-1 flex flex-col gap-4">
173
- <div>
174
- <div class="mb-1 block pb-2 text-sm font-semibold">Avatar</div>
175
- <input
176
- type="file"
177
- accept="image/*"
178
- name="avatar"
179
- id="avatar"
180
- class="hidden"
181
- on:change={onFilesChange}
182
- />
183
-
184
- {#if (files && files[0]) || (assistant?.avatar && !deleteExistingAvatar)}
185
- <div class="group relative mx-auto h-12 w-12">
186
- {#if files && files[0]}
187
- <img
188
- src={URL.createObjectURL(files[0])}
189
- alt="avatar"
190
- class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
191
- />
192
- {:else if assistant?.avatar}
193
- <img
194
- src="{base}/settings/assistants/{assistant._id}/avatar.jpg?hash={assistant.avatar}"
195
- alt="avatar"
196
- class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
197
- />
198
- {/if}
199
-
200
- <label
201
- for="avatar"
202
- class="invisible absolute bottom-0 h-12 w-12 rounded-full bg-black bg-opacity-50 p-1 group-hover:visible hover:visible"
203
- >
204
- <CarbonPen class="mx-auto my-auto h-full cursor-pointer text-center text-white" />
205
- </label>
206
- </div>
207
- <div class="mx-auto w-max pt-1">
208
- <button
209
- type="button"
210
- on:click|stopPropagation|preventDefault={() => {
211
- files = null;
212
- deleteExistingAvatar = true;
213
- }}
214
- class="mx-auto w-max text-center text-xs text-gray-600 hover:underline"
215
- >
216
- Delete
217
- </button>
218
- </div>
219
- {:else}
220
- <div class="mb-1 flex w-max flex-row gap-4">
221
- <label
222
- for="avatar"
223
- class="btn flex h-8 rounded-lg border bg-white px-3 py-1 text-gray-500 shadow-sm transition-all hover:bg-gray-100"
224
- >
225
- <CarbonUpload class="mr-2 text-xs " /> Upload
226
- </label>
227
- </div>
228
- {/if}
229
- <p class="text-xs text-red-500">{getError("avatar", form)}</p>
230
- </div>
231
-
232
- <label>
233
- <div class="mb-1 font-semibold">Name</div>
234
- <input
235
- name="name"
236
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
237
- placeholder="Assistant Name"
238
- value={assistant?.name ?? ""}
239
- />
240
- <p class="text-xs text-red-500">{getError("name", form)}</p>
241
- </label>
242
-
243
- <label>
244
- <div class="mb-1 font-semibold">Description</div>
245
- <textarea
246
- name="description"
247
- class="h-15 w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
248
- placeholder="He knows everything about python"
249
- value={assistant?.description ?? ""}
250
- />
251
- <p class="text-xs text-red-500">{getError("description", form)}</p>
252
- </label>
253
-
254
- <label>
255
- <div class="mb-1 font-semibold">Model</div>
256
- <div class="flex gap-2">
257
- <select
258
- name="modelId"
259
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
260
- bind:value={modelId}
261
- >
262
- {#each models.filter((model) => !model.unlisted) as model}
263
- <option value={model.id}>{model.displayName}</option>
264
- {/each}
265
- <p class="text-xs text-red-500">{getError("modelId", form)}</p>
266
- </select>
267
- <button
268
- type="button"
269
- class="flex aspect-square items-center gap-2 whitespace-nowrap rounded-lg border px-3 {showModelSettings
270
- ? 'border-blue-500/20 bg-blue-50 text-blue-600'
271
- : ''}"
272
- on:click={() => (showModelSettings = !showModelSettings)}
273
- ><CarbonSettingsAdjust class="text-xs" /></button
274
- >
275
- </div>
276
- <div
277
- class="mt-2 rounded-lg border border-blue-500/20 bg-blue-500/5 px-2 py-0.5"
278
- class:hidden={!showModelSettings}
279
- >
280
- <p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
281
- <div class="my-2 grid grid-cols-1 gap-2.5 sm:grid-cols-2 sm:grid-rows-2">
282
- <label for="temperature" class="flex justify-between">
283
- <span class="m-1 ml-0 flex items-center gap-1.5 whitespace-nowrap text-sm">
284
- Temperature
285
-
286
- <HoverTooltip
287
- label="Temperature: Controls creativity, higher values allow more variety."
288
- >
289
- <CarbonHelpFilled
290
- class="inline text-xxs text-gray-500 group-hover/tooltip:text-blue-600"
291
- />
292
- </HoverTooltip>
293
- </span>
294
- <input
295
- type="number"
296
- name="temperature"
297
- min="0.1"
298
- max="2"
299
- step="0.1"
300
- class="w-20 rounded-lg border-2 border-gray-200 bg-gray-100 px-2 py-1"
301
- placeholder={selectedModel?.parameters?.temperature?.toString() ?? "1"}
302
- value={assistant?.generateSettings?.temperature ?? ""}
303
- />
304
- </label>
305
- <label for="top_p" class="flex justify-between">
306
- <span class="m-1 ml-0 flex items-center gap-1.5 whitespace-nowrap text-sm">
307
- Top P
308
- <HoverTooltip
309
- label="Top P: Sets word choice boundaries, lower values tighten focus."
310
- >
311
- <CarbonHelpFilled
312
- class="inline text-xxs text-gray-500 group-hover/tooltip:text-blue-600"
313
- />
314
- </HoverTooltip>
315
- </span>
316
-
317
- <input
318
- type="number"
319
- name="top_p"
320
- class="w-20 rounded-lg border-2 border-gray-200 bg-gray-100 px-2 py-1"
321
- min="0.05"
322
- max="1"
323
- step="0.05"
324
- placeholder={selectedModel?.parameters?.top_p?.toString() ?? "1"}
325
- value={assistant?.generateSettings?.top_p ?? ""}
326
- />
327
- </label>
328
- <label for="repetition_penalty" class="flex justify-between">
329
- <span class="m-1 ml-0 flex items-center gap-1.5 whitespace-nowrap text-sm">
330
- Repetition penalty
331
- <HoverTooltip
332
- label="Repetition penalty: Prevents reuse, higher values decrease repetition."
333
- >
334
- <CarbonHelpFilled
335
- class="inline text-xxs text-gray-500 group-hover/tooltip:text-blue-600"
336
- />
337
- </HoverTooltip>
338
- </span>
339
- <input
340
- type="number"
341
- name="repetition_penalty"
342
- min="0.1"
343
- max="2"
344
- class="w-20 rounded-lg border-2 border-gray-200 bg-gray-100 px-2 py-1"
345
- placeholder={selectedModel?.parameters?.repetition_penalty?.toString() ?? "1.0"}
346
- value={assistant?.generateSettings?.repetition_penalty ?? ""}
347
- />
348
- </label>
349
- <label for="top_k" class="flex justify-between">
350
- <span class="m-1 ml-0 flex items-center gap-1.5 whitespace-nowrap text-sm">
351
- Top K <HoverTooltip
352
- label="Top K: Restricts word options, lower values for predictability."
353
- >
354
- <CarbonHelpFilled
355
- class="inline text-xxs text-gray-500 group-hover/tooltip:text-blue-600"
356
- />
357
- </HoverTooltip>
358
- </span>
359
- <input
360
- type="number"
361
- name="top_k"
362
- min="5"
363
- max="100"
364
- step="5"
365
- class="w-20 rounded-lg border-2 border-gray-200 bg-gray-100 px-2 py-1"
366
- placeholder={selectedModel?.parameters?.top_k?.toString() ?? "50"}
367
- value={assistant?.generateSettings?.top_k ?? ""}
368
- />
369
- </label>
370
- </div>
371
- </div>
372
- </label>
373
-
374
- <label>
375
- <div class="mb-1 font-semibold">User start messages</div>
376
- <div class="grid gap-1.5 text-sm md:grid-cols-2">
377
- <input
378
- name="exampleInput1"
379
- placeholder="Start Message 1"
380
- bind:value={inputMessage1}
381
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
382
- />
383
- <input
384
- name="exampleInput2"
385
- placeholder="Start Message 2"
386
- bind:value={inputMessage2}
387
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
388
- />
389
-
390
- <input
391
- name="exampleInput3"
392
- placeholder="Start Message 3"
393
- bind:value={inputMessage3}
394
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
395
- />
396
- <input
397
- name="exampleInput4"
398
- placeholder="Start Message 4"
399
- bind:value={inputMessage4}
400
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
401
- />
402
- </div>
403
- <p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
404
- </label>
405
- {#if $page.data.enableAssistantsRAG}
406
- <div class="mb-4 flex flex-col flex-nowrap">
407
- <span class="mt-2 text-smd font-semibold"
408
- >Internet access
409
- <IconInternet classNames="inline text-sm text-blue-600" />
410
-
411
- <span class="ml-1 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-600"
412
- >Experimental</span
413
- >
414
-
415
- {#if isHuggingChat}
416
- <a
417
- href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions/385"
418
- target="_blank"
419
- class="ml-0.5 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-700 underline decoration-gray-400"
420
- >Give feedback</a
421
- >
422
- {/if}
423
- </span>
424
-
425
- <label class="mt-1">
426
- <input
427
- checked={!ragMode}
428
- on:change={() => (ragMode = false)}
429
- type="radio"
430
- name="ragMode"
431
- value={false}
432
- />
433
- <span class="my-2 text-sm" class:font-semibold={!ragMode}> Default </span>
434
- {#if !ragMode}
435
- <span class="block text-xs text-gray-500">
436
- Assistant will not use internet to do information retrieval and will respond faster.
437
- Recommended for most Assistants.
438
- </span>
439
- {/if}
440
- </label>
441
-
442
- <label class="mt-1">
443
- <input
444
- checked={ragMode === "all"}
445
- on:change={() => (ragMode = "all")}
446
- type="radio"
447
- name="ragMode"
448
- value={"all"}
449
- />
450
- <span class="my-2 text-sm" class:font-semibold={ragMode === "all"}> Web search </span>
451
- {#if ragMode === "all"}
452
- <span class="block text-xs text-gray-500">
453
- Assistant will do a web search on each user request to find information.
454
- </span>
455
- {/if}
456
- </label>
457
-
458
- <label class="mt-1">
459
- <input
460
- checked={ragMode === "domains"}
461
- on:change={() => (ragMode = "domains")}
462
- type="radio"
463
- name="ragMode"
464
- value={false}
465
- />
466
- <span class="my-2 text-sm" class:font-semibold={ragMode === "domains"}>
467
- Domains search
468
- </span>
469
- </label>
470
- {#if ragMode === "domains"}
471
- <span class="mb-2 text-xs text-gray-500">
472
- Specify domains and URLs that the application can search, separated by commas.
473
- </span>
474
-
475
- <input
476
- name="ragDomainList"
477
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
478
- placeholder="wikipedia.org,bbc.com"
479
- value={assistant?.rag?.allowedDomains?.join(",") ?? ""}
480
- />
481
- <p class="text-xs text-red-500">{getError("ragDomainList", form)}</p>
482
- {/if}
483
-
484
- <label class="mt-1">
485
- <input
486
- checked={ragMode === "links"}
487
- on:change={() => (ragMode = "links")}
488
- type="radio"
489
- name="ragMode"
490
- value={false}
491
- />
492
- <span class="my-2 text-sm" class:font-semibold={ragMode === "links"}>
493
- Specific Links
494
- </span>
495
- </label>
496
- {#if ragMode === "links"}
497
- <span class="mb-2 text-xs text-gray-500">
498
- Specify a maximum of 10 direct URLs that the Assistant will access. HTML & Plain Text
499
- only, separated by commas
500
- </span>
501
- <input
502
- name="ragLinkList"
503
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
504
- placeholder="https://raw.githubusercontent.com/huggingface/chat-ui/main/README.md"
505
- value={assistant?.rag?.allowedLinks.join(",") ?? ""}
506
- />
507
- <p class="text-xs text-red-500">{getError("ragLinkList", form)}</p>
508
- {/if}
509
-
510
- <!-- divider -->
511
- <div class="my-3 ml-0 mr-6 w-full border border-gray-200" />
512
-
513
- <label class="text-sm has-[:checked]:font-semibold">
514
- <input type="checkbox" name="dynamicPrompt" bind:checked={dynamicPrompt} />
515
- Dynamic Prompt
516
- <p class="mb-2 text-xs font-normal text-gray-500">
517
- Allow the use of template variables {"{{url=https://example.com/path}}"}
518
- to insert dynamic content into your prompt by making GET requests to specified URLs on
519
- each inference.
520
- </p>
521
- </label>
522
- </div>
523
- {/if}
524
- </div>
525
-
526
- <div class="relative col-span-1 flex h-full flex-col">
527
- <div class="mb-1 flex justify-between text-sm">
528
- <span class="font-semibold"> Instructions (System Prompt) </span>
529
- {#if dynamicPrompt && templateVariables.length}
530
- <div class="relative">
531
- <button
532
- type="button"
533
- class="peer rounded bg-blue-500/20 px-1 text-xs text-blue-600 focus:bg-blue-500/30 focus:text-blue-800 sm:text-sm"
534
- >
535
- {templateVariables.length} template variable{templateVariables.length > 1 ? "s" : ""}
536
- </button>
537
- <div
538
- class="invisible absolute right-0 top-6 z-10 rounded-lg border bg-white p-2 text-xs shadow-lg peer-focus:visible hover:visible sm:w-96"
539
- >
540
- Will performs a GET request and injects the response into the prompt. Works better
541
- with plain text, csv or json content.
542
- {#each templateVariables as match}
543
- <a href={match} target="_blank" class="text-gray-500 underline decoration-gray-300"
544
- >{match}</a
545
- >
546
- {/each}
547
- </div>
548
- </div>
549
- {/if}
550
- </div>
551
- <div class="relative mb-20 flex h-full flex-col gap-2">
552
- <textarea
553
- name="preprompt"
554
- class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
555
- placeholder="You'll act as..."
556
- bind:value={systemPrompt}
557
- />
558
- {#if modelId}
559
- {@const model = models.find((_model) => _model.id === modelId)}
560
- {#if model?.tokenizer && systemPrompt}
561
- <TokensCounter
562
- classNames="absolute bottom-4 right-4"
563
- prompt={systemPrompt}
564
- modelTokenizer={model.tokenizer}
565
- truncate={model?.parameters?.truncate}
566
- />
567
- {/if}
568
- {/if}
569
-
570
- <p class="text-xs text-red-500">{getError("preprompt", form)}</p>
571
- </div>
572
- <div class="absolute bottom-6 flex w-full justify-end gap-2 md:right-0 md:w-fit">
573
- <a
574
- href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
575
- class="flex items-center justify-center rounded-full bg-gray-200 px-5 py-2 font-semibold text-gray-600"
576
- >
577
- Cancel
578
- </a>
579
- <button
580
- type="submit"
581
- disabled={loading}
582
- aria-disabled={loading}
583
- class="flex items-center justify-center rounded-full bg-black px-8 py-2 font-semibold"
584
- class:bg-gray-200={loading}
585
- class:text-gray-600={loading}
586
- class:text-white={!loading}
587
- >
588
- {assistant ? "Save" : "Create"}
589
- </button>
590
- </div>
591
- </div>
592
- </div>
593
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/ContinueBtn.svelte DELETED
@@ -1,13 +0,0 @@
1
- <script lang="ts">
2
- import CarbonContinue from "~icons/carbon/continue";
3
-
4
- export let classNames = "";
5
- </script>
6
-
7
- <button
8
- type="button"
9
- on:click
10
- class="btn flex h-8 rounded-lg border bg-white px-3 py-1 text-gray-500 shadow-sm transition-all hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 {classNames}"
11
- >
12
- <CarbonContinue class="mr-2 text-xs " /> Continue
13
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/CopyToClipBoardBtn.svelte CHANGED
@@ -35,17 +35,17 @@
35
  </script>
36
 
37
  <button
38
- class="btn rounded-lg border border-gray-200 px-2 py-2 text-sm shadow-sm transition-all hover:border-gray-300 active:shadow-inner dark:border-gray-700 dark:hover:border-gray-500 {classNames}"
 
 
 
39
  title={"Copy to clipboard"}
40
  type="button"
41
  on:click
42
  on:click={handleClick}
43
  >
44
- <div class="relative">
45
- <slot>
46
- <IconCopy classNames="dark:text-gray-700 text-gray-200" />
47
- </slot>
48
-
49
  <Tooltip classNames={isSuccess ? "opacity-100" : "opacity-0"} />
50
- </div>
51
  </button>
 
35
  </script>
36
 
37
  <button
38
+ class="btn rounded-lg border border-gray-200 px-2 py-2 text-sm shadow-sm transition-all hover:border-gray-300 active:shadow-inner dark:border-gray-600 dark:hover:border-gray-400 {classNames}
39
+ {!isSuccess && 'text-gray-200 dark:text-gray-200'}
40
+ {isSuccess && 'text-green-500'}
41
+ "
42
  title={"Copy to clipboard"}
43
  type="button"
44
  on:click
45
  on:click={handleClick}
46
  >
47
+ <span class="relative">
48
+ <IconCopy />
 
 
 
49
  <Tooltip classNames={isSuccess ? "opacity-100" : "opacity-0"} />
50
+ </span>
51
  </button>
src/lib/components/DisclaimerModal.svelte CHANGED
@@ -1,54 +1,59 @@
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
- import { env as envPublic } from "$env/dynamic/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
- import { useSettingsStore } from "$lib/stores/settings";
8
- import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
9
  import Logo from "./icons/Logo.svelte";
10
 
11
- const settings = useSettingsStore();
12
  </script>
13
 
14
  <Modal>
15
  <div
16
- class="from-primary-500/40 via-primary-500/10 to-primary-500/0 flex w-full flex-col items-center gap-6 bg-gradient-to-b px-5 pb-8 pt-9 text-center sm:px-6"
17
  >
18
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
19
  <Logo classNames="mr-1" />
20
- {envPublic.PUBLIC_APP_NAME}
21
  </h2>
22
 
23
  <p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
24
- {envPublic.PUBLIC_APP_DESCRIPTION}
25
  </p>
26
 
27
  <p class="text-sm text-gray-500">
28
- {envPublic.PUBLIC_APP_DISCLAIMER_MESSAGE}
 
29
  </p>
30
 
31
  <div class="flex w-full flex-col items-center gap-2">
32
  {#if $page.data.guestMode || !$page.data.loginEnabled}
33
- <button
34
- class="w-full justify-center rounded-full border-2 border-gray-300 bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
35
- class:bg-white={$page.data.loginEnabled}
36
- class:text-gray-800={$page.data.loginEnabled}
37
- class:hover:bg-slate-100={$page.data.loginEnabled}
38
- on:click|preventDefault|stopPropagation={() => {
39
- if (!cookiesAreEnabled()) {
40
- window.open(window.location.href, "_blank");
41
- }
42
-
43
- $settings.ethicsModalAccepted = true;
44
- }}
45
- >
46
- {#if $page.data.loginEnabled}
47
- Try as guest
48
- {:else}
49
- Start chatting
50
- {/if}
51
- </button>
 
 
 
 
 
52
  {/if}
53
  {#if $page.data.loginEnabled}
54
  <form action="{base}/login" target="_parent" method="POST" class="w-full">
@@ -57,7 +62,7 @@
57
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
58
  >
59
  Sign in
60
- {#if envPublic.PUBLIC_APP_NAME === "HuggingChat"}
61
  with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5 flex-none" /> Hugging Face
62
  {/if}
63
  </button>
 
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
+ import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
+ import type { LayoutData } from "../../routes/$types";
 
8
  import Logo from "./icons/Logo.svelte";
9
 
10
+ export let settings: LayoutData["settings"];
11
  </script>
12
 
13
  <Modal>
14
  <div
15
+ class="flex w-full flex-col items-center gap-6 bg-gradient-to-b from-primary-500/40 via-primary-500/10 to-primary-500/0 px-5 pb-8 pt-9 text-center sm:px-6"
16
  >
17
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
18
  <Logo classNames="mr-1" />
19
+ {PUBLIC_APP_NAME}
20
  </h2>
21
 
22
  <p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
23
+ {PUBLIC_APP_DESCRIPTION}
24
  </p>
25
 
26
  <p class="text-sm text-gray-500">
27
+ Disclaimer: AI is an area of active research with known problems such as biased generation and
28
+ misinformation. Do not use this application for high-stakes decisions or advice.
29
  </p>
30
 
31
  <div class="flex w-full flex-col items-center gap-2">
32
  {#if $page.data.guestMode || !$page.data.loginEnabled}
33
+ <form action="{base}/settings" method="POST" class="w-full">
34
+ <input type="hidden" name="ethicsModalAccepted" value={true} />
35
+ {#each Object.entries(settings).filter(([k]) => !(k === "customPrompts")) as [key, val]}
36
+ <input type="hidden" name={key} value={val} />
37
+ {/each}
38
+ <input
39
+ type="hidden"
40
+ name="customPrompts"
41
+ value={JSON.stringify(settings.customPrompts)}
42
+ />
43
+ <button
44
+ type="submit"
45
+ class="w-full justify-center rounded-full border-2 border-gray-300 bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
46
+ class:bg-white={$page.data.loginEnabled}
47
+ class:text-gray-800={$page.data.loginEnabled}
48
+ class:hover:bg-slate-100={$page.data.loginEnabled}
49
+ >
50
+ {#if $page.data.loginEnabled}
51
+ Try as guest
52
+ {:else}
53
+ Start chatting
54
+ {/if}
55
+ </button>
56
+ </form>
57
  {/if}
58
  {#if $page.data.loginEnabled}
59
  <form action="{base}/login" target="_parent" method="POST" class="w-full">
 
62
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
63
  >
64
  Sign in
65
+ {#if PUBLIC_APP_NAME === "HuggingChat"}
66
  with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5 flex-none" /> Hugging Face
67
  {/if}
68
  </button>
src/lib/components/ExpandNavigation.svelte DELETED
@@ -1,14 +0,0 @@
1
- <script lang="ts">
2
- export let isCollapsed: boolean;
3
- export let classNames: string;
4
- </script>
5
-
6
- <button
7
- on:click
8
- class="{classNames} group flex h-16 w-6 flex-col items-center justify-center -space-y-1 outline-none *:h-3 *:w-1 *:rounded-full *:hover:bg-gray-300 max-md:hidden dark:*:hover:bg-gray-600 {!isCollapsed
9
- ? '*:bg-gray-200/70 dark:*:bg-gray-800'
10
- : '*:bg-gray-200 dark:*:bg-gray-700'}"
11
- >
12
- <div class={!isCollapsed ? "group-hover:rotate-[20deg]" : "group-hover:-rotate-[20deg]"} />
13
- <div class={!isCollapsed ? "group-hover:-rotate-[20deg]" : "group-hover:rotate-[20deg]"} />
14
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/HoverTooltip.svelte DELETED
@@ -1,12 +0,0 @@
1
- <script lang="ts">
2
- export let label = "";
3
- </script>
4
-
5
- <div class="group/tooltip md:relative">
6
- <slot />
7
- <div
8
- class="invisible absolute z-10 w-64 whitespace-normal rounded-md bg-black p-2 text-center text-white group-hover/tooltip:visible group-active/tooltip:visible max-sm:left-1/2 max-sm:-translate-x-1/2"
9
- >
10
- {label}
11
- </div>
12
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/LoginModal.svelte CHANGED
@@ -1,31 +1,28 @@
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
- import { env as envPublic } from "$env/dynamic/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
- import { useSettingsStore } from "$lib/stores/settings";
8
- import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
9
  import Logo from "./icons/Logo.svelte";
10
-
11
- const settings = useSettingsStore();
12
  </script>
13
 
14
  <Modal on:close>
15
  <div
16
- class="from-primary-500/40 via-primary-500/10 to-primary-500/0 flex w-full flex-col items-center gap-6 bg-gradient-to-b px-5 pb-8 pt-9 text-center"
17
  >
18
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
19
  <Logo classNames="mr-1" />
20
- {envPublic.PUBLIC_APP_NAME}
21
  </h2>
22
- <p class="text-balance text-lg font-semibold leading-snug text-gray-800">
23
- {envPublic.PUBLIC_APP_DESCRIPTION}
24
  </p>
25
- <p class="text-balance rounded-xl border bg-white/80 p-2 text-base text-gray-800">
26
- You have reached the guest message limit, <strong class="font-semibold"
27
- >Sign In with a free Hugging Face account</strong
28
- > to continue using HuggingChat.
29
  </p>
30
 
31
  <form
@@ -40,20 +37,18 @@
40
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full bg-black px-5 py-2 text-center text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
41
  >
42
  Sign in
43
- {#if envPublic.PUBLIC_APP_NAME === "HuggingChat"}
44
  with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5" /> Hugging Face
45
  {/if}
46
  </button>
47
  {:else}
 
 
 
 
48
  <button
 
49
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
50
- on:click={(e) => {
51
- if (!cookiesAreEnabled()) {
52
- e.preventDefault();
53
- window.open(window.location.href, "_blank");
54
- }
55
- $settings.ethicsModalAccepted = true;
56
- }}
57
  >
58
  Start chatting
59
  </button>
 
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
+ import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
+ import type { LayoutData } from "../../routes/$types";
 
8
  import Logo from "./icons/Logo.svelte";
9
+ export let settings: LayoutData["settings"];
 
10
  </script>
11
 
12
  <Modal on:close>
13
  <div
14
+ class="flex w-full flex-col items-center gap-6 bg-gradient-to-b from-primary-500/40 via-primary-500/10 to-primary-500/0 px-5 pb-8 pt-9 text-center"
15
  >
16
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
17
  <Logo classNames="mr-1" />
18
+ {PUBLIC_APP_NAME}
19
  </h2>
20
+ <p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
21
+ {PUBLIC_APP_DESCRIPTION}
22
  </p>
23
+ <p class="rounded-xl border bg-white/80 p-2 text-base text-gray-800">
24
+ You have reached the guest message limit, please Sign In with your Hugging Face account to
25
+ continue.
 
26
  </p>
27
 
28
  <form
 
37
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full bg-black px-5 py-2 text-center text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
38
  >
39
  Sign in
40
+ {#if PUBLIC_APP_NAME === "HuggingChat"}
41
  with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5" /> Hugging Face
42
  {/if}
43
  </button>
44
  {:else}
45
+ <input type="hidden" name="ethicsModalAccepted" value={true} />
46
+ {#each Object.entries(settings) as [key, val]}
47
+ <input type="hidden" name={key} value={val} />
48
+ {/each}
49
  <button
50
+ type="submit"
51
  class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
 
 
 
 
 
 
 
52
  >
53
  Start chatting
54
  </button>
src/lib/components/MobileNav.svelte CHANGED
@@ -3,16 +3,15 @@
3
  import { createEventDispatcher } from "svelte";
4
  import { browser } from "$app/environment";
5
  import { base } from "$app/paths";
6
- import { page } from "$app/stores";
7
 
8
  import CarbonClose from "~icons/carbon/close";
 
9
  import CarbonTextAlignJustify from "~icons/carbon/text-align-justify";
10
- import IconNew from "$lib/components/icons/IconNew.svelte";
11
 
12
  export let isOpen = false;
13
  export let title: string | undefined;
14
 
15
- $: title = title ?? "New Chat";
16
 
17
  let closeEl: HTMLButtonElement;
18
  let openEl: HTMLButtonElement;
@@ -31,31 +30,29 @@
31
  </script>
32
 
33
  <nav
34
- class="flex h-12 items-center justify-between border-b bg-gray-50 px-3 md:hidden dark:border-gray-800 dark:bg-gray-800/70"
35
  >
36
  <button
37
  type="button"
38
- class="-ml-3 flex size-12 shrink-0 items-center justify-center text-lg"
39
  on:click={() => dispatch("toggle", true)}
40
  aria-label="Open menu"
41
  bind:this={openEl}><CarbonTextAlignJustify /></button
42
  >
43
  <span class="truncate px-4">{title}</span>
44
- <a
45
- class:invisible={!$page.params.id}
46
- href="{base}/"
47
- class="-mr-3 flex size-12 shrink-0 items-center justify-center text-lg"><IconNew /></a
48
  >
49
  </nav>
50
  <nav
51
- class="fixed inset-0 z-30 grid max-h-screen grid-cols-1 grid-rows-[auto,auto,1fr,auto] bg-white dark:bg-gray-900 {isOpen
52
  ? 'block'
53
  : 'hidden'}"
54
  >
55
  <div class="flex h-12 items-center px-4">
56
  <button
57
  type="button"
58
- class="-mr-3 ml-auto flex size-12 items-center justify-center text-lg"
59
  on:click={() => dispatch("toggle", false)}
60
  aria-label="Close menu"
61
  bind:this={closeEl}><CarbonClose /></button
 
3
  import { createEventDispatcher } from "svelte";
4
  import { browser } from "$app/environment";
5
  import { base } from "$app/paths";
 
6
 
7
  import CarbonClose from "~icons/carbon/close";
8
+ import CarbonAdd from "~icons/carbon/add";
9
  import CarbonTextAlignJustify from "~icons/carbon/text-align-justify";
 
10
 
11
  export let isOpen = false;
12
  export let title: string | undefined;
13
 
14
+ $: title = title || "New Chat";
15
 
16
  let closeEl: HTMLButtonElement;
17
  let openEl: HTMLButtonElement;
 
30
  </script>
31
 
32
  <nav
33
+ class="flex h-12 items-center justify-between border-b bg-gray-50 px-4 dark:border-gray-800 dark:bg-gray-800/70 md:hidden"
34
  >
35
  <button
36
  type="button"
37
+ class="-ml-3 flex h-9 w-9 shrink-0 items-center justify-center"
38
  on:click={() => dispatch("toggle", true)}
39
  aria-label="Open menu"
40
  bind:this={openEl}><CarbonTextAlignJustify /></button
41
  >
42
  <span class="truncate px-4">{title}</span>
43
+ <a href={`${base}/`} class="-mr-3 flex h-9 w-9 shrink-0 items-center justify-center"
44
+ ><CarbonAdd /></a
 
 
45
  >
46
  </nav>
47
  <nav
48
+ class="fixed inset-0 z-30 grid max-h-screen grid-cols-1 grid-rows-[auto,auto,1fr,auto] bg-white bg-gradient-to-l from-gray-50 dark:bg-gray-900 dark:from-gray-800/30 max-sm:rounded-t-2xl {isOpen
49
  ? 'block'
50
  : 'hidden'}"
51
  >
52
  <div class="flex h-12 items-center px-4">
53
  <button
54
  type="button"
55
+ class="-mr-3 ml-auto flex h-9 w-9 items-center justify-center"
56
  on:click={() => dispatch("toggle", false)}
57
  aria-label="Close menu"
58
  bind:this={closeEl}><CarbonClose /></button
src/lib/components/Modal.svelte CHANGED
@@ -21,9 +21,6 @@
21
  }
22
 
23
  function handleBackdropClick(event: MouseEvent) {
24
- if (window?.getSelection()?.toString()) {
25
- return;
26
- }
27
  if (event.target === backdropEl) {
28
  dispatch("close");
29
  }
@@ -49,7 +46,7 @@
49
  role="presentation"
50
  tabindex="-1"
51
  bind:this={backdropEl}
52
- on:click|stopPropagation={handleBackdropClick}
53
  transition:fade|global={{ easing: cubicOut, duration: 300 }}
54
  class="fixed inset-0 z-40 flex items-center justify-center bg-black/80 p-8 backdrop-blur-sm dark:bg-black/50"
55
  >
 
21
  }
22
 
23
  function handleBackdropClick(event: MouseEvent) {
 
 
 
24
  if (event.target === backdropEl) {
25
  dispatch("close");
26
  }
 
46
  role="presentation"
47
  tabindex="-1"
48
  bind:this={backdropEl}
49
+ on:click={handleBackdropClick}
50
  transition:fade|global={{ easing: cubicOut, duration: 300 }}
51
  class="fixed inset-0 z-40 flex items-center justify-center bg-black/80 p-8 backdrop-blur-sm dark:bg-black/50"
52
  >
src/lib/components/ModelCardMetadata.svelte CHANGED
@@ -1,7 +1,6 @@
1
  <script lang="ts">
2
  import CarbonEarth from "~icons/carbon/earth";
3
  import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
4
- import BIMeta from "~icons/bi/meta";
5
  import type { Model } from "$lib/types/Model";
6
 
7
  export let model: Pick<Model, "name" | "datasetName" | "websiteUrl" | "modelUrl" | "datasetUrl">;
@@ -42,13 +41,8 @@
42
  class="ml-auto flex items-center hover:underline"
43
  rel="noreferrer"
44
  >
45
- {#if model.name === "meta-llama/Meta-Llama-3-70B-Instruct"}
46
- <BIMeta class="mr-1.5 shrink-0 text-xs text-gray-400" />
47
- Built with Meta Llama 3
48
- {:else}
49
- <CarbonEarth class="mr-1.5 shrink-0 text-xs text-gray-400" />
50
- Website
51
- {/if}
52
  </a>
53
  {/if}
54
  </div>
 
1
  <script lang="ts">
2
  import CarbonEarth from "~icons/carbon/earth";
3
  import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
 
4
  import type { Model } from "$lib/types/Model";
5
 
6
  export let model: Pick<Model, "name" | "datasetName" | "websiteUrl" | "modelUrl" | "datasetUrl">;
 
41
  class="ml-auto flex items-center hover:underline"
42
  rel="noreferrer"
43
  >
44
+ <CarbonEarth class="mr-1.5 shrink-0 text-xs text-gray-400" />
45
+ Website
 
 
 
 
 
46
  </a>
47
  {/if}
48
  </div>
src/lib/components/ModelsModal.svelte ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from "svelte";
3
+
4
+ import Modal from "$lib/components/Modal.svelte";
5
+ import CarbonClose from "~icons/carbon/close";
6
+ import CarbonCheckmark from "~icons/carbon/checkmark-filled";
7
+ import ModelCardMetadata from "./ModelCardMetadata.svelte";
8
+ import type { Model } from "$lib/types/Model";
9
+ import type { LayoutData } from "../../routes/$types";
10
+ import { enhance } from "$app/forms";
11
+ import { base } from "$app/paths";
12
+
13
+ import CarbonEdit from "~icons/carbon/edit";
14
+ import CarbonSave from "~icons/carbon/save";
15
+ import CarbonRestart from "~icons/carbon/restart";
16
+
17
+ export let settings: LayoutData["settings"];
18
+ export let models: Array<Model>;
19
+
20
+ let selectedModelId = settings.activeModel;
21
+
22
+ const dispatch = createEventDispatcher<{ close: void }>();
23
+
24
+ let expanded = false;
25
+
26
+ function onToggle() {
27
+ if (expanded) {
28
+ settings.customPrompts[selectedModelId] = value;
29
+ }
30
+ expanded = !expanded;
31
+ }
32
+
33
+ let value = "";
34
+
35
+ function onModelChange() {
36
+ value =
37
+ settings.customPrompts[selectedModelId] ??
38
+ models.filter((el) => el.id === selectedModelId)[0].preprompt ??
39
+ "";
40
+ }
41
+
42
+ $: selectedModelId, onModelChange();
43
+ </script>
44
+
45
+ <Modal width="max-w-lg" on:close>
46
+ <form
47
+ action="{base}/settings"
48
+ method="post"
49
+ on:submit={() => {
50
+ if (expanded) {
51
+ onToggle();
52
+ }
53
+ }}
54
+ use:enhance={() => {
55
+ dispatch("close");
56
+ }}
57
+ class="flex w-full flex-col gap-5 p-6"
58
+ >
59
+ {#each Object.entries(settings).filter(([k]) => !(k == "activeModel" || k === "customPrompts")) as [key, val]}
60
+ <input type="hidden" name={key} value={val} />
61
+ {/each}
62
+ <input type="hidden" name="customPrompts" value={JSON.stringify(settings.customPrompts)} />
63
+ <div class="flex items-start justify-between text-xl font-semibold text-gray-800">
64
+ <h2>Models</h2>
65
+ <button type="button" class="group" on:click={() => dispatch("close")}>
66
+ <CarbonClose class="text-gray-900 group-hover:text-gray-500" />
67
+ </button>
68
+ </div>
69
+
70
+ <div class="space-y-4">
71
+ {#each models as model}
72
+ {@const active = model.id === selectedModelId}
73
+ <div
74
+ class="relative rounded-xl border border-gray-100 {active
75
+ ? 'bg-gradient-to-r from-primary-200/40 via-primary-500/10'
76
+ : ''}"
77
+ >
78
+ <label
79
+ class="group flex cursor-pointer flex-col p-3"
80
+ on:change
81
+ aria-label={model.displayName}
82
+ >
83
+ <input
84
+ type="radio"
85
+ class="sr-only"
86
+ name="activeModel"
87
+ value={model.id}
88
+ bind:group={selectedModelId}
89
+ />
90
+ <div
91
+ class="mb-1.5 block pr-8 text-sm font-semibold leading-tight text-gray-800 sm:text-base"
92
+ >
93
+ {model.displayName}
94
+ </div>
95
+ {#if model.description}
96
+ <div class="text-xs text-gray-500 sm:text-sm">{model.description}</div>
97
+ {/if}
98
+ <CarbonCheckmark
99
+ class="absolute right-2 top-2 text-xl {active
100
+ ? 'text-primary-400'
101
+ : 'text-transparent group-hover:text-gray-200'}"
102
+ />
103
+ </label>
104
+ {#if active}
105
+ <div class=" overflow-hidden rounded-xl px-3 pb-2">
106
+ <div class="flex flex-row flex-nowrap gap-2 pb-1">
107
+ <div class="text-xs font-semibold text-gray-500">System Prompt</div>
108
+ {#if expanded}
109
+ <button
110
+ class="text-gray-500 hover:text-gray-900"
111
+ on:click|preventDefault={onToggle}
112
+ >
113
+ <CarbonSave class="text-sm" />
114
+ </button>
115
+ <button
116
+ class="text-gray-500 hover:text-gray-900"
117
+ on:click|preventDefault={() => {
118
+ value = model.preprompt ?? "";
119
+ }}
120
+ >
121
+ <CarbonRestart class="text-sm" />
122
+ </button>
123
+ {:else}
124
+ <button
125
+ class=" text-gray-500 hover:text-gray-900"
126
+ on:click|preventDefault={onToggle}
127
+ >
128
+ <CarbonEdit class="text-sm" />
129
+ </button>
130
+ {/if}
131
+ </div>
132
+ <textarea
133
+ enterkeyhint="send"
134
+ tabindex="0"
135
+ rows="1"
136
+ class="h-20 w-full resize-none scroll-p-3 overflow-x-hidden overflow-y-scroll rounded-md border border-gray-300 bg-transparent p-1 text-xs outline-none focus:ring-0 focus-visible:ring-0"
137
+ bind:value
138
+ hidden={!expanded}
139
+ />
140
+ </div>
141
+ {/if}
142
+ <ModelCardMetadata {model} />
143
+ </div>
144
+ {/each}
145
+ </div>
146
+ <button
147
+ type="submit"
148
+ class="sticky bottom-6 mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-colors hover:ring"
149
+ >
150
+ Apply
151
+ </button>
152
+ </form>
153
+ </Modal>
src/lib/components/NavConversationItem.svelte CHANGED
@@ -7,9 +7,8 @@
7
  import CarbonTrashCan from "~icons/carbon/trash-can";
8
  import CarbonClose from "~icons/carbon/close";
9
  import CarbonEdit from "~icons/carbon/edit";
10
- import type { ConvSidebar } from "$lib/types/ConvSidebar";
11
 
12
- export let conv: ConvSidebar;
13
 
14
  let confirmDelete = false;
15
 
@@ -25,30 +24,16 @@
25
  confirmDelete = false;
26
  }}
27
  href="{base}/conversation/{conv.id}"
28
- class="group flex h-10 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 {conv.id ===
29
  $page.params.id
30
  ? 'bg-gray-100 dark:bg-gray-700'
31
  : ''}"
32
  >
33
- <div class="flex flex-1 items-center truncate">
34
  {#if confirmDelete}
35
- <span class="mr-1 font-semibold"> Delete </span>
36
- {/if}
37
- {#if conv.avatarHash}
38
- <img
39
- src="{base}/settings/assistants/{conv.assistantId}/avatar.jpg?hash={conv.avatarHash}"
40
- alt="Assistant avatar"
41
- class="mr-1.5 inline size-4 flex-none rounded-full object-cover"
42
- />
43
- {conv.title.replace(/\p{Emoji}/gu, "")}
44
- {:else if conv.assistantId}
45
- <div
46
- class="mr-1.5 flex size-4 flex-none items-center justify-center rounded-full bg-gray-300 text-xs font-bold uppercase text-gray-500"
47
- />
48
- {conv.title.replace(/\p{Emoji}/gu, "")}
49
- {:else}
50
- {conv.title}
51
  {/if}
 
52
  </div>
53
 
54
  {#if confirmDelete}
@@ -56,10 +41,7 @@
56
  type="button"
57
  class="flex h-5 w-5 items-center justify-center rounded md:hidden md:group-hover:flex"
58
  title="Confirm delete action"
59
- on:click|preventDefault={() => {
60
- confirmDelete = false;
61
- dispatch("deleteConversation", conv.id);
62
- }}
63
  >
64
  <CarbonCheckmark class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
65
  </button>
@@ -67,7 +49,9 @@
67
  type="button"
68
  class="flex h-5 w-5 items-center justify-center rounded md:hidden md:group-hover:flex"
69
  title="Cancel delete action"
70
- on:click|preventDefault={() => (confirmDelete = false)}
 
 
71
  >
72
  <CarbonClose class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
73
  </button>
 
7
  import CarbonTrashCan from "~icons/carbon/trash-can";
8
  import CarbonClose from "~icons/carbon/close";
9
  import CarbonEdit from "~icons/carbon/edit";
 
10
 
11
+ export let conv: { id: string; title: string };
12
 
13
  let confirmDelete = false;
14
 
 
24
  confirmDelete = false;
25
  }}
26
  href="{base}/conversation/{conv.id}"
27
+ class="group flex h-11 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 {conv.id ===
28
  $page.params.id
29
  ? 'bg-gray-100 dark:bg-gray-700'
30
  : ''}"
31
  >
32
+ <div class="flex-1 truncate">
33
  {#if confirmDelete}
34
+ <span class="font-semibold"> Delete </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  {/if}
36
+ {conv.title}
37
  </div>
38
 
39
  {#if confirmDelete}
 
41
  type="button"
42
  class="flex h-5 w-5 items-center justify-center rounded md:hidden md:group-hover:flex"
43
  title="Confirm delete action"
44
+ on:click|preventDefault={() => dispatch("deleteConversation", conv.id)}
 
 
 
45
  >
46
  <CarbonCheckmark class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
47
  </button>
 
49
  type="button"
50
  class="flex h-5 w-5 items-center justify-center rounded md:hidden md:group-hover:flex"
51
  title="Cancel delete action"
52
+ on:click|preventDefault={() => {
53
+ confirmDelete = false;
54
+ }}
55
  >
56
  <CarbonClose class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
57
  </button>
src/lib/components/NavMenu.svelte CHANGED
@@ -1,58 +1,37 @@
1
  <script lang="ts">
2
  import { base } from "$app/paths";
 
3
 
4
  import Logo from "$lib/components/icons/Logo.svelte";
5
  import { switchTheme } from "$lib/switchTheme";
6
  import { isAborted } from "$lib/stores/isAborted";
7
- import { env as envPublic } from "$env/dynamic/public";
8
  import NavConversationItem from "./NavConversationItem.svelte";
9
  import type { LayoutData } from "../../routes/$types";
10
- import type { ConvSidebar } from "$lib/types/ConvSidebar";
11
- import type { Model } from "$lib/types/Model";
12
- import { page } from "$app/stores";
13
 
14
- export let conversations: ConvSidebar[] = [];
 
 
 
 
 
 
 
 
 
 
15
  export let canLogin: boolean;
16
  export let user: LayoutData["user"];
17
 
18
  function handleNewChatClick() {
19
  isAborted.set(true);
20
  }
21
-
22
- const dateRanges = [
23
- new Date().setDate(new Date().getDate() - 1),
24
- new Date().setDate(new Date().getDate() - 7),
25
- new Date().setMonth(new Date().getMonth() - 1),
26
- ];
27
-
28
- $: groupedConversations = {
29
- today: conversations.filter(({ updatedAt }) => updatedAt.getTime() > dateRanges[0]),
30
- week: conversations.filter(
31
- ({ updatedAt }) => updatedAt.getTime() > dateRanges[1] && updatedAt.getTime() < dateRanges[0]
32
- ),
33
- month: conversations.filter(
34
- ({ updatedAt }) => updatedAt.getTime() > dateRanges[2] && updatedAt.getTime() < dateRanges[1]
35
- ),
36
- older: conversations.filter(({ updatedAt }) => updatedAt.getTime() < dateRanges[2]),
37
- };
38
-
39
- const titles: { [key: string]: string } = {
40
- today: "Today",
41
- week: "This week",
42
- month: "This month",
43
- older: "Older",
44
- } as const;
45
-
46
- const nModels: number = $page.data.models.filter((el: Model) => !el.unlisted).length;
47
  </script>
48
 
49
  <div class="sticky top-0 flex flex-none items-center justify-between px-3 py-3.5 max-sm:pt-0">
50
- <a
51
- class="flex items-center rounded-xl text-lg font-semibold"
52
- href="{envPublic.PUBLIC_ORIGIN}{base}/"
53
- >
54
  <Logo classNames="mr-1" />
55
- {envPublic.PUBLIC_APP_NAME}
56
  </a>
57
  <a
58
  href={`${base}/`}
@@ -63,27 +42,20 @@
63
  </a>
64
  </div>
65
  <div
66
- class="scrollbar-custom flex flex-col gap-1 overflow-y-auto rounded-r-xl from-gray-50 px-3 pb-3 pt-2 max-sm:bg-gradient-to-t md:bg-gradient-to-l dark:from-gray-800/30"
67
  >
68
- {#each Object.entries(groupedConversations) as [group, convs]}
69
- {#if convs.length}
70
- <h4 class="mb-1.5 mt-4 pl-0.5 text-sm text-gray-400 first:mt-0 dark:text-gray-500">
71
- {titles[group]}
72
- </h4>
73
- {#each convs as conv}
74
- <NavConversationItem on:editConversationTitle on:deleteConversation {conv} />
75
- {/each}
76
- {/if}
77
  {/each}
78
  </div>
79
  <div
80
- class="mt-0.5 flex flex-col gap-1 rounded-r-xl p-3 text-sm md:bg-gradient-to-l md:from-gray-50 md:dark:from-gray-800/30"
81
  >
82
  {#if user?.username || user?.email}
83
  <form
84
  action="{base}/logout"
85
  method="post"
86
- class="group flex items-center gap-1.5 rounded-lg pl-2.5 pr-2 hover:bg-gray-100 dark:hover:bg-gray-700"
87
  >
88
  <span
89
  class="flex h-9 flex-none shrink items-center gap-1.5 truncate pr-2 text-gray-500 dark:text-gray-400"
@@ -91,7 +63,7 @@
91
  >
92
  <button
93
  type="submit"
94
- class="ml-auto h-6 flex-none items-center gap-1.5 rounded-md border bg-white px-2 text-gray-700 shadow-sm group-hover:flex hover:shadow-none md:hidden dark:border-gray-600 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-300"
95
  >
96
  Sign Out
97
  </button>
@@ -101,7 +73,7 @@
101
  <form action="{base}/login" method="POST" target="_parent">
102
  <button
103
  type="submit"
104
- class="flex h-9 w-full flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
105
  >
106
  Login
107
  </button>
@@ -110,45 +82,29 @@
110
  <button
111
  on:click={switchTheme}
112
  type="button"
113
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
114
  >
115
  Theme
116
  </button>
117
- {#if nModels > 1}
118
- <a
119
- href="{base}/models"
120
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
121
- >
122
- Models
123
- <span
124
- class="ml-auto rounded-full border border-gray-300 px-2 py-0.5 text-xs text-gray-500 dark:border-gray-500 dark:text-gray-400"
125
- >{nModels}</span
126
- >
127
- </a>
128
- {/if}
129
- {#if $page.data.enableAssistants}
130
  <a
131
- href="{base}/assistants"
132
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
 
 
133
  >
134
- Assistants
135
- <span
136
- class="ml-auto rounded-full border border-gray-300 px-2 py-0.5 text-xs text-gray-500 dark:border-gray-500 dark:text-gray-400"
137
- >New</span
138
- >
139
  </a>
140
- {/if}
141
-
142
- <a
143
- href="{base}/settings"
144
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
145
- >
146
- Settings
147
- </a>
148
- {#if envPublic.PUBLIC_APP_NAME === "HuggingChat"}
149
  <a
150
  href="{base}/privacy"
151
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
152
  >
153
  About & Privacy
154
  </a>
 
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
+ import { createEventDispatcher } from "svelte";
4
 
5
  import Logo from "$lib/components/icons/Logo.svelte";
6
  import { switchTheme } from "$lib/switchTheme";
7
  import { isAborted } from "$lib/stores/isAborted";
8
+ import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public";
9
  import NavConversationItem from "./NavConversationItem.svelte";
10
  import type { LayoutData } from "../../routes/$types";
 
 
 
11
 
12
+ const dispatch = createEventDispatcher<{
13
+ shareConversation: { id: string; title: string };
14
+ clickSettings: void;
15
+ clickLogout: void;
16
+ }>();
17
+
18
+ export let conversations: Array<{
19
+ id: string;
20
+ title: string;
21
+ }> = [];
22
+
23
  export let canLogin: boolean;
24
  export let user: LayoutData["user"];
25
 
26
  function handleNewChatClick() {
27
  isAborted.set(true);
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </script>
30
 
31
  <div class="sticky top-0 flex flex-none items-center justify-between px-3 py-3.5 max-sm:pt-0">
32
+ <a class="flex items-center rounded-xl text-lg font-semibold" href="{PUBLIC_ORIGIN}{base}/">
 
 
 
33
  <Logo classNames="mr-1" />
34
+ {PUBLIC_APP_NAME}
35
  </a>
36
  <a
37
  href={`${base}/`}
 
42
  </a>
43
  </div>
44
  <div
45
+ class="scrollbar-custom flex flex-col gap-1 overflow-y-auto rounded-r-xl bg-gradient-to-l from-gray-50 px-3 pb-3 pt-2 dark:from-gray-800/30"
46
  >
47
+ {#each conversations as conv (conv.id)}
48
+ <NavConversationItem on:editConversationTitle on:deleteConversation {conv} />
 
 
 
 
 
 
 
49
  {/each}
50
  </div>
51
  <div
52
+ class="mt-0.5 flex flex-col gap-1 rounded-r-xl bg-gradient-to-l from-gray-50 p-3 text-sm dark:from-gray-800/30"
53
  >
54
  {#if user?.username || user?.email}
55
  <form
56
  action="{base}/logout"
57
  method="post"
58
+ class="group flex items-center gap-1.5 rounded-lg pl-3 pr-2 hover:bg-gray-100 dark:hover:bg-gray-700"
59
  >
60
  <span
61
  class="flex h-9 flex-none shrink items-center gap-1.5 truncate pr-2 text-gray-500 dark:text-gray-400"
 
63
  >
64
  <button
65
  type="submit"
66
+ class="ml-auto h-6 flex-none items-center gap-1.5 rounded-md border bg-white px-2 text-gray-700 shadow-sm group-hover:flex hover:shadow-none dark:border-gray-600 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-300 md:hidden"
67
  >
68
  Sign Out
69
  </button>
 
73
  <form action="{base}/login" method="POST" target="_parent">
74
  <button
75
  type="submit"
76
+ class="flex h-9 w-full flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
77
  >
78
  Login
79
  </button>
 
82
  <button
83
  on:click={switchTheme}
84
  type="button"
85
+ class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
86
  >
87
  Theme
88
  </button>
89
+ <button
90
+ on:click={() => dispatch("clickSettings")}
91
+ type="button"
92
+ class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
93
+ >
94
+ Settings
95
+ </button>
96
+ {#if PUBLIC_APP_NAME === "HuggingChat"}
 
 
 
 
 
97
  <a
98
+ href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
99
+ target="_blank"
100
+ rel="noreferrer"
101
+ class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
102
  >
103
+ Feedback
 
 
 
 
104
  </a>
 
 
 
 
 
 
 
 
 
105
  <a
106
  href="{base}/privacy"
107
+ class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
108
  >
109
  About & Privacy
110
  </a>
src/lib/components/OpenWebSearchResults.svelte CHANGED
@@ -1,56 +1,41 @@
1
  <script lang="ts">
2
  import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
 
3
 
 
4
  import CarbonError from "~icons/carbon/error-filled";
 
5
  import EosIconsLoading from "~icons/eos-icons/loading";
6
- import IconInternet from "./icons/IconInternet.svelte";
7
 
 
8
  export let classNames = "";
9
  export let webSearchMessages: WebSearchUpdate[] = [];
10
 
11
- $: sources = webSearchMessages.find((m) => m.sources)?.sources;
12
- $: lastMessage = webSearchMessages.filter((m) => m.messageType !== "sources").slice(-1)[0];
13
- $: loading = !sources && lastMessage.messageType !== "error";
14
  </script>
15
 
16
  <details
17
  class="flex w-fit rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900 {classNames} max-w-full"
 
18
  >
19
- <summary class="grid min-w-72 select-none grid-cols-[40px,1fr] items-center gap-2.5 p-2">
20
- <div
21
- class="relative grid aspect-square place-content-center overflow-hidden rounded-lg bg-gray-100 dark:bg-gray-800"
22
- >
23
- <svg
24
- class="absolute inset-0 text-gray-300 transition-opacity dark:text-gray-700 {loading
25
- ? 'opacity-100'
26
- : 'opacity-0'}"
27
- width="40"
28
- height="40"
29
- viewBox="0 0 38 38"
30
- fill="none"
31
- xmlns="http://www.w3.org/2000/svg"
32
- >
33
- <path
34
- class="loading-path"
35
- d="M8 2.5H30C30 2.5 35.5 2.5 35.5 8V30C35.5 30 35.5 35.5 30 35.5H8C8 35.5 2.5 35.5 2.5 30V8C2.5 8 2.5 2.5 8 2.5Z"
36
- stroke="currentColor"
37
- stroke-width="1"
38
- stroke-linecap="round"
39
- id="shape"
40
- />
41
- </svg>
42
- <IconInternet classNames="relative fill-current text-xl" />
43
  </div>
44
- <dl class="leading-4">
45
- <dd class="text-sm">Web Search</dd>
46
- <dt class="flex items-center gap-1 truncate whitespace-nowrap text-[.82rem] text-gray-400">
47
- {#if sources}
48
- Completed
49
- {:else}
50
- {lastMessage.message}
51
- {/if}
52
- </dt>
53
- </dl>
54
  </summary>
55
 
56
  <div class="content px-5 pb-5 pt-4">
@@ -103,18 +88,27 @@
103
  </details>
104
 
105
  <style>
106
- details summary::-webkit-details-marker {
107
- display: none;
 
 
 
 
 
 
 
 
 
 
108
  }
109
 
110
- .loading-path {
111
- stroke-dasharray: 61.45;
112
- animation: loading 2s linear infinite;
 
113
  }
114
 
115
- @keyframes loading {
116
- to {
117
- stroke-dashoffset: 122.9;
118
- }
119
  }
120
  </style>
 
1
  <script lang="ts">
2
  import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
3
+ import CarbonCaretRight from "~icons/carbon/caret-right";
4
 
5
+ import CarbonCheckmark from "~icons/carbon/checkmark-filled";
6
  import CarbonError from "~icons/carbon/error-filled";
7
+
8
  import EosIconsLoading from "~icons/eos-icons/loading";
 
9
 
10
+ export let loading = false;
11
  export let classNames = "";
12
  export let webSearchMessages: WebSearchUpdate[] = [];
13
 
14
+ let detailsOpen: boolean;
15
+ let error: boolean;
16
+ $: error = webSearchMessages[webSearchMessages.length - 1]?.messageType === "error";
17
  </script>
18
 
19
  <details
20
  class="flex w-fit rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900 {classNames} max-w-full"
21
+ bind:open={detailsOpen}
22
  >
23
+ <summary
24
+ class="align-center flex cursor-pointer select-none list-none py-1 pl-2.5 pr-2 align-text-top transition-all"
25
+ >
26
+ {#if error}
27
+ <CarbonError class="my-auto text-red-700 dark:text-red-500" />
28
+ {:else if loading}
29
+ <EosIconsLoading class="my-auto text-gray-500" />
30
+ {:else}
31
+ <CarbonCheckmark class="my-auto text-gray-500" />
32
+ {/if}
33
+ <span class="px-2 font-medium" class:text-red-700={error} class:dark:text-red-500={error}
34
+ >Web search
35
+ </span>
36
+ <div class="my-auto transition-all" class:rotate-90={detailsOpen}>
37
+ <CarbonCaretRight />
 
 
 
 
 
 
 
 
 
38
  </div>
 
 
 
 
 
 
 
 
 
 
39
  </summary>
40
 
41
  <div class="content px-5 pb-5 pt-4">
 
88
  </details>
89
 
90
  <style>
91
+ @keyframes grow {
92
+ 0% {
93
+ font-size: 0;
94
+ opacity: 0;
95
+ }
96
+ 30% {
97
+ font-size: 1em;
98
+ opacity: 0;
99
+ }
100
+ 100% {
101
+ opacity: 1;
102
+ }
103
  }
104
 
105
+ details[open] .content {
106
+ animation-name: grow;
107
+ animation-duration: 300ms;
108
+ animation-delay: 0ms;
109
  }
110
 
111
+ details summary::-webkit-details-marker {
112
+ display: none;
 
 
113
  }
114
  </style>
src/lib/components/Pagination.svelte DELETED
@@ -1,94 +0,0 @@
1
- <script lang="ts">
2
- import { page } from "$app/stores";
3
- import { getHref } from "$lib/utils/getHref";
4
- import PaginationArrow from "./PaginationArrow.svelte";
5
-
6
- export let classNames = "";
7
- export let numItemsPerPage: number;
8
- export let numTotalItems: number;
9
-
10
- const ELLIPSIS_IDX = -1 as const;
11
-
12
- $: numTotalPages = Math.ceil(numTotalItems / numItemsPerPage);
13
- $: pageIndex = parseInt($page.url.searchParams.get("p") ?? "0");
14
- $: pageIndexes = getPageIndexes(pageIndex, numTotalPages);
15
-
16
- function getPageIndexes(pageIdx: number, nTotalPages: number) {
17
- let pageIdxs: number[] = [];
18
-
19
- const NUM_EXTRA_BUTTONS = 2; // The number of page links to show on either side of the current page link.
20
-
21
- const minIdx = 0;
22
- const maxIdx = nTotalPages - 1;
23
-
24
- pageIdxs = [pageIdx];
25
-
26
- // forward
27
- for (let i = 1; i < NUM_EXTRA_BUTTONS + 1; i++) {
28
- const newPageIdx = pageIdx + i;
29
- if (newPageIdx > maxIdx) {
30
- continue;
31
- }
32
- pageIdxs.push(newPageIdx);
33
- }
34
- if (maxIdx - pageIdxs[pageIdxs.length - 1] > 1) {
35
- pageIdxs.push(...[ELLIPSIS_IDX, maxIdx]);
36
- } else if (maxIdx - pageIdxs[pageIdxs.length - 1] === 1) {
37
- pageIdxs.push(maxIdx);
38
- }
39
-
40
- // backward
41
- for (let i = 1; i < NUM_EXTRA_BUTTONS + 1; i++) {
42
- const newPageIdx = pageIdx - i;
43
- if (newPageIdx < minIdx) {
44
- continue;
45
- }
46
- pageIdxs.unshift(newPageIdx);
47
- }
48
- if (pageIdxs[0] - minIdx > 1) {
49
- pageIdxs.unshift(...[minIdx, ELLIPSIS_IDX]);
50
- } else if (pageIdxs[0] - minIdx === 1) {
51
- pageIdxs.unshift(minIdx);
52
- }
53
- return pageIdxs;
54
- }
55
- </script>
56
-
57
- {#if numTotalPages > 1}
58
- <nav>
59
- <ul
60
- class="flex select-none items-center justify-between space-x-2 text-gray-700 sm:justify-center dark:text-gray-300 {classNames}"
61
- >
62
- <li>
63
- <PaginationArrow
64
- href={getHref($page.url, { newKeys: { p: (pageIndex - 1).toString() } })}
65
- direction="previous"
66
- isDisabled={pageIndex - 1 < 0}
67
- />
68
- </li>
69
- {#each pageIndexes as pageIdx}
70
- <li class="hidden sm:block">
71
- <a
72
- class="
73
- rounded-lg px-2.5 py-1
74
- {pageIndex === pageIdx
75
- ? 'bg-gray-50 font-semibold ring-1 ring-inset ring-gray-200 dark:bg-gray-800 dark:text-yellow-500 dark:ring-gray-700'
76
- : ''}
77
- "
78
- class:pointer-events-none={pageIdx === ELLIPSIS_IDX || pageIndex === pageIdx}
79
- href={getHref($page.url, { newKeys: { p: pageIdx.toString() } })}
80
- >
81
- {pageIdx === ELLIPSIS_IDX ? "..." : pageIdx + 1}
82
- </a>
83
- </li>
84
- {/each}
85
- <li>
86
- <PaginationArrow
87
- href={getHref($page.url, { newKeys: { p: (pageIndex + 1).toString() } })}
88
- direction="next"
89
- isDisabled={pageIndex + 1 >= numTotalPages}
90
- />
91
- </li>
92
- </ul>
93
- </nav>
94
- {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/PaginationArrow.svelte DELETED
@@ -1,23 +0,0 @@
1
- <script lang="ts">
2
- import CarbonCaretLeft from "~icons/carbon/caret-left";
3
- import CarbonCaretRight from "~icons/carbon/caret-right";
4
-
5
- export let href: string;
6
- export let direction: "next" | "previous";
7
- export let isDisabled = false;
8
- </script>
9
-
10
- <a
11
- class="flex items-center rounded-lg px-2.5 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 {isDisabled
12
- ? 'pointer-events-none opacity-50'
13
- : ''}"
14
- {href}
15
- >
16
- {#if direction === "previous"}
17
- <CarbonCaretLeft classNames="mr-1.5" />
18
- Previous
19
- {:else}
20
- Next
21
- <CarbonCaretRight classNames="ml-1.5" />
22
- {/if}
23
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/SettingsModal.svelte ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from "svelte";
3
+
4
+ import Modal from "$lib/components/Modal.svelte";
5
+ import CarbonClose from "~icons/carbon/close";
6
+ import Switch from "$lib/components/Switch.svelte";
7
+ import { enhance } from "$app/forms";
8
+ import { base } from "$app/paths";
9
+ import { PUBLIC_APP_DATA_SHARING } from "$env/static/public";
10
+ import type { Model } from "$lib/types/Model";
11
+ import type { LayoutData } from "../../routes/$types";
12
+
13
+ export let settings: LayoutData["settings"];
14
+ export let models: Array<Model>;
15
+
16
+ let shareConversationsWithModelAuthors = settings.shareConversationsWithModelAuthors;
17
+ let isConfirmingDeletion = false;
18
+
19
+ const dispatch = createEventDispatcher<{ close: void }>();
20
+ </script>
21
+
22
+ <Modal on:close>
23
+ <div class="flex w-full flex-col gap-5 p-6">
24
+ <div class="flex items-start justify-between text-xl font-semibold text-gray-800">
25
+ <h2>Settings</h2>
26
+ <button type="button" class="group" on:click={() => dispatch("close")}>
27
+ <CarbonClose class="text-gray-900 group-hover:text-gray-500" />
28
+ </button>
29
+ </div>
30
+ <form
31
+ class="flex flex-col gap-5"
32
+ use:enhance={() => {
33
+ dispatch("close");
34
+ }}
35
+ method="post"
36
+ action="{base}/settings"
37
+ >
38
+ {#if PUBLIC_APP_DATA_SHARING}
39
+ <label class="flex cursor-pointer select-none items-center gap-2 text-gray-500">
40
+ {#each Object.entries(settings).filter(([k]) => !(k === "shareConversationsWithModelAuthors" || k === "customPrompts")) as [key, val]}
41
+ <input type="hidden" name={key} value={val} />
42
+ {/each}
43
+ <input
44
+ type="hidden"
45
+ name="customPrompts"
46
+ value={JSON.stringify(settings.customPrompts)}
47
+ />
48
+ <Switch
49
+ name="shareConversationsWithModelAuthors"
50
+ bind:checked={shareConversationsWithModelAuthors}
51
+ />
52
+ Share conversations with model authors
53
+ </label>
54
+
55
+ <p class="text-gray-800">
56
+ Sharing your data will help improve the training data and make open models better over
57
+ time.
58
+ </p>
59
+ <p class="text-gray-800">
60
+ You can change this setting at any time, it applies to all your conversations.
61
+ </p>
62
+ <div>
63
+ <p class="text-gray-800">Read more about model authors:</p>
64
+ <ul class="list-inside list-disc">
65
+ {#each models as model}
66
+ <li class="list-item">
67
+ <a
68
+ href={model["websiteUrl"]}
69
+ target="_blank"
70
+ rel="noreferrer"
71
+ class="underline decoration-gray-300 hover:decoration-gray-700">{model["name"]}</a
72
+ >
73
+ </li>
74
+ {/each}
75
+ </ul>
76
+ </div>
77
+ {/if}
78
+ <label class="flex cursor-pointer select-none items-center gap-2 text-sm text-gray-500">
79
+ <input
80
+ type="checkbox"
81
+ name="hideEmojiOnSidebar"
82
+ bind:checked={settings.hideEmojiOnSidebar}
83
+ />
84
+ Hide emoticons in conversation topics
85
+ </label>
86
+ <form
87
+ method="post"
88
+ action="{base}/conversations?/delete"
89
+ on:submit|preventDefault={() => (isConfirmingDeletion = true)}
90
+ >
91
+ <button type="submit" class="underline decoration-gray-300 hover:decoration-gray-700">
92
+ Delete all conversations
93
+ </button>
94
+ </form>
95
+ <button
96
+ type="submit"
97
+ class="mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
98
+ >
99
+ Apply
100
+ </button>
101
+ </form>
102
+
103
+ {#if isConfirmingDeletion}
104
+ <Modal on:close={() => (isConfirmingDeletion = false)}>
105
+ <form
106
+ use:enhance={() => {
107
+ dispatch("close");
108
+ }}
109
+ method="post"
110
+ action="{base}/conversations?/delete"
111
+ class="flex w-full flex-col gap-5 p-6"
112
+ >
113
+ <div class="flex items-start justify-between text-xl font-semibold text-gray-800">
114
+ <h2>Are you sure?</h2>
115
+ <button type="button" class="group" on:click={() => (isConfirmingDeletion = false)}>
116
+ <CarbonClose class="text-gray-900 group-hover:text-gray-500" />
117
+ </button>
118
+ </div>
119
+ <p class="text-gray-800">
120
+ This action will delete all your conversations. This cannot be undone.
121
+ </p>
122
+ <button
123
+ type="submit"
124
+ class="mt-2 rounded-full bg-red-700 px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
125
+ >
126
+ Confirm deletion
127
+ </button>
128
+ </form>
129
+ </Modal>
130
+ {/if}
131
+ </div>
132
+ </Modal>
src/lib/components/Switch.svelte CHANGED
@@ -10,7 +10,9 @@
10
  aria-label="switch"
11
  role="switch"
12
  tabindex="0"
13
- class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-gray-300 p-1 shadow-inner ring-gray-400 transition-all peer-checked:bg-blue-600 peer-focus-visible:ring peer-focus-visible:ring-offset-1 hover:bg-gray-400 dark:bg-gray-600 peer-checked:[&>div]:translate-x-3.5"
 
 
14
  >
15
  <div class="h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-all" />
16
  </div>
 
10
  aria-label="switch"
11
  role="switch"
12
  tabindex="0"
13
+ on:click
14
+ on:keypress
15
+ class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full bg-gray-300 p-1 shadow-inner ring-gray-400 transition-all peer-checked:bg-blue-600 peer-focus-visible:ring peer-focus-visible:ring-offset-1 hover:bg-gray-400 dark:bg-gray-600 peer-checked:[&>div]:translate-x-3.5"
16
  >
17
  <div class="h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-all" />
18
  </div>
src/lib/components/SystemPromptModal.svelte CHANGED
@@ -12,7 +12,7 @@
12
  type="button"
13
  class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 px-3 py-1 text-xs text-gray-500 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
14
  on:click={() => (isOpen = !isOpen)}
15
- on:keydown={(e) => e.key === "Enter" && (isOpen = !isOpen)}
16
  >
17
  <CarbonBlockchain class="text-xxs" /> Using Custom System Prompt
18
  </button>
 
12
  type="button"
13
  class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 px-3 py-1 text-xs text-gray-500 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
14
  on:click={() => (isOpen = !isOpen)}
15
+ on:keypress={(e) => e.key === "Enter" && (isOpen = !isOpen)}
16
  >
17
  <CarbonBlockchain class="text-xxs" /> Using Custom System Prompt
18
  </button>
src/lib/components/TokensCounter.svelte DELETED
@@ -1,44 +0,0 @@
1
- <script lang="ts">
2
- import type { Model } from "$lib/types/Model";
3
- import { getTokenizer } from "$lib/utils/getTokenizer";
4
- import type { PreTrainedTokenizer } from "@xenova/transformers";
5
-
6
- export let classNames = "";
7
- export let prompt = "";
8
- export let modelTokenizer: Exclude<Model["tokenizer"], undefined>;
9
- export let truncate: number | undefined = undefined;
10
-
11
- let tokenizer: PreTrainedTokenizer | undefined = undefined;
12
-
13
- async function tokenizeText(_prompt: string) {
14
- if (!tokenizer) {
15
- return;
16
- }
17
- const { input_ids } = await tokenizer(_prompt);
18
- return input_ids.size;
19
- }
20
-
21
- $: (async () => {
22
- tokenizer = await getTokenizer(modelTokenizer);
23
- })();
24
- </script>
25
-
26
- {#if tokenizer}
27
- {#await tokenizeText(prompt) then nTokens}
28
- {@const exceedLimit = nTokens > (truncate || Infinity)}
29
- <div class={classNames}>
30
- <p
31
- class="peer text-sm {exceedLimit
32
- ? 'text-red-500 opacity-100'
33
- : 'opacity-60 hover:opacity-90'}"
34
- >
35
- {nTokens}{truncate ? `/${truncate}` : ""}
36
- </p>
37
- <div
38
- class="invisible absolute -top-6 right-0 whitespace-nowrap rounded bg-black px-1 text-sm text-white peer-hover:visible"
39
- >
40
- Tokens usage
41
- </div>
42
- </div>
43
- {/await}
44
- {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/WebSearchToggle.svelte CHANGED
@@ -9,13 +9,13 @@
9
  <div
10
  class="flex h-8 cursor-pointer select-none items-center gap-2 rounded-lg border bg-white p-1.5 shadow-sm hover:shadow-none dark:border-gray-800 dark:bg-gray-900"
11
  on:click={toggle}
12
- on:keydown={toggle}
13
  aria-checked={$webSearchParameters.useSearch}
14
  aria-label="web search toggle"
15
  role="switch"
16
  tabindex="0"
17
  >
18
- <Switch name="useSearch" bind:checked={$webSearchParameters.useSearch} on:click on:keydown />
19
  <div class="whitespace-nowrap text-sm text-gray-800 dark:text-gray-200">Search web</div>
20
  <div class="group relative w-max">
21
  <CarbonInformation class="text-xs text-gray-500" />
 
9
  <div
10
  class="flex h-8 cursor-pointer select-none items-center gap-2 rounded-lg border bg-white p-1.5 shadow-sm hover:shadow-none dark:border-gray-800 dark:bg-gray-900"
11
  on:click={toggle}
12
+ on:keypress={toggle}
13
  aria-checked={$webSearchParameters.useSearch}
14
  aria-label="web search toggle"
15
  role="switch"
16
  tabindex="0"
17
  >
18
+ <Switch name="useSearch" bind:checked={$webSearchParameters.useSearch} on:click on:keypress />
19
  <div class="whitespace-nowrap text-sm text-gray-800 dark:text-gray-200">Search web</div>
20
  <div class="group relative w-max">
21
  <CarbonInformation class="text-xs text-gray-500" />
src/lib/components/chat/AssistantIntroduction.svelte DELETED
@@ -1,160 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher } from "svelte";
3
- import IconGear from "~icons/bi/gear-fill";
4
- import { base } from "$app/paths";
5
- import type { Assistant } from "$lib/types/Assistant";
6
- import { formatUserCount } from "$lib/utils/formatUserCount";
7
- import IconInternet from "../icons/IconInternet.svelte";
8
- import CarbonExport from "~icons/carbon/export";
9
- import CarbonCheckmark from "~icons/carbon/checkmark";
10
- import CarbonUserMultiple from "~icons/carbon/user-multiple";
11
-
12
- import { share } from "$lib/utils/share";
13
- import { env as envPublic } from "$env/dynamic/public";
14
- import { page } from "$app/stores";
15
-
16
- export let assistant: Pick<
17
- Assistant,
18
- | "avatar"
19
- | "name"
20
- | "rag"
21
- | "dynamicPrompt"
22
- | "modelId"
23
- | "createdByName"
24
- | "exampleInputs"
25
- | "_id"
26
- | "description"
27
- | "userCount"
28
- >;
29
-
30
- const dispatch = createEventDispatcher<{ message: string }>();
31
-
32
- $: hasRag =
33
- assistant?.rag?.allowAllDomains ||
34
- (assistant?.rag?.allowedDomains?.length ?? 0) > 0 ||
35
- (assistant?.rag?.allowedLinks?.length ?? 0) > 0 ||
36
- assistant?.dynamicPrompt;
37
-
38
- const prefix =
39
- envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || $page.url.origin}${base}`;
40
-
41
- $: shareUrl = `${prefix}/assistant/${assistant?._id}`;
42
-
43
- let isCopied = false;
44
- </script>
45
-
46
- <div class="flex h-full w-full flex-col content-center items-center justify-center pb-52">
47
- <div
48
- class="relative mt-auto rounded-2xl bg-gray-100 text-gray-600 dark:border-gray-800 dark:bg-gray-800/60 dark:text-gray-300"
49
- >
50
- <div
51
- class="mt-3 flex min-w-[80dvw] items-center gap-4 p-4 pr-1 sm:min-w-[440px] md:p-8 xl:gap-8"
52
- >
53
- {#if assistant.avatar}
54
- <img
55
- src={`${base}/settings/assistants/${assistant._id.toString()}/avatar.jpg?hash=${
56
- assistant.avatar
57
- }`}
58
- alt="avatar"
59
- class="size-16 flex-none rounded-full object-cover max-sm:self-start md:size-32"
60
- />
61
- {:else}
62
- <div
63
- class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 max-sm:self-start sm:text-4xl md:size-32 dark:bg-gray-600"
64
- >
65
- {assistant?.name[0]}
66
- </div>
67
- {/if}
68
-
69
- <div class="flex h-full flex-col gap-2 text-balance">
70
- <p class="-mb-1">Assistant</p>
71
-
72
- <p class="text-xl font-bold sm:text-2xl">{assistant.name}</p>
73
- {#if assistant.description}
74
- <p class="line-clamp-6 text-sm text-gray-500 dark:text-gray-400">
75
- {assistant.description}
76
- </p>
77
- {/if}
78
-
79
- {#if hasRag}
80
- <div
81
- class="flex h-5 w-fit items-center gap-1 rounded-full bg-blue-500/10 pl-1 pr-2 text-xs"
82
- title="This assistant uses the websearch."
83
- >
84
- <IconInternet classNames="text-sm text-blue-600" />
85
- Has internet access
86
- </div>
87
- {/if}
88
-
89
- {#if assistant.createdByName}
90
- <p class="pt-1 text-sm text-gray-400 dark:text-gray-500">
91
- Created by
92
- <a class="hover:underline" href="{base}/assistants?user={assistant.createdByName}">
93
- {assistant.createdByName}
94
- </a>
95
- {#if assistant.userCount && assistant.userCount > 1}
96
- <span class="mx-1">·</span>
97
- <div
98
- class="inline-flex items-baseline gap-1 text-sm text-gray-400 dark:text-gray-500"
99
- title="Number of users"
100
- >
101
- <CarbonUserMultiple class="text-xxs" />{formatUserCount(assistant.userCount)} users
102
- </div>
103
- {/if}
104
- </p>
105
- {/if}
106
- </div>
107
- </div>
108
-
109
- <div class="absolute right-3 top-3 md:right-4 md:top-4">
110
- <div class="flex flex-row items-center gap-1">
111
- <button
112
- class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner max-sm:px-1.5 md:text-sm dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800"
113
- on:click={() => {
114
- if (!isCopied) {
115
- share(shareUrl, assistant.name);
116
- isCopied = true;
117
- setTimeout(() => {
118
- isCopied = false;
119
- }, 2000);
120
- }
121
- }}
122
- >
123
- {#if isCopied}
124
- <CarbonCheckmark class="text-xxs text-green-600 max-sm:text-xs" />
125
- <span class="text-green-600 max-sm:hidden"> Link copied </span>
126
- {:else}
127
- <CarbonExport class="text-xxs max-sm:text-xs" />
128
- <span class="max-sm:hidden"> Share </span>
129
- {/if}
130
- </button>
131
- <a
132
- href="{base}/settings/assistants/{assistant._id.toString()}"
133
- class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner md:text-sm dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800"
134
- ><IconGear class="text-xxs" />Settings</a
135
- >
136
- </div>
137
- </div>
138
- </div>
139
- {#if assistant.exampleInputs}
140
- <div class="mx-auto mt-auto w-full gap-8 sm:-mb-8">
141
- <div class="md:col-span-2 md:mt-6">
142
- <div
143
- class="grid grid-cols-1 gap-3 {assistant.exampleInputs.length > 1
144
- ? 'md:grid-cols-2'
145
- : ''}"
146
- >
147
- {#each assistant.exampleInputs as example}
148
- <button
149
- type="button"
150
- class="truncate whitespace-nowrap rounded-xl border bg-gray-50 px-3 py-2 text-left text-smd text-gray-600 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
151
- on:click={() => dispatch("message", example)}
152
- >
153
- {example}
154
- </button>
155
- {/each}
156
- </div>
157
- </div>
158
- </div>
159
- {/if}
160
- </div>