Unverified Commit 6e843ab5 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #3882 from open-webui/dev

0.3.9
parents eff736ac b3a0d47a
......@@ -52,7 +52,7 @@
import { createOpenAITextStream } from '$lib/apis/streaming';
import { queryMemory } from '$lib/apis/memories';
import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
import { chatCompleted, generateTitle, generateSearchQuery } from '$lib/apis';
import { chatCompleted, generateTitle, generateSearchQuery, chatAction } from '$lib/apis';
import Banner from '../common/Banner.svelte';
import MessageInput from '$lib/components/chat/MessageInput.svelte';
......@@ -78,6 +78,8 @@
let showEventConfirmation = false;
let eventConfirmationTitle = '';
let eventConfirmationMessage = '';
let eventConfirmationInput = false;
let eventConfirmationInputPlaceholder = '';
let eventCallback = null;
let showModelSelector = true;
......@@ -96,6 +98,8 @@
let title = '';
let prompt = '';
let chatFiles = [];
let files = [];
let messages = [];
let history = {
......@@ -104,6 +108,7 @@
};
let params = {};
let valves = {};
$: if (history.currentId !== null) {
let _messages = [];
......@@ -156,12 +161,27 @@
} else {
message.citations = [data];
}
} else if (type === 'message') {
message.content += data.content;
} else if (type === 'replace') {
message.content = data.content;
} else if (type === 'confirmation') {
eventCallback = cb;
eventConfirmationInput = false;
showEventConfirmation = true;
eventConfirmationTitle = data.title;
eventConfirmationMessage = data.message;
} else if (type === 'input') {
eventCallback = cb;
eventConfirmationInput = true;
showEventConfirmation = true;
eventConfirmationTitle = data.title;
eventConfirmationMessage = data.message;
eventConfirmationInputPlaceholder = data.placeholder;
} else {
console.log('Unknown message type', data);
}
......@@ -315,6 +335,7 @@
}
params = chatContent?.params ?? {};
chatFiles = chatContent?.files ?? {};
autoScroll = true;
await tick();
......@@ -347,7 +368,7 @@
}
};
const chatCompletedHandler = async (modelId, responseMessageId, messages) => {
const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
await mermaid.run({
querySelector: '.mermaid'
});
......@@ -361,7 +382,7 @@
info: m.info ? m.info : undefined,
timestamp: m.timestamp
})),
chat_id: $chatId,
chat_id: chatId,
session_id: $socket?.id,
id: responseMessageId
}).catch((error) => {
......@@ -383,6 +404,65 @@
};
}
}
if ($chatId == chatId) {
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, chatId, {
models: selectedModels,
messages: messages,
history: history,
params: params,
files: chatFiles
});
await chats.set(await getChatList(localStorage.token));
}
}
};
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId) => {
const res = await chatAction(localStorage.token, actionId, {
model: modelId,
messages: messages.map((m) => ({
id: m.id,
role: m.role,
content: m.content,
info: m.info ? m.info : undefined,
timestamp: m.timestamp
})),
chat_id: chatId,
session_id: $socket?.id,
id: responseMessageId
}).catch((error) => {
toast.error(error);
messages.at(-1).error = { content: error };
return null;
});
if (res !== null) {
// Update chat history with the new messages
for (const message of res.messages) {
history.messages[message.id] = {
...history.messages[message.id],
...(history.messages[message.id].content !== message.content
? { originalContent: history.messages[message.id].content }
: {}),
...message
};
}
}
if ($chatId == chatId) {
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, chatId, {
models: selectedModels,
messages: messages,
history: history,
params: params,
files: chatFiles
});
await chats.set(await getChatList(localStorage.token));
}
}
};
const getChatEventEmitter = async (modelId: string, chatId: string = '') => {
......@@ -439,6 +519,13 @@
}
const _files = JSON.parse(JSON.stringify(files));
chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type)));
chatFiles = chatFiles.filter(
// Remove duplicates
(item, index, array) =>
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
);
files = [];
prompt = '';
......@@ -679,25 +766,10 @@
}
});
let files = [];
let files = JSON.parse(JSON.stringify(chatFiles));
if (model?.info?.meta?.knowledge ?? false) {
files = model.info.meta.knowledge;
files.push(...model.info.meta.knowledge);
}
const lastUserMessage = messages.filter((message) => message.role === 'user').at(-1);
files = [
...files,
...(lastUserMessage?.files?.filter((item) =>
['doc', 'file', 'collection', 'web_search_results'].includes(item.type)
) ?? []),
...(responseMessage?.files?.filter((item) =>
['doc', 'file', 'collection', 'web_search_results'].includes(item.type)
) ?? [])
].filter(
// Remove duplicates
(item, index, array) =>
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
);
eventTarget.dispatchEvent(
new CustomEvent('chat:start', {
......@@ -729,6 +801,7 @@
keep_alive: $settings.keepAlive ?? undefined,
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
files: files.length > 0 ? files : undefined,
...(Object.keys(valves).length ? { valves } : {}),
session_id: $socket?.id,
chat_id: $chatId,
id: responseMessageId
......@@ -752,7 +825,7 @@
controller.abort('User: Stop Response');
} else {
const messages = createMessagesList(responseMessageId);
await chatCompletedHandler(model.id, responseMessageId, messages);
await chatCompletedHandler(_chatId, model.id, responseMessageId, messages);
}
_response = responseMessage.content;
......@@ -860,7 +933,8 @@
messages: messages,
history: history,
models: selectedModels,
params: params
params: params,
files: chatFiles
});
await chats.set(await getChatList(localStorage.token));
}
......@@ -914,7 +988,7 @@
scrollToBottom();
}
if (messages.length == 2 && messages.at(1).content !== '') {
if (messages.length == 2 && messages.at(1).content !== '' && selectedModels[0] === model.id) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
await setChatTitle(_chatId, _title);
......@@ -927,24 +1001,10 @@
let _response = null;
const responseMessage = history.messages[responseMessageId];
let files = [];
let files = JSON.parse(JSON.stringify(chatFiles));
if (model?.info?.meta?.knowledge ?? false) {
files = model.info.meta.knowledge;
files.push(...model.info.meta.knowledge);
}
const lastUserMessage = messages.filter((message) => message.role === 'user').at(-1);
files = [
...files,
...(lastUserMessage?.files?.filter((item) =>
['doc', 'file', 'collection', 'web_search_results'].includes(item.type)
) ?? []),
...(responseMessage?.files?.filter((item) =>
['doc', 'file', 'collection', 'web_search_results'].includes(item.type)
) ?? [])
].filter(
// Remove duplicates
(item, index, array) =>
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
);
scrollToBottom();
......@@ -1033,6 +1093,7 @@
max_tokens: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
files: files.length > 0 ? files : undefined,
...(Object.keys(valves).length ? { valves } : {}),
session_id: $socket?.id,
chat_id: $chatId,
id: responseMessageId
......@@ -1064,7 +1125,7 @@
} else {
const messages = createMessagesList(responseMessageId);
await chatCompletedHandler(model.id, responseMessageId, messages);
await chatCompletedHandler(_chatId, model.id, responseMessageId, messages);
}
_response = responseMessage.content;
......@@ -1137,7 +1198,8 @@
models: selectedModels,
messages: messages,
history: history,
params: params
params: params,
files: chatFiles
});
await chats.set(await getChatList(localStorage.token));
}
......@@ -1175,7 +1237,7 @@
scrollToBottom();
}
if (messages.length == 2) {
if (messages.length == 2 && selectedModels[0] === model.id) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
......@@ -1408,8 +1470,14 @@
bind:show={showEventConfirmation}
title={eventConfirmationTitle}
message={eventConfirmationMessage}
input={eventConfirmationInput}
inputPlaceholder={eventConfirmationInputPlaceholder}
on:confirm={(e) => {
eventCallback(true);
if (e.detail) {
eventCallback(e.detail);
} else {
eventCallback(true);
}
}}
on:cancel={() => {
eventCallback(false);
......@@ -1511,6 +1579,7 @@
{sendPrompt}
{continueGeneration}
{regenerateResponse}
{chatActionHandler}
/>
</div>
</div>
......@@ -1539,6 +1608,18 @@
</div>
</div>
<ChatControls bind:show={showControls} bind:params />
<ChatControls
models={selectedModelIds.reduce((a, e, i, arr) => {
const model = $models.find((m) => m.id === e);
if (model) {
return [...a, model];
}
return a;
}, [])}
bind:show={showControls}
bind:chatFiles
bind:params
bind:valves
/>
</div>
{/if}
......@@ -6,7 +6,12 @@
export let show = false;
export let models = [];
export let chatId = null;
export let chatFiles = [];
export let valves = {};
export let params = {};
let largeScreen = false;
......@@ -43,6 +48,9 @@
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:valves
bind:params
/>
</div>
......@@ -56,6 +64,9 @@
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:valves
bind:params
/>
</div>
......
......@@ -5,7 +5,13 @@
import XMark from '$lib/components/icons/XMark.svelte';
import AdvancedParams from '../Settings/Advanced/AdvancedParams.svelte';
import Valves from '$lib/components/common/Valves.svelte';
import FileItem from '$lib/components/common/FileItem.svelte';
export let models = [];
export let chatFiles = [];
export let valves = {};
export let params = {};
</script>
......@@ -23,15 +29,51 @@
</div>
<div class=" dark:text-gray-200 text-sm font-primary">
{#if chatFiles.length > 0}
<div>
<div class="mb-1.5 font-medium">{$i18n.t('Files')}</div>
<div>
{#each chatFiles as file}
<FileItem
className="w-full"
url={`${file?.url}`}
name={file.name}
type={file.type}
dismissible={true}
on:dismiss={() => {
// Remove the file from the chatFiles array
chatFiles = chatFiles.filter((f) => f.id !== file.id);
}}
/>
{/each}
</div>
</div>
<hr class="my-2 border-gray-100 dark:border-gray-800" />
{/if}
{#if models.length === 1 && models[0]?.pipe?.valves_spec}
<div>
<div class=" font-medium">{$i18n.t('Valves')}</div>
<div>
<Valves valvesSpec={models[0]?.pipe?.valves_spec} bind:valves />
</div>
</div>
<hr class="my-2 border-gray-100 dark:border-gray-800" />
{/if}
<div>
<div class="mb-1.5 font-medium">System Prompt</div>
<div class="mb-1.5 font-medium">{$i18n.t('System Prompt')}</div>
<div>
<textarea
bind:value={params.system}
class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 border border-gray-100 dark:border-gray-800 outline-none resize-none"
rows="3"
placeholder="Enter system prompt"
placeholder={$i18n.t('Enter system prompt')}
/>
</div>
</div>
......@@ -39,7 +81,7 @@
<hr class="my-2 border-gray-100 dark:border-gray-800" />
<div>
<div class="mb-1.5 font-medium">Advanced Params</div>
<div class="mb-1.5 font-medium">{$i18n.t('Advanced Params')}</div>
<div>
<AdvancedParams bind:params />
......
......@@ -40,6 +40,8 @@
import Headphone from '../icons/Headphone.svelte';
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
import { transcribeAudio } from '$lib/apis/audio';
import FileItem from '../common/FileItem.svelte';
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
const i18n = getContext('i18n');
......@@ -297,24 +299,7 @@
});
</script>
{#if dragged}
<div
class="fixed {$showSidebar
? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
: 'left-0'} w-full h-full flex z-50 touch-none pointer-events-none"
id="dropzone"
role="region"
aria-label="Drag and Drop Container"
>
<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="m-auto pt-64 flex flex-col justify-center">
<div class="max-w-md">
<AddFilesPlaceholder />
</div>
</div>
</div>
</div>
{/if}
<FilesOverlay show={dragged} />
<div class="w-full font-primary">
<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
......@@ -500,10 +485,10 @@
dir={$settings?.chatDirection ?? 'LTR'}
>
{#if files.length > 0}
<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
<div class="mx-1 mt-2.5 mb-1 flex flex-wrap gap-2">
{#each files as file, fileIdx}
<div class=" relative group">
{#if file.type === 'image'}
{#if file.type === 'image'}
<div class=" relative group">
<div class="relative">
<img
src={file.url}
......@@ -534,137 +519,39 @@
</Tooltip>
{/if}
</div>
{:else if ['doc', 'file'].includes(file.type)}
<div
class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
{#if file.status === 'processed'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
{:else}
<svg
class=" w-6 h-6 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
.spinner_oXPr {
animation-delay: 0.1s;
}
.spinner_ZTLf {
animation-delay: 0.2s;
}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
}
100% {
transform: translate(0);
}
}
</style><circle
class="spinner_qM83"
cx="4"
cy="12"
r="2.5"
/><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle
class="spinner_qM83 spinner_ZTLf"
cx="20"
cy="12"
r="2.5"
/></svg
>
{/if}
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file.name}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
</div>
</div>
{:else if file.type === 'collection'}
<div
class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click={() => {
files.splice(fileIdx, 1);
files = files;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
viewBox="0 0 20 20"
fill="currentColor"
class="w-6 h-6"
class="w-4 h-4"
>
<path
d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
/>
<path
d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div>
</button>
</div>
{/if}
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click={() => {
files.splice(fileIdx, 1);
files = files;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
</div>
{:else}
<FileItem
name={file.name}
type={file.type}
dismissible={true}
on:dismiss={() => {
files.splice(fileIdx, 1);
files = files;
}}
/>
{/if}
{/each}
</div>
{/if}
......
<script lang="ts">
import { showSidebar } from '$lib/stores';
import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
export let show = false;
let overlayElement = null;
$: if (show && overlayElement) {
document.body.appendChild(overlayElement);
document.body.style.overflow = 'hidden';
} else if (overlayElement) {
document.body.removeChild(overlayElement);
document.body.style.overflow = 'unset';
}
</script>
{#if show}
<div
bind:this={overlayElement}
class="fixed {$showSidebar
? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
: 'left-0'} fixed top-0 right-0 bottom-0 w-full h-full flex z-[9999] touch-none pointer-events-none"
id="dropzone"
role="region"
aria-label="Drag and Drop Container"
>
<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="m-auto pt-64 flex flex-col justify-center">
<div class="max-w-md">
<AddFilesPlaceholder />
</div>
</div>
</div>
</div>
{/if}
......@@ -22,6 +22,7 @@
export let sendPrompt: Function;
export let continueGeneration: Function;
export let regenerateResponse: Function;
export let chatActionHandler: Function;
export let user = $_user;
export let prompt;
......@@ -335,6 +336,9 @@
copyToClipboard={copyToClipboardWithToast}
{continueGeneration}
{regenerateResponse}
on:action={async (e) => {
await chatActionHandler(chatId, e.detail, message.model, message.id);
}}
on:save={async (e) => {
console.log('save', e);
......
<script lang="ts">
import { getContext, onMount, tick } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
const i18n = getContext('i18n');
......@@ -54,9 +53,25 @@
<div class="text-sm font-medium dark:text-gray-300">
{$i18n.t('Source')}
</div>
<div class="text-sm dark:text-gray-400">
{document.source?.name ?? $i18n.t('No source available')}
</div>
{#if document.source?.name}
<div class="text-sm dark:text-gray-400">
<a
href={document?.metadata?.file_id
? `/api/v1/files/${document?.metadata?.file_id}/content`
: document.source.name.includes('http')
? document.source.name
: `#`}
target="_blank"
>
{document?.metadata?.name ?? document.source.name}
</a>
</div>
{:else}
<div class="text-sm dark:text-gray-400">
{$i18n.t('No source available')}
</div>
{/if}
</div>
<div class="flex flex-col w-full">
<div class=" text-sm font-medium dark:text-gray-300">
......
......@@ -37,6 +37,7 @@
import CitationsModal from '$lib/components/chat/Messages/CitationsModal.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import WebSearchResults from './ResponseMessage/WebSearchResults.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte';
export let message;
export let siblings;
......@@ -54,6 +55,7 @@
export let copyToClipboard: Function;
export let continueGeneration: Function;
export let regenerateResponse: Function;
export let chatActionHandler: Function;
let model = null;
$: model = $models.find((m) => m.id === message.model);
......@@ -1020,6 +1022,33 @@
</svg>
</button>
</Tooltip>
{#each model?.actions ?? [] as action}
<Tooltip content={action.name} placement="bottom">
<button
type="button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
dispatch('action', action.id);
}}
>
{#if action.icon_url}
<img
src={action.icon_url}
class="w-4 h-4 {action.icon_url.includes('svg')
? 'dark:invert-[80%]'
: ''}"
style="fill: currentColor;"
alt={action.name}
/>
{:else}
<Sparkles strokeWidth="2.1" className="size-4" />
{/if}
</button>
</Tooltip>
{/each}
{/if}
{/if}
{/if}
......
......@@ -9,6 +9,7 @@
import { user as _user } from '$lib/stores';
import { getFileContentById } from '$lib/apis/files';
import FileItem from '$lib/components/common/FileItem.svelte';
const i18n = getContext('i18n');
......@@ -99,106 +100,11 @@
{#if file.type === 'image'}
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
{:else if file.type === 'file'}
<button
class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-850 rounded-xl border border-gray-200 dark:border-none text-left"
type="button"
on:click={async () => {
if (file?.url) {
window.open(`${file?.url}/content`, '_blank').focus();
}
}}
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file.name}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('File')}</div>
</div>
</button>
<FileItem url={`${file?.url}/content`} name={file.name} type={$i18n.t('File')} />
{:else if file.type === 'doc'}
<button
class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-850 rounded-xl border border-gray-200 dark:border-none text-left"
type="button"
on:click={() => {
if (file?.url) {
window.open(file?.url, '_blank').focus();
}
}}
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file.name}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
</div>
</button>
<FileItem url={`${file?.url}`} name={file.name} type={$i18n.t('Document')} />
{:else if file.type === 'collection'}
<button
class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
type="button"
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
/>
<path
d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
/>
</svg>
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div>
</button>
<FileItem name={file?.title ?? `#${file.name}`} type={$i18n.t('Collection')} />
{/if}
</div>
{/each}
......
<script lang="ts">
import Switch from '$lib/components/common/Switch.svelte';
import { getContext, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
......@@ -720,10 +721,22 @@
{#if (params?.use_mmap ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.use_mmap ?? null) !== null}
<div class="flex justify-between items-center mt-1">
<div class="text-xs text-gray-500">
{params.use_mmap ? 'Enabled' : 'Disabled'}
</div>
<div class=" pr-2">
<Switch bind:state={params.use_mmap} />
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
......@@ -740,10 +753,22 @@
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.use_mlock ?? null) !== null}
<div class="flex justify-between items-center mt-1">
<div class="text-xs text-gray-500">
{params.use_mlock ? 'Enabled' : 'Disabled'}
</div>
<div class=" pr-2">
<Switch bind:state={params.use_mlock} />
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
......
......@@ -19,6 +19,7 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import Switch from '$lib/components/common/Switch.svelte';
import Valves from '$lib/components/common/Valves.svelte';
const dispatch = createEventDispatcher();
......@@ -170,91 +171,7 @@
<div>
{#if !loading}
{#if valvesSpec}
{#each Object.keys(valvesSpec.properties) as property, idx}
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{valvesSpec.properties[property].title}
{#if (valvesSpec?.required ?? []).includes(property)}
<span class=" text-gray-500">*required</span>
{/if}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
valves[property] =
(valves[property] ?? null) === null
? valvesSpec.properties[property]?.default ?? ''
: null;
}}
>
{#if (valves[property] ?? null) === null}
<span class="ml-2 self-center">
{#if (valvesSpec?.required ?? []).includes(property)}
{$i18n.t('None')}
{:else}
{$i18n.t('Default')}
{/if}
</span>
{:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
{/if}
</button>
</div>
{#if (valves[property] ?? null) !== null}
<!-- {valves[property]} -->
<div class="flex mt-0.5 mb-1.5 space-x-2">
<div class=" flex-1">
{#if valvesSpec.properties[property]?.enum ?? null}
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={valves[property]}
>
{#each valvesSpec.properties[property].enum as option}
<option value={option} selected={option === valves[property]}>
{option}
</option>
{/each}
</select>
{:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'}
<div class="flex justify-between items-center">
<div class="text-xs text-gray-500">
{valves[property] ? 'Enabled' : 'Disabled'}
</div>
<div class=" pr-2">
<Switch bind:state={valves[property]} />
</div>
</div>
{:else}
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={valvesSpec.properties[property].title}
bind:value={valves[property]}
autocomplete="off"
required
/>
{/if}
</div>
</div>
{/if}
{#if (valvesSpec.properties[property]?.description ?? null) !== null}
<div class="text-xs text-gray-500">
{valvesSpec.properties[property].description}
</div>
{/if}
</div>
{/each}
{:else}
<div>No valves</div>
{/if}
<Valves {valvesSpec} bind:valves />
{:else}
<Spinner className="size-5" />
{/if}
......
......@@ -13,9 +13,14 @@
export let cancelLabel = $i18n.t('Cancel');
export let confirmLabel = $i18n.t('Confirm');
export let input = false;
export let inputPlaceholder = '';
export let show = false;
let modalElement = null;
let mounted = false;
let inputValue = '';
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
......@@ -73,6 +78,16 @@
{:else}
{$i18n.t('This action cannot be undone. Do you wish to continue?')}
{/if}
{#if input}
<textarea
bind:value={inputValue}
placeholder={inputPlaceholder ? inputPlaceholder : $i18n.t('Enter your message')}
class="w-full mt-2 rounded-lg px-4 py-2 text-sm dark:text-gray-300 dark:bg-gray-900 outline-none resize-none"
rows="3"
required
/>
{/if}
</div>
</slot>
......@@ -91,7 +106,7 @@
class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
on:click={() => {
show = false;
dispatch('confirm');
dispatch('confirm', inputValue);
}}
type="button"
>
......
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let className = 'w-72';
export let url: string | null = null;
export let clickHandler: Function | null = null;
export let dismissible = false;
export let status = 'processed';
export let name: string;
export let type: string;
</script>
<div class="relative group">
<button
class="h-14 {className} flex items-center space-x-3 bg-white dark:bg-gray-800 rounded-xl border border-gray-100 dark:border-gray-800 text-left"
type="button"
on:click={async () => {
if (clickHandler === null) {
if (url) {
if (type === 'file') {
window.open(`${url}/content`, '_blank').focus();
} else {
window.open(`${url}`, '_blank').focus();
}
}
} else {
clickHandler();
}
}}
>
<div class="p-4 py-[1.1rem] bg-red-400 text-white rounded-l-xl">
{#if status === 'processed'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class=" size-5"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
{:else}
<svg
class=" size-5 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
.spinner_oXPr {
animation-delay: 0.1s;
}
.spinner_ZTLf {
animation-delay: 0.2s;
}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
}
100% {
transform: translate(0);
}
}
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
>
{/if}
</div>
<div class="flex flex-col justify-center -space-y-0.5 pl-1.5 pr-4 w-full">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{name}
</div>
<div class=" text-gray-500 text-xs">
{#if type === 'file'}
{$i18n.t('File')}
{:else if type === 'doc'}
{$i18n.t('Document')}
{:else if type === 'collection'}
{$i18n.t('Collection')}
{:else}
<span class=" capitalize">{type}</span>
{/if}
</div>
</div>
</button>
{#if dismissible}
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click={() => {
dispatch('dismiss');
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/if}
</div>
<script lang="ts">
export let value: string = '';
export let placeholder = '';
export let required = true;
export let readOnly = false;
export let outerClassName = 'flex flex-1';
export let inputClassName =
......@@ -15,7 +16,7 @@
class={inputClassName}
{placeholder}
bind:value
required={!readOnly}
required={required && !readOnly}
disabled={readOnly}
autocomplete="off"
{...{ type: show ? 'text' : 'password' }}
......
<script>
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
import Switch from './Switch.svelte';
export let valvesSpec = null;
export let valves = {};
</script>
{#if valvesSpec}
{#each Object.keys(valvesSpec.properties) as property, idx}
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{valvesSpec.properties[property].title}
{#if (valvesSpec?.required ?? []).includes(property)}
<span class=" text-gray-500">*required</span>
{/if}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
valves[property] =
(valves[property] ?? null) === null
? valvesSpec.properties[property]?.default ?? ''
: null;
}}
>
{#if (valves[property] ?? null) === null}
<span class="ml-2 self-center">
{#if (valvesSpec?.required ?? []).includes(property)}
{$i18n.t('None')}
{:else}
{$i18n.t('Default')}
{/if}
</span>
{:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
{/if}
</button>
</div>
{#if (valves[property] ?? null) !== null}
<!-- {valves[property]} -->
<div class="flex mt-0.5 mb-1.5 space-x-2">
<div class=" flex-1">
{#if valvesSpec.properties[property]?.enum ?? null}
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800"
bind:value={valves[property]}
>
{#each valvesSpec.properties[property].enum as option}
<option value={option} selected={option === valves[property]}>
{option}
</option>
{/each}
</select>
{:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'}
<div class="flex justify-between items-center">
<div class="text-xs text-gray-500">
{valves[property] ? 'Enabled' : 'Disabled'}
</div>
<div class=" pr-2">
<Switch bind:state={valves[property]} />
</div>
</div>
{:else}
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800"
type="text"
placeholder={valvesSpec.properties[property].title}
bind:value={valves[property]}
autocomplete="off"
required
/>
{/if}
</div>
</div>
{/if}
{#if (valvesSpec.properties[property]?.description ?? null) !== null}
<div class="text-xs text-gray-500">
{valvesSpec.properties[property].description}
</div>
{/if}
</div>
{/each}
{:else}
<div class="text-sm">No valves</div>
{/if}
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
/>
</svg>
......@@ -8,14 +8,16 @@
import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
import { uploadDocToVectorDB } from '$lib/apis/rag';
import { transformFileName } from '$lib/utils';
import { processDocToVectorDB, uploadDocToVectorDB } from '$lib/apis/rag';
import { blobToFile, transformFileName } from '$lib/utils';
import Checkbox from '$lib/components/common/Checkbox.svelte';
import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
import { transcribeAudio } from '$lib/apis/audio';
import { uploadFile } from '$lib/apis/files';
const i18n = getContext('i18n');
......@@ -50,7 +52,28 @@
};
const uploadDoc = async (file) => {
const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
console.log(file);
// Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav'].includes(file['type'])) {
const transcribeRes = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(error);
return null;
});
if (transcribeRes) {
console.log(transcribeRes);
const blob = new Blob([transcribeRes.text], { type: 'text/plain' });
file = blobToFile(blob, `${file.name}.txt`);
}
}
// Upload the file to the server
const uploadedFile = await uploadFile(localStorage.token, file).catch((error) => {
toast.error(error);
return null;
});
const res = await processDocToVectorDB(localStorage.token, uploadedFile.id).catch((error) => {
toast.error(error);
return null;
});
......
......@@ -122,12 +122,17 @@
if (res) {
if (func.is_global) {
toast.success($i18n.t('Filter is now globally enabled'));
func.type === 'filter'
? toast.success($i18n.t('Filter is now globally enabled'))
: toast.success($i18n.t('Function is now globally enabled'));
} else {
toast.success($i18n.t('Filter is now globally disabled'));
func.type === 'filter'
? toast.success($i18n.t('Filter is now globally disabled'))
: toast.success($i18n.t('Function is now globally disabled'));
}
functions.set(await getFunctions(localStorage.token));
models.set(await getModels(localStorage.token));
}
};
</script>
......@@ -230,7 +235,7 @@
</a>
<div class="flex flex-row gap-0.5 self-center">
{#if func?.meta?.manifest?.funding_url ?? false}
<Tooltip content="Support">
<Tooltip content={$i18n.t('Support')}>
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
......@@ -244,7 +249,7 @@
</Tooltip>
{/if}
<Tooltip content="Valves">
<Tooltip content={$i18n.t('Valves')}>
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
......@@ -294,7 +299,7 @@
showDeleteConfirm = true;
}}
toggleGlobalHandler={() => {
if (func.type === 'filter') {
if (['filter', 'action'].includes(func.type)) {
toggleGlobalHandler(func);
}
}}
......@@ -309,7 +314,7 @@
</FunctionMenu>
<div class=" self-center mx-1">
<Tooltip content={func.is_active ? 'Enabled' : 'Disabled'}>
<Tooltip content={func.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
<Switch
bind:state={func.is_active}
on:change={async (e) => {
......@@ -489,15 +494,15 @@
<div>Please carefully review the following warnings:</div>
<ul class=" mt-1 list-disc pl-4 text-xs">
<li>Functions allow arbitrary code execution.</li>
<li>Do not install functions from sources you do not fully trust.</li>
<li>{$i18n.t('Functions allow arbitrary code execution.')}</li>
<li>{$i18n.t('Do not install functions from sources you do not fully trust.')}</li>
</ul>
</div>
<div class="my-3">
I acknowledge that I have read and I understand the implications of my action. I am aware of
the risks associated with executing arbitrary code and I have verified the trustworthiness of
the source.
{$i18n.t(
'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
)}
</div>
</div>
</ConfirmDialog>
......@@ -309,7 +309,7 @@ class Pipe:
<input
class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
placeholder="Function Name (e.g. My Filter)"
placeholder={$i18n.t('Function Name (e.g. My Filter)')}
bind:value={name}
required
/>
......@@ -317,7 +317,7 @@ class Pipe:
<input
class="w-full px-3 py-2 text-sm font-medium disabled:text-gray-300 dark:disabled:text-gray-700 bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
placeholder="Function ID (e.g. my_filter)"
placeholder={$i18n.t('Function ID (e.g. my_filter)')}
bind:value={id}
required
disabled={edit}
......@@ -326,7 +326,9 @@ class Pipe:
<input
class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
placeholder="Function Description (e.g. A filter to remove profanity from text)"
placeholder={$i18n.t(
'Function Description (e.g. A filter to remove profanity from text)'
)}
bind:value={meta.description}
required
/>
......@@ -348,10 +350,10 @@ class Pipe:
<div class="pb-3 flex justify-between">
<div class="flex-1 pr-3">
<div class="text-xs text-gray-500 line-clamp-2">
<span class=" font-semibold dark:text-gray-200">Warning:</span> Functions allow
arbitrary code execution <br />—
<span class=" font-semibold dark:text-gray-200">{$i18n.t('Warning:')}</span>
{$i18n.t('Functions allow arbitrary code execution')} <br />—
<span class=" font-medium dark:text-gray-400"
>don't install random functions from sources you don't trust.</span
>{$i18n.t(`don't install random functions from sources you don't trust.`)}</span
>
</div>
</div>
......@@ -376,18 +378,18 @@ class Pipe:
>
<div class="text-sm text-gray-500">
<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
<div>Please carefully review the following warnings:</div>
<div>{$i18n.t('Please carefully review the following warnings:')}</div>
<ul class=" mt-1 list-disc pl-4 text-xs">
<li>Functions allow arbitrary code execution.</li>
<li>Do not install functions from sources you do not fully trust.</li>
<li>{$i18n.t('Functions allow arbitrary code execution.')}</li>
<li>{$i18n.t('Do not install functions from sources you do not fully trust.')}</li>
</ul>
</div>
<div class="my-3">
I acknowledge that I have read and I understand the implications of my action. I am aware of
the risks associated with executing arbitrary code and I have verified the trustworthiness of
the source.
{$i18n.t(
'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
)}
</div>
</div>
</ConfirmDialog>
......@@ -48,7 +48,7 @@
align="start"
transition={flyAndScale}
>
{#if func.type === 'filter'}
{#if ['filter', 'action'].includes(func.type)}
<div
class="flex gap-2 justify-between items-center px-3 py-2 text-sm font-medium cursor-pointerrounded-md"
>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment