Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
fa598b59
Unverified
Commit
fa598b59
authored
Jan 04, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Jan 04, 2024
Browse files
Merge branch 'main' into rag
parents
becb7b1d
fe8c2244
Changes
31
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1021 additions
and
28 deletions
+1021
-28
src/lib/utils/index.ts
src/lib/utils/index.ts
+16
-0
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+5
-1
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+27
-14
src/routes/(app)/c/[id]/+page.svelte
src/routes/(app)/c/[id]/+page.svelte
+13
-10
src/routes/(app)/modelfiles/+page.svelte
src/routes/(app)/modelfiles/+page.svelte
+25
-1
src/routes/(app)/prompts/+page.svelte
src/routes/(app)/prompts/+page.svelte
+309
-0
src/routes/(app)/prompts/create/+page.svelte
src/routes/(app)/prompts/create/+page.svelte
+222
-0
src/routes/(app)/prompts/edit/+page.svelte
src/routes/(app)/prompts/edit/+page.svelte
+221
-0
src/routes/+layout.svelte
src/routes/+layout.svelte
+1
-0
static/themes/rosepine-dawn.css
static/themes/rosepine-dawn.css
+140
-0
static/themes/rosepine.css
static/themes/rosepine.css
+42
-2
No files found.
src/lib/utils/index.ts
View file @
fa598b59
...
...
@@ -111,3 +111,19 @@ export const checkVersion = (required, current) => {
caseFirst
:
'
upper
'
})
<
0
;
};
export
const
findWordIndices
=
(
text
)
=>
{
const
regex
=
/
\[([^\]]
+
)\]
/g
;
let
matches
=
[];
let
match
;
while
((
match
=
regex
.
exec
(
text
))
!==
null
)
{
matches
.
push
({
word
:
match
[
1
],
startIndex
:
match
.
index
,
endIndex
:
regex
.
lastIndex
-
1
});
}
return
matches
;
};
src/routes/(app)/+layout.svelte
View file @
fa598b59
...
...
@@ -9,10 +9,11 @@
import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
import { getModelfiles } from '$lib/apis/modelfiles';
import { getPrompts } from '$lib/apis/prompts';
import { getOpenAIModels } from '$lib/apis/openai';
import { user, showSettings, settings, models, modelfiles } from '$lib/stores';
import { user, showSettings, settings, models, modelfiles
, prompts
} from '$lib/stores';
import { OLLAMA_API_BASE_URL, REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
...
...
@@ -101,6 +102,9 @@
console.log();
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
await modelfiles.set(await getModelfiles(localStorage.token));
await prompts.set(await getPrompts(localStorage.token));
console.log($modelfiles);
modelfiles.subscribe(async () => {
...
...
src/routes/(app)/+page.svelte
View file @
fa598b59
...
...
@@ -6,7 +6,7 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { models, modelfiles, user, settings, chats, chatId } from '$lib/stores';
import { models, modelfiles, user, settings, chats, chatId
, config
} from '$lib/stores';
import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
...
...
@@ -90,9 +90,18 @@
messages: {},
currentId: null
};
selectedModels = $page.url.searchParams.get('models')
? $page.url.searchParams.get('models')?.split(',')
: $settings.models ?? [''];
console.log($config);
if ($page.url.searchParams.get('models')) {
selectedModels = $page.url.searchParams.get('models')?.split(',');
} else if ($settings?.models) {
selectedModels = $settings?.models;
} else if ($config?.default_models) {
selectedModels = $config?.default_models.split(',');
} else {
selectedModels = [''];
}
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
settings.set({
...
...
@@ -109,10 +118,14 @@
await Promise.all(
selectedModels.map(async (model) => {
console.log(model);
if ($models.filter((m) => m.name === model)[0].external) {
const modelTag = $models.filter((m) => m.name === model).at(0);
if (modelTag?.external) {
await sendPromptOpenAI(model, prompt, parentId, _chatId);
} else {
} else
if (modelTag)
{
await sendPromptOllama(model, prompt, parentId, _chatId);
} else {
toast.error(`Model ${model} not found`);
}
})
);
...
...
@@ -379,13 +392,13 @@
}
: { content: message.content })
})),
seed: $settings.options.seed ?? undefined,
stop: $settings.options.stop ?? undefined,
temperature: $settings.options.temperature ?? undefined,
top_p: $settings.options.top_p ?? undefined,
num_ctx: $settings.options.num_ctx ?? undefined,
frequency_penalty: $settings.options.repeat_penalty ?? undefined,
max_tokens: $settings.options.num_predict ?? undefined
seed: $settings
?
.options
?
.seed ?? undefined,
stop: $settings
?
.options
?
.stop ?? undefined,
temperature: $settings
?
.options
?
.temperature ?? undefined,
top_p: $settings
?
.options
?
.top_p ?? undefined,
num_ctx: $settings
?
.options
?
.num_ctx ?? undefined,
frequency_penalty: $settings
?
.options
?
.repeat_penalty ?? undefined,
max_tokens: $settings
?
.options
?
.num_predict ?? undefined
})
}
).catch((err) => {
...
...
@@ -584,7 +597,7 @@
const title = await generateTitle(
$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
localStorage.token,
selectedModels[0],
$settings?.titleAutoGenerateModel ??
selectedModels[0],
userPrompt
);
...
...
src/routes/(app)/c/[id]/+page.svelte
View file @
fa598b59
...
...
@@ -136,17 +136,20 @@
await Promise.all(
selectedModels.map(async (model) => {
console.log(model);
if ($models.filter((m) => m.name === model)[0].external) {
const modelTag = $models.filter((m) => m.name === model).at(0);
if (modelTag?.external) {
await sendPromptOpenAI(model, prompt, parentId, _chatId);
} else {
} else
if (modelTag)
{
await sendPromptOllama(model, prompt, parentId, _chatId);
} else {
toast.error(`Model ${model} not found`);
}
})
);
await chats.set(await getChatList(localStorage.token));
};
const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
// Create response message
let responseMessageId = uuidv4();
...
...
@@ -406,13 +409,13 @@
}
: { content: message.content })
})),
seed: $settings.options.seed ?? undefined,
stop: $settings.options.stop ?? undefined,
temperature: $settings.options.temperature ?? undefined,
top_p: $settings.options.top_p ?? undefined,
num_ctx: $settings.options.num_ctx ?? undefined,
frequency_penalty: $settings.options.repeat_penalty ?? undefined,
max_tokens: $settings.options.num_predict ?? undefined
seed: $settings
?
.options
?
.seed ?? undefined,
stop: $settings
?
.options
?
.stop ?? undefined,
temperature: $settings
?
.options
?
.temperature ?? undefined,
top_p: $settings
?
.options
?
.top_p ?? undefined,
num_ctx: $settings
?
.options
?
.num_ctx ?? undefined,
frequency_penalty: $settings
?
.options
?
.repeat_penalty ?? undefined,
max_tokens: $settings
?
.options
?
.num_predict ?? undefined
})
}
).catch((err) => {
...
...
src/routes/(app)/modelfiles/+page.svelte
View file @
fa598b59
...
...
@@ -44,7 +44,7 @@
const url = 'https://ollamahub.com';
const tab = await window.open(`${url}/create`, '_blank');
const tab = await window.open(`${url}/
modelfiles/
create`, '_blank');
window.addEventListener(
'message',
(event) => {
...
...
@@ -254,6 +254,30 @@
</svg>
</div>
</button>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
saveModelfiles($modelfiles);
}}
>
<div class=" self-center mr-2 font-medium">Export Modelfiles</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3.5 h-3.5"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
</div>
{#if localModelfiles.length > 0}
...
...
src/routes/(app)/prompts/+page.svelte
0 → 100644
View file @
fa598b59
<script lang="ts">
import toast from 'svelte-french-toast';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { onMount } from 'svelte';
import { prompts } from '$lib/stores';
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
import { error } from '@sveltejs/kit';
let importFiles = '';
let query = '';
const sharePrompt = async (prompt) => {
toast.success('Redirecting you to OllamaHub');
const url = 'https://ollamahub.com';
const tab = await window.open(`${url}/prompts/create`, '_blank');
window.addEventListener(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(JSON.stringify(prompt), '*');
}
},
false
);
};
const deletePrompt = async (command) => {
await deletePromptByCommand(localStorage.token, command);
await prompts.set(await getPrompts(localStorage.token));
};
</script>
<div class="min-h-screen w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class="mb-6 flex justify-between items-center">
<div class=" text-2xl font-semibold self-center">My Prompts</div>
</div>
<div class=" flex w-full space-x-2">
<div class="flex flex-1">
<div class=" self-center ml-1 mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
clip-rule="evenodd"
/>
</svg>
</div>
<input
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
bind:value={query}
placeholder="Search Prompt"
/>
</div>
<div>
<a
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
href="/prompts/create"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</a>
</div>
</div>
{#if $prompts.length === 0}
<div />
{:else}
{#each $prompts.filter((p) => query === '' || p.command.includes(query)) as prompt}
<hr class=" dark:border-gray-700 my-2.5" />
<div class=" flex space-x-4 cursor-pointer w-full mb-3">
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<a href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
<div class=" flex-1 self-center pl-5">
<div class=" font-bold">{prompt.command}</div>
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
{prompt.title}
</div>
</div>
</a>
</div>
<div class="flex flex-row space-x-1 self-center">
<a
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</a>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
sharePrompt(prompt);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
deletePrompt(prompt.command);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</div>
</div>
{/each}
{/if}
<hr class=" dark:border-gray-700 my-2.5" />
<div class=" flex justify-between w-full mb-3">
<div class="flex space-x-2">
<input
id="prompts-import-input"
bind:files={importFiles}
type="file"
accept=".json"
hidden
on:change={() => {
console.log(importFiles);
const reader = new FileReader();
reader.onload = async (event) => {
const savedPrompts = JSON.parse(event.target.result);
console.log(savedPrompts);
for (const prompt of savedPrompts) {
await createNewPrompt(
localStorage.token,
prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
prompt.title,
prompt.content
).catch((error) => {
toast.error(error);
return null;
});
}
await prompts.set(await getPrompts(localStorage.token));
};
reader.readAsText(importFiles[0]);
}}
/>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
document.getElementById('prompts-import-input')?.click();
}}
>
<div class=" self-center mr-2 font-medium">Import Prompts</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
// document.getElementById('modelfiles-import-input')?.click();
let blob = new Blob([JSON.stringify($prompts)], {
type: 'application/json'
});
saveAs(blob, `prompts-export-${Date.now()}.json`);
}}
>
<div class=" self-center mr-2 font-medium">Export Prompts</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
<!-- <button
on:click={() => {
loadDefaultPrompts();
}}
>
dd
</button> -->
</div>
</div>
<div class=" my-16">
<div class=" text-2xl font-semibold mb-6">Made by OllamaHub Community</div>
<a
class=" flex space-x-4 cursor-pointer w-full mb-3"
href="https://ollamahub.com/?type=prompts"
target="_blank"
>
<div class=" self-center w-10">
<div
class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6"
>
<path
fill-rule="evenodd"
d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
<div class=" self-center">
<div class=" font-bold">Discover a prompt</div>
<div class=" text-sm">Discover, download, and explore custom Prompts</div>
</div>
</a>
</div>
</div>
</div>
</div>
src/routes/(app)/prompts/create/+page.svelte
0 → 100644
View file @
fa598b59
<script>
import toast from 'svelte-french-toast';
import { goto } from '$app/navigation';
import { prompts } from '$lib/stores';
import { onMount, tick } from 'svelte';
import { createNewPrompt, getPrompts } from '$lib/apis/prompts';
let loading = false;
// ///////////
// Prompt
// ///////////
let title = '';
let command = '';
let content = '';
$: command = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}` : '';
const submitHandler = async () => {
loading = true;
if (validateCommandString(command)) {
const prompt = await createNewPrompt(localStorage.token, command, title, content).catch(
(error) => {
toast.error(error);
return null;
}
);
if (prompt) {
await prompts.set(await getPrompts(localStorage.token));
await goto('/prompts');
}
} else {
toast.error('Only alphanumeric characters and hyphens are allowed in the command string.');
}
loading = false;
};
const validateCommandString = (inputString) => {
// Regular expression to match only alphanumeric characters and hyphen
const regex = /^[a-zA-Z0-9-]+$/;
// Test the input string against the regular expression
return regex.test(inputString);
};
onMount(() => {
window.addEventListener('message', async (event) => {
if (
!['https://ollamahub.com', 'https://www.ollamahub.com', 'http://localhost:5173'].includes(
event.origin
)
)
return;
const prompt = JSON.parse(event.data);
console.log(prompt);
title = prompt.title;
await tick();
content = prompt.content;
command = prompt.command;
});
if (window.opener ?? false) {
window.opener.postMessage('loaded', '*');
}
});
</script>
<div class="min-h-screen w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class=" text-2xl font-semibold mb-6">My Prompts</div>
<button
class="flex space-x-1"
on:click={() => {
history.back();
}}
>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center font-medium text-sm">Back</div>
</button>
<hr class="my-3 dark:border-gray-700" />
<form
class="flex flex-col"
on:submit|preventDefault={() => {
submitHandler();
}}
>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Title*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a short title for this prompt"
bind:value={title}
required
/>
</div>
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Command*</div>
<div class="flex items-center mb-1">
<div
class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
>
/
</div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-r-lg"
placeholder="short-summary"
bind:value={command}
required
/>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Only <span class=" text-gray-600 dark:text-gray-300 font-medium"
>alphanumeric characters and hyphens</span
>
are allowed; Activate this command by typing "<span
class=" text-gray-600 dark:text-gray-300 font-medium"
>
/{command}
</span>" to chat input.
</div>
</div>
<div class="my-2">
<div class="flex w-full justify-between">
<div class=" self-center text-sm font-semibold">Prompt Content*</div>
</div>
<div class="mt-2">
<div>
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder={`Write a summary in 50 words that summarizes [topic or keyword].`}
rows="6"
bind:value={content}
required
/>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Format your variables using square brackets like this: <span
class=" text-gray-600 dark:text-gray-300 font-medium">[variable]</span
>
. Make sure to enclose them with
<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
and <span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span> .
</div>
</div>
</div>
<div class="my-2 flex justify-end">
<button
class=" text-sm px-3 py-2 transition rounded-xl {loading
? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
type="submit"
disabled={loading}
>
<div class=" self-center font-medium">Save & Create</div>
{#if loading}
<div class="ml-1.5 self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{/if}
</button>
</div>
</form>
</div>
</div>
</div>
src/routes/(app)/prompts/edit/+page.svelte
0 → 100644
View file @
fa598b59
<script>
import toast from 'svelte-french-toast';
import { goto } from '$app/navigation';
import { prompts } from '$lib/stores';
import { onMount, tick } from 'svelte';
import { getPrompts, updatePromptByCommand } from '$lib/apis/prompts';
import { page } from '$app/stores';
let loading = false;
// ///////////
// Prompt
// ///////////
let title = '';
let command = '';
let content = '';
const updateHandler = async () => {
loading = true;
if (validateCommandString(command)) {
const prompt = await updatePromptByCommand(localStorage.token, command, title, content).catch(
(error) => {
toast.error(error);
return null;
}
);
if (prompt) {
await prompts.set(await getPrompts(localStorage.token));
await goto('/prompts');
}
} else {
toast.error('Only alphanumeric characters and hyphens are allowed in the command string.');
}
loading = false;
};
const validateCommandString = (inputString) => {
// Regular expression to match only alphanumeric characters and hyphen
const regex = /^[a-zA-Z0-9-]+$/;
// Test the input string against the regular expression
return regex.test(inputString);
};
onMount(async () => {
command = $page.url.searchParams.get('command');
if (command) {
const prompt = $prompts.filter((prompt) => prompt.command === command).at(0);
if (prompt) {
console.log(prompt);
console.log(prompt.command);
title = prompt.title;
await tick();
command = prompt.command.slice(1);
content = prompt.content;
} else {
goto('/prompts');
}
} else {
goto('/prompts');
}
});
</script>
<div class="min-h-screen w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class=" text-2xl font-semibold mb-6">My Prompts</div>
<button
class="flex space-x-1"
on:click={() => {
history.back();
}}
>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center font-medium text-sm">Back</div>
</button>
<hr class="my-3 dark:border-gray-700" />
<form
class="flex flex-col"
on:submit|preventDefault={() => {
updateHandler();
}}
>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Title*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a short title for this prompt"
bind:value={title}
required
/>
</div>
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Command*</div>
<div class="flex items-center mb-1">
<div
class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
>
/
</div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border disabled:text-gray-500 dark:border-gray-600 outline-none rounded-r-lg"
placeholder="short-summary"
bind:value={command}
disabled
required
/>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Only <span class=" text-gray-600 dark:text-gray-300 font-medium"
>alphanumeric characters and hyphens</span
>
are allowed; Activate this command by typing "<span
class=" text-gray-600 dark:text-gray-300 font-medium"
>
/{command}
</span>" to chat input.
</div>
</div>
<div class="my-2">
<div class="flex w-full justify-between">
<div class=" self-center text-sm font-semibold">Prompt Content*</div>
</div>
<div class="mt-2">
<div>
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder={`Write a summary in 50 words that summarizes [topic or keyword].`}
rows="6"
bind:value={content}
required
/>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Format your variables using square brackets like this: <span
class=" text-gray-600 dark:text-gray-300 font-medium">[variable]</span
>
. Make sure to enclose them with
<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
and <span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span> .
</div>
</div>
</div>
<div class="my-2 flex justify-end">
<button
class=" text-sm px-3 py-2 transition rounded-xl {loading
? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
type="submit"
disabled={loading}
>
<div class=" self-center font-medium">Save & Update</div>
{#if loading}
<div class="ml-1.5 self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{/if}
</button>
</div>
</form>
</div>
</div>
</div>
src/routes/+layout.svelte
View file @
fa598b59
...
...
@@ -57,6 +57,7 @@
<title>Ollama</title>
<link rel="stylesheet" type="text/css" href="/themes/rosepine.css" />
<link rel="stylesheet" type="text/css" href="/themes/rosepine-dawn.css" />
</svelte:head>
{#if loaded}
...
...
static/themes/rosepine-dawn.css
0 → 100644
View file @
fa598b59
.rose-pine-dawn
*
{
color
:
#575279
!important
;
stroke
:
#d7827e
!important
;
}
.rose-pine-dawn
.app
>
*
{
background-color
:
#faf4ed
!important
;
}
.rose-pine-dawn
#nav
{
background-color
:
#fffaf3
;
}
.rose-pine-dawn
.py-2
\
.
5
.my-auto.flex.flex-col.justify-between.h-screen
{
background
:
#f2e9e1
;
}
.rose-pine-dawn
.bg-white.dark
\
:bg-gray-800
{
background
:
#f2e9e1
;
}
.rose-pine-dawn
.w-4.h-4
{
fill
:
#ebbcba
;
}
.rose-pine-dawn
#chat-textarea
{
background
:
#cecacd
;
margin
:
0.3rem
;
padding
:
0.5rem
;
}
.rose-pine-dawn
.bg-gradient-to-t.from-white.dark
\
:from-gray-800
.from-40
\
%
.pb-2
{
background
:
#f2e9e1
!important
;
padding-top
:
0.6rem
;
}
.rose-pine-dawn
.text-white.bg-gray-100.dark
\
:text-gray-800
.dark
\
:bg-gray-600
.disabled.transition.rounded-lg.p-1.mr-0
\
.
5
.w-7.h-7.self-center
{
background-color
:
#cecacd
;
transition
:
background-color
0.2s
ease-out
linear
;
}
.rose-pine-dawn
.bg-black.text-white.hover
\
:bg-gray-900
.dark
\
:bg-white
.dark
\
:text-black
.dark
\
:hover
\
:bg-gray-100
.transition.rounded-lg.p-1.mr-0
\
.
5
.w-7.h-7.self-center
{
background-color
:
#286983
;
transition
:
background-color
0.2s
ease-out
linear
;
}
.rose-pine-dawn
.bg-black.text-white.hover
\
:bg-gray-900
.dark
\
:bg-white
.dark
\
:text-black
.dark
\
:hover
\
:bg-gray-100
.transition.rounded-lg.p-1.mr-0
\
.
5
.w-7.h-7.self-center
>
*
{
fill
:
#56949f
!important
;
transition
:
fill
0.2s
ease-out
linear
;
}
.rose-pine-dawn
.w-full.flex.justify-between.rounded-md.px-3.py-2.hover
\
:bg-gray-900
.bg-gray-900.transition.whitespace-nowrap.text-ellipsis
{
background-color
:
#56526e
;
font-weight
:
bold
;
}
.rose-pine-dawn
.hover
\
:bg-gray-900:hover
{
--tw-bg-opacity
:
1
;
background-color
:
rgb
(
152
147
165
/
var
(
--tw-bg-opacity
));
}
.rose-pine-dawn
.text-xs.text-gray-700.uppercase.bg-gray-50.dark
\
:bg-gray-700
.dark
\
:text-gray-400
{
background-color
:
#403d52
;
}
.rose-pine-dawn
.scrollbar-hidden.relative.overflow-x-auto.whitespace-nowrap.svelte-3g4avz
{
border-radius
:
16px
16px
0
0
;
}
.rose-pine-dawn
.base.enter.svelte-ug60r4
{
background-color
:
#286983
;
}
.rose-pine-dawn
.message.svelte-1nauejd
{
color
:
#e0def4
!important
;
}
.rose-pine-dawn
#dropdownDots
{
background-color
:
#dfdad9
;
}
.rose-pine-dawn
.flex.py-2
\
.
5
.px-3
\
.
5
.w-full.hover
\
:bg-gray-800
.transition
:hover
{
background
:
#cecacd
;
}
.rose-pine-dawn
#dropdownDots
{
background-color
:
#dfdad9
;
}
.rose-pine-dawn
.flex.py-2
\
.
5
.px-3
\
.
5
.w-full.hover
\
:bg-gray-800
.transition
:hover
{
background
:
#cecacd
;
}
.rose-pine-dawn
.m-auto.rounded-xl.max-w-full.w-
\
[
40
rem
\
]
.mx-2.bg-gray-50.dark
\
:bg-gray-900
.shadow-3xl
{
background-color
:
#f2e9e1
;
}
.rose-pine-dawn
.w-full.rounded.p-4.text-sm.dark
\
:text-gray-300
.dark
\
:bg-gray-800
.outline-none.resize-none
{
background-color
:
#cecacd
;
}
.rose-pine-dawn
.w-full.rounded.py-2.px-4.text-sm.dark
\
:text-gray-300
.dark
\
:bg-gray-800
.outline-none.svelte-1vx7r9s
{
background-color
:
#cecacd
;
}
.rose-pine-dawn
.px-2
\
.
5
.py-2
\
.
5
.min-w-fit.rounded-lg.flex-1.md
\
:flex-none
.flex.text-right.transition.bg-gray-200.dark
\
:bg-gray-700
{
background-color
:
#dfdad9
;
}
.rose-pine-dawn
.px-2
\
.
5
.py-2
\
.
5
.min-w-fit.rounded-lg.flex-1.md
\
:flex-none
.flex.text-right.transition.hover
\
:bg-gray-300
.dark
\
:hover
\
:bg-gray-800:hover
{
background-color
:
#cecacd
;
}
.rose-pine-dawn
.px-4.py-2.bg-emerald-600.hover
\
:bg-emerald-700
.text-gray-100.transition.rounded
{
background-color
:
#56949f
;
}
.rose-pine-dawn
#chat-search
>
*
{
background-color
:
#dfdad9
!important
;
}
.rose-pine-dawn
.svelte-1ee93ns
{
--primary
:
#b4637a
!important
;
--secondary
:
#fffaf3
!important
;
}
.rose-pine-dawn
.svelte-11kvm4p
{
--primary
:
#56949f
!important
;
--secondary
:
#fffaf3
!important
;
}
static/themes/rosepine.css
View file @
fa598b59
...
...
@@ -37,13 +37,13 @@
.rose-pine
.text-white.bg-gray-100.dark
\
:text-gray-800
.dark
\
:bg-gray-600
.disabled.transition.rounded-lg.p-1.mr-0
\
.
5
.w-7.h-7.self-center
{
background-color
:
#6e6a86
;
transition
:
background
0.2s
ease-out
linear
;
transition
:
background
-color
0.2s
ease-out
linear
;
}
.rose-pine
.bg-black.text-white.hover
\
:bg-gray-900
.dark
\
:bg-white
.dark
\
:text-black
.dark
\
:hover
\
:bg-gray-100
.transition.rounded-lg.p-1.mr-0
\
.
5
.w-7.h-7.self-center
{
background-color
:
#286983
;
transition
:
background
0.2s
ease-out
linear
;
transition
:
background
-color
0.2s
ease-out
linear
;
}
.rose-pine
...
...
@@ -80,6 +80,46 @@
color
:
#e0def4
!important
;
}
.rose-pine
#dropdownDots
{
background-color
:
#403d52
;
}
.rose-pine
.flex.py-2
\
.
5
.px-3
\
.
5
.w-full.hover
\
:bg-gray-800
.transition
:hover
{
background
:
#524f67
;
}
.rose-pine
.m-auto.rounded-xl.max-w-full.w-
\
[
40
rem
\
]
.mx-2.bg-gray-50.dark
\
:bg-gray-900
.shadow-3xl
{
background-color
:
#26233a
;
}
.rose-pine
.w-full.rounded.p-4.text-sm.dark
\
:text-gray-300
.dark
\
:bg-gray-800
.outline-none.resize-none
{
background-color
:
#524f67
;
}
.rose-pine
.w-full.rounded.py-2.px-4.text-sm.dark
\
:text-gray-300
.dark
\
:bg-gray-800
.outline-none.svelte-1vx7r9s
{
background-color
:
#524f67
;
}
.rose-pine
.px-2
\
.
5
.py-2
\
.
5
.min-w-fit.rounded-lg.flex-1.md
\
:flex-none
.flex.text-right.transition.bg-gray-200.dark
\
:bg-gray-700
{
background-color
:
#403d52
;
}
.rose-pine
.px-2
\
.
5
.py-2
\
.
5
.min-w-fit.rounded-lg.flex-1.md
\
:flex-none
.flex.text-right.transition.hover
\
:bg-gray-300
.dark
\
:hover
\
:bg-gray-800:hover
{
background-color
:
#524f67
;
}
.rose-pine
.px-4.py-2.bg-emerald-600.hover
\
:bg-emerald-700
.text-gray-100.transition.rounded
{
background-color
:
#31748f
;
}
.rose-pine
#chat-search
>
*
{
background-color
:
#403d52
!important
;
}
.rose-pine
.svelte-1ee93ns
{
--primary
:
#eb6f92
!important
;
--secondary
:
#e0def4
!important
;
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment