Spaces:
Paused
Paused
Orca-2-13b
#323
by
Khalid776826
- opened
This view is limited to 50 files because it contains too many changes.
See the raw diff here.
- .dockerignore +0 -3
- .env +38 -86
- .env.template +105 -223
- .eslintrc.cjs +0 -1
- .github/release.yml +0 -16
- .github/workflows/build-image.yml +0 -125
- .github/workflows/deploy-release.yml +7 -8
- .github/workflows/deploy-staging.yml +2 -2
- .github/workflows/lint-and-test.yml +1 -1
- .gitignore +3 -4
- .vscode/settings.json +1 -1
- Dockerfile +9 -65
- PRIVACY.md +4 -10
- PROMPTS.md +0 -24
- README.md +47 -441
- .env.ci → conf/.env.ci +0 -0
- entrypoint.sh +0 -19
- package-lock.json +0 -0
- package.json +12 -34
- scripts/populate.ts +0 -269
- scripts/updateProdEnv.ts +4 -12
- src/ambient.d.ts +0 -4
- src/app.d.ts +0 -5
- src/hooks.server.ts +31 -131
- src/lib/actions/clickOutside.ts +0 -18
- src/lib/assistantStats/refresh-assistants-counts.ts +0 -90
- src/lib/buildPrompt.ts +78 -19
- src/lib/components/AnnouncementBanner.svelte +1 -1
- src/lib/components/AssistantSettings.svelte +0 -593
- src/lib/components/ContinueBtn.svelte +0 -13
- src/lib/components/CopyToClipBoardBtn.svelte +7 -7
- src/lib/components/DisclaimerModal.svelte +33 -28
- src/lib/components/ExpandNavigation.svelte +0 -14
- src/lib/components/HoverTooltip.svelte +0 -12
- src/lib/components/LoginModal.svelte +16 -21
- src/lib/components/MobileNav.svelte +8 -11
- src/lib/components/Modal.svelte +1 -4
- src/lib/components/ModelCardMetadata.svelte +2 -8
- src/lib/components/ModelsModal.svelte +153 -0
- src/lib/components/NavConversationItem.svelte +9 -25
- src/lib/components/NavMenu.svelte +37 -81
- src/lib/components/OpenWebSearchResults.svelte +41 -47
- src/lib/components/Pagination.svelte +0 -94
- src/lib/components/PaginationArrow.svelte +0 -23
- src/lib/components/SettingsModal.svelte +132 -0
- src/lib/components/Switch.svelte +3 -1
- src/lib/components/SystemPromptModal.svelte +1 -1
- src/lib/components/TokensCounter.svelte +0 -44
- src/lib/components/WebSearchToggle.svelte +2 -2
- 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 |
-
|
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 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
"
|
83 |
-
"
|
84 |
-
|
85 |
-
"
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
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": "
|
112 |
-
"linkTitle": "
|
113 |
-
"linkHref": "https://huggingface.co/
|
114 |
}
|
115 |
]`
|
116 |
|
117 |
-
PUBLIC_APPLE_APP_ID=#1234567890 / Leave empty to disable
|
118 |
-
|
119 |
PARQUET_EXPORT_DATASET=
|
120 |
PARQUET_EXPORT_HF_TOKEN=
|
121 |
-
|
122 |
|
123 |
-
|
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 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
"
|
13 |
-
"
|
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.
|
133 |
"top_p": 0.95,
|
134 |
-
"repetition_penalty": 1,
|
135 |
"top_k": 50,
|
136 |
-
"truncate":
|
137 |
-
"max_new_tokens":
|
138 |
-
"stop": ["<|im_end|>"]
|
139 |
}
|
140 |
},
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
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 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
|
|
179 |
"parameters": {
|
180 |
-
"temperature": 0.
|
181 |
"top_p": 0.95,
|
182 |
"repetition_penalty": 1.2,
|
183 |
"top_k": 50,
|
184 |
-
"truncate":
|
185 |
-
"max_new_tokens":
|
186 |
-
|
187 |
},
|
188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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": "
|
203 |
-
"
|
204 |
-
"description"
|
205 |
-
"
|
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>{{
|
210 |
"parameters": {
|
211 |
-
"
|
|
|
|
|
|
|
|
|
212 |
"max_new_tokens": 1024,
|
213 |
-
"
|
214 |
},
|
215 |
"promptExamples": [
|
216 |
{
|
@@ -225,47 +122,41 @@ MODELS=`[
|
|
225 |
}
|
226 |
]
|
227 |
},
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
"
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
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 |
-
|
280 |
-
PUBLIC_APP_DATA_SHARING=0
|
281 |
PUBLIC_APP_DISCLAIMER=1
|
282 |
|
283 |
-
|
284 |
-
|
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 |
-
|
290 |
-
ENABLE_ASSISTANTS_RAG=true
|
291 |
-
REQUIRE_FEATURED_ASSISTANTS=true
|
292 |
-
EXPOSE_API=true
|
293 |
|
294 |
-
|
295 |
-
|
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 |
-
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
44 |
-
run: git push https://nsarrazin:$
|
|
|
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 |
-
|
24 |
-
run: git push https://nsarrazin:$
|
|
|
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
|
|
|
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 |
-
|
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":
|
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 |
-
|
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
|
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 |
-
|
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 |
-
|
83 |
-
COPY --from=builder --chown=1000 /app/build /app/build
|
84 |
-
COPY --from=builder --chown=1000 /app/node_modules /app/node_modules
|
85 |
|
86 |
-
|
|
|
|
|
87 |
|
88 |
-
CMD
|
|
|
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:
|
4 |
|
5 |
Users of HuggingChat are authenticated through their HF user account.
|
6 |
|
7 |
-
|
8 |
|
9 |
-
|
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 |
-
|
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. [
|
26 |
-
5. [
|
27 |
-
6. [
|
28 |
-
7. [Deploying to a HF Space](#deploying-to-a-hf-space)
|
29 |
-
8. [Building](#building)
|
30 |
|
31 |
-
##
|
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 `
|
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 |
-
|
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
|
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/))
|
170 |
|
171 |
-
You can also simply enable the local
|
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": "
|
181 |
-
"
|
182 |
-
"description": "
|
183 |
-
"websiteUrl": "https://
|
184 |
-
"
|
185 |
-
"
|
186 |
-
"
|
187 |
-
|
188 |
-
|
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
|
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
|
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 |
-
|
667 |
-
|
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 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
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.
|
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.
|
24 |
-
"@sveltejs/kit": "^1.
|
25 |
"@tailwindcss/typography": "^0.5.9",
|
26 |
-
"@types/express": "^4.17.21",
|
27 |
"@types/jsdom": "^21.1.1",
|
28 |
-
"@types/
|
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 |
-
"
|
37 |
"prettier": "^2.8.0",
|
38 |
"prettier-plugin-svelte": "^2.10.1",
|
39 |
"prettier-plugin-tailwindcss": "^0.2.7",
|
40 |
-
"
|
41 |
-
"svelte": "^4.
|
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.
|
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 |
-
"@
|
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 |
-
"
|
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.
|
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
|
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
|
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 |
-
|
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 ${
|
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 {
|
2 |
-
import {
|
3 |
-
import
|
|
|
|
|
|
|
|
|
4 |
import { collections } from "$lib/server/database";
|
5 |
import { base } from "$app/paths";
|
6 |
-
import {
|
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 |
-
|
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 |
-
|
56 |
-
message: "An error occurred",
|
57 |
-
errorId,
|
58 |
-
};
|
59 |
-
};
|
60 |
|
61 |
-
|
62 |
-
logger.debug({
|
63 |
-
locals: event.locals,
|
64 |
-
url: event.url.pathname,
|
65 |
-
params: event.params,
|
66 |
-
request: event.request,
|
67 |
-
});
|
68 |
|
69 |
-
if (
|
70 |
-
|
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 |
-
|
133 |
-
|
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 |
-
|
154 |
-
|
155 |
-
|
|
|
156 |
|
157 |
-
|
158 |
-
|
159 |
-
|
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 |
-
!
|
170 |
requiresUser &&
|
171 |
-
!((
|
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 |
-
!!
|
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%",
|
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 |
-
|
|
|
|
|
5 |
model: BackendModel;
|
6 |
-
|
|
|
|
|
|
|
|
|
7 |
|
8 |
export async function buildPrompt({
|
9 |
messages,
|
10 |
model,
|
|
|
11 |
preprompt,
|
12 |
-
|
13 |
}: buildPromptOptions): Promise<string> {
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
return acc.slice(0, acc.length - curr.length);
|
27 |
-
}
|
28 |
-
return acc;
|
29 |
-
}, prompt.trimEnd());
|
30 |
}
|
31 |
|
32 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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="
|
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-
|
|
|
|
|
|
|
39 |
title={"Copy to clipboard"}
|
40 |
type="button"
|
41 |
on:click
|
42 |
on:click={handleClick}
|
43 |
>
|
44 |
-
<
|
45 |
-
<
|
46 |
-
<IconCopy classNames="dark:text-gray-700 text-gray-200" />
|
47 |
-
</slot>
|
48 |
-
|
49 |
<Tooltip classNames={isSuccess ? "opacity-100" : "opacity-0"} />
|
50 |
-
</
|
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 {
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
7 |
-
import {
|
8 |
-
import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
|
9 |
import Logo from "./icons/Logo.svelte";
|
10 |
|
11 |
-
|
12 |
</script>
|
13 |
|
14 |
<Modal>
|
15 |
<div
|
16 |
-
class="
|
17 |
>
|
18 |
<h2 class="flex items-center text-2xl font-semibold text-gray-800">
|
19 |
<Logo classNames="mr-1" />
|
20 |
-
{
|
21 |
</h2>
|
22 |
|
23 |
<p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
|
24 |
-
{
|
25 |
</p>
|
26 |
|
27 |
<p class="text-sm text-gray-500">
|
28 |
-
|
|
|
29 |
</p>
|
30 |
|
31 |
<div class="flex w-full flex-col items-center gap-2">
|
32 |
{#if $page.data.guestMode || !$page.data.loginEnabled}
|
33 |
-
<
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
}
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
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 {
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
7 |
-
import {
|
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="
|
17 |
>
|
18 |
<h2 class="flex items-center text-2xl font-semibold text-gray-800">
|
19 |
<Logo classNames="mr-1" />
|
20 |
-
{
|
21 |
</h2>
|
22 |
-
<p class="text-
|
23 |
-
{
|
24 |
</p>
|
25 |
-
<p class="
|
26 |
-
You have reached the guest message limit,
|
27 |
-
|
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
|
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
|
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-
|
35 |
>
|
36 |
<button
|
37 |
type="button"
|
38 |
-
class="-ml-3 flex
|
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 |
-
|
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
|
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
|
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 |
-
|
46 |
-
|
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:
|
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-
|
29 |
$page.params.id
|
30 |
? 'bg-gray-100 dark:bg-gray-700'
|
31 |
: ''}"
|
32 |
>
|
33 |
-
<div class="flex
|
34 |
{#if confirmDelete}
|
35 |
-
<span class="
|
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={() =>
|
|
|
|
|
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 {
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
{
|
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
|
67 |
>
|
68 |
-
{#each
|
69 |
-
{
|
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
|
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-
|
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
|
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-
|
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-
|
114 |
>
|
115 |
Theme
|
116 |
</button>
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
>{nModels}</span
|
126 |
-
>
|
127 |
-
</a>
|
128 |
-
{/if}
|
129 |
-
{#if $page.data.enableAssistants}
|
130 |
<a
|
131 |
-
href="
|
132 |
-
|
|
|
|
|
133 |
>
|
134 |
-
|
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-
|
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 |
-
|
12 |
-
|
13 |
-
$:
|
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
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
<
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
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 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
}
|
109 |
|
110 |
-
.
|
111 |
-
|
112 |
-
animation:
|
|
|
113 |
}
|
114 |
|
115 |
-
|
116 |
-
|
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 |
-
|
|
|
|
|
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:
|
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:
|
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:
|
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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|