Merge pull request #30 from ollama-webui/dev

feat: server url edit & markdown styling added
This commit is contained in:
Timothy Jaeryang Baek 2023-10-24 20:15:13 -05:00 committed by GitHub
commit 886c19246f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 316 additions and 51 deletions

View file

@ -117,6 +117,34 @@ docker run -d -p 3000:8080 --name ollama-webui --restart always ollama-webui
caddy run --envfile .env --config ./Caddyfile.localhost caddy run --envfile .env --config ./Caddyfile.localhost
``` ```
## Troubleshooting
### Connection Errors
If you encounter difficulties connecting to the Ollama server, please follow these steps to diagnose and resolve the issue:
**1. Verify Ollama Server Configuration**
Ensure that the Ollama server is properly configured to accept incoming connections from all origins. To do this, make sure the server is launched with the `OLLAMA_ORIGINS=*` environment variable, as shown in the following command:
```bash
OLLAMA_HOST=0.0.0.0 OLLAMA_ORIGINS=* ollama serve
```
This configuration allows Ollama to accept connections from any source.
**2. Check Ollama URL Format**
Ensure that the Ollama URL is correctly formatted in the application settings. Follow these steps:
- Go to "Settings" within the Ollama WebUI.
- Navigate to the "General" section.
- Verify that the Ollama URL is in the following format: `http://localhost:11434/api`.
It is crucial to include the `/api` at the end of the URL to ensure that the Ollama Web UI can communicate with the server.
By following these troubleshooting steps, you should be able to identify and resolve connection issues with your Ollama server configuration. If you require further assistance or have additional questions, please don't hesitate to reach out or refer to our documentation for comprehensive guidance.
## What's Next? 🚀 ## What's Next? 🚀
### To-Do List 📝 ### To-Do List 📝

77
package-lock.json generated
View file

@ -21,6 +21,7 @@
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3", "@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4", "@sveltejs/kit": "^1.20.4",
"@tailwindcss/typography": "^0.5.10",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
@ -838,6 +839,34 @@
"vite": "^4.0.0" "vite": "^4.0.0"
} }
}, },
"node_modules/@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@types/cookie": { "node_modules/@types/cookie": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@ -2603,6 +2632,18 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -4670,6 +4711,30 @@
"debug": "^4.3.4" "debug": "^4.3.4"
} }
}, },
"@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"dependencies": {
"postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
}
}
},
"@types/cookie": { "@types/cookie": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@ -5909,6 +5974,18 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"lodash.merge": { "lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View file

@ -20,6 +20,7 @@
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3", "@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4", "@sveltejs/kit": "^1.20.4",
"@tailwindcss/typography": "^0.5.10",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View file

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
import { API_BASE_URL } from '$lib/constants'; import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import toast from 'svelte-french-toast'; import toast from 'svelte-french-toast';
export let show = false; export let show = false;
export let saveSettings: Function; export let saveSettings: Function;
export let getModelTags: Function; export let getModelTags: Function;
let API_BASE_URL = BUILD_TIME_API_BASE_URL;
let system = ''; let system = '';
let temperature = 0.8; let temperature = 0.8;
@ -33,6 +34,22 @@
}); });
}; };
const checkOllamaConnection = async () => {
if (API_BASE_URL === '') {
API_BASE_URL = BUILD_TIME_API_BASE_URL;
}
const res = await getModelTags(API_BASE_URL);
if (res) {
toast.success('Server connection verified');
saveSettings(
API_BASE_URL,
system != '' ? system : null,
temperature != 0.8 ? temperature : null
);
}
};
const pullModelHandler = async () => { const pullModelHandler = async () => {
const res = await fetch(`${API_BASE_URL}/pull`, { const res = await fetch(`${API_BASE_URL}/pull`, {
method: 'POST', method: 'POST',
@ -139,6 +156,7 @@
$: if (show) { $: if (show) {
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
system = settings.system ?? ''; system = settings.system ?? '';
temperature = settings.temperature ?? 0.8; temperature = settings.temperature ?? 0.8;
} }
@ -227,6 +245,50 @@
<div class="flex-1 md:min-h-[300px]"> <div class="flex-1 md:min-h-[300px]">
{#if selectedMenu === 'general'} {#if selectedMenu === 'general'}
<div class="flex flex-col space-y-3"> <div class="flex flex-col space-y-3">
<div>
<div class=" mb-2.5 text-sm font-medium">Ollama Server URL</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded py-2 px-4 text-sm text-gray-300 bg-gray-800 outline-none"
placeholder="Enter URL (e.g. http://localhost:11434/api)"
bind:value={API_BASE_URL}
/>
</div>
<button
class="px-3 bg-gray-600 hover:bg-gray-700 rounded transition"
on:click={() => {
checkOllamaConnection();
}}
>
<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="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
<div class="mt-2 text-xs text-gray-500">
Trouble accessing Ollama? <a
class=" text-gray-300 font-medium"
href="https://github.com/ollama-webui/ollama-webui#troubleshooting"
target="_blank"
>
Click here for help.
</a>
</div>
</div>
<hr class=" border-gray-700" />
<div> <div>
<div class=" mb-2.5 text-sm font-medium">System Prompt</div> <div class=" mb-2.5 text-sm font-medium">System Prompt</div>
<textarea <textarea
@ -260,6 +322,7 @@
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 transition rounded" class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 transition rounded"
on:click={() => { on:click={() => {
saveSettings( saveSettings(
API_BASE_URL === '' ? BUILD_TIME_API_BASE_URL : API_BASE_URL,
system != '' ? system : null, system != '' ? system : null,
temperature != 0.8 ? temperature : null temperature != 0.8 ? temperature : null
); );
@ -305,11 +368,11 @@
</div> </div>
<div class="mt-2 text-xs text-gray-500"> <div class="mt-2 text-xs text-gray-500">
To access the available model names for downloading, click <a To access the available model names for downloading, <a
class=" text-gray-300 font-medium" class=" text-gray-300 font-medium"
href="https://ollama.ai/library" href="https://ollama.ai/library"
target="_blank">here</a target="_blank">click here.</a
>. >
</div> </div>
{#if pullProgress !== ''} {#if pullProgress !== ''}

View file

@ -6,13 +6,14 @@
import fileSaver from 'file-saver'; import fileSaver from 'file-saver';
const { saveAs } = fileSaver; const { saveAs } = fileSaver;
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/dark.min.css'; import 'highlight.js/styles/github-dark.min.css';
import { API_BASE_URL } from '$lib/constants'; import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import Navbar from '$lib/components/layout/Navbar.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte'; import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
let API_BASE_URL = BUILD_TIME_API_BASE_URL;
let suggestions = ''; // $page.url.searchParams.get('suggestions'); let suggestions = ''; // $page.url.searchParams.get('suggestions');
let models = []; let models = [];
@ -26,26 +27,24 @@
let chats = []; let chats = [];
let chatId = uuidv4(); let chatId = uuidv4();
let title = ``; let title = '';
let prompt = ''; let prompt = '';
let messages = []; let messages = [];
onMount(async () => { onMount(async () => {
console.log(API_BASE_URL); let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
await getModelTags();
let settings = localStorage.getItem('settings'); API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
if (settings) { console.log(API_BASE_URL);
settings = JSON.parse(settings); system = settings.system ?? null;
console.log(settings); temperature = settings.temperature ?? null;
await getModelTags();
selectedModel = selectedModel =
settings.model && models.map((model) => model.name).includes(settings.model) settings.model && models.map((model) => model.name).includes(settings.model)
? settings.model ? settings.model
: ''; : '';
system = settings.system ?? null;
temperature = settings.temperature ?? null;
}
db = await openDB('Chats', 1, { db = await openDB('Chats', 1, {
upgrade(db) { upgrade(db) {
@ -117,6 +116,41 @@
); );
}; };
const createCopyCodeBlockButton = () => {
// use a class selector if available
let blocks = document.querySelectorAll('pre');
console.log(blocks);
blocks.forEach((block) => {
// only add button if browser supports Clipboard API
if (navigator.clipboard && block.childNodes.length < 2) {
let button = document.createElement('button');
button.innerText = 'Copy Code';
block.appendChild(button);
button.addEventListener('click', async () => {
await copyCode(block, button);
});
}
});
async function copyCode(block, button) {
let code = block.querySelector('code');
let text = code.innerText;
await navigator.clipboard.writeText(text);
// visual feedback that task is completed
button.innerText = 'Code Copied';
setTimeout(() => {
button.innerText = 'Copy Code';
}, 700);
}
};
////////////////////////// //////////////////////////
// Web functions // Web functions
////////////////////////// //////////////////////////
@ -133,21 +167,23 @@
toast.success('Default model updated'); toast.success('Default model updated');
}; };
const saveSettings = (_system, _temperature) => { const saveSettings = async (_api_base_url, _system, _temperature) => {
API_BASE_URL = _api_base_url;
system = _system; system = _system;
temperature = _temperature; temperature = _temperature;
let settings = localStorage.getItem('settings') ?? '{}'; let settings = localStorage.getItem('settings') ?? '{}';
if (settings) { if (settings) {
settings = JSON.parse(settings); settings = JSON.parse(settings);
settings.API_BASE_URL = API_BASE_URL;
settings.system = system; settings.system = system;
settings.temperature = temperature; settings.temperature = temperature;
localStorage.setItem('settings', JSON.stringify(settings)); localStorage.setItem('settings', JSON.stringify(settings));
} }
console.log(settings); console.log(settings);
await getModelTags();
console.log('saved');
}; };
const createNewChat = () => { const createNewChat = () => {
@ -163,7 +199,10 @@
settings = JSON.parse(settings); settings = JSON.parse(settings);
console.log(settings); console.log(settings);
selectedModel = settings.model ?? selectedModel; selectedModel =
settings.model && models.map((model) => model.name).includes(settings.model)
? settings.model
: '';
system = settings.system ?? system; system = settings.system ?? system;
temperature = settings.temperature ?? temperature; temperature = settings.temperature ?? temperature;
} }
@ -172,12 +211,18 @@
const loadChat = async (id) => { const loadChat = async (id) => {
const chat = await db.get('chats', id); const chat = await db.get('chats', id);
if (chatId !== chat.id) {
messages = chat.messages; messages = chat.messages;
title = chat.title; title = chat.title;
chatId = chat.id; chatId = chat.id;
selectedModel = chat.model ?? selectedModel; selectedModel = chat.model ?? selectedModel;
system = chat.system ?? system; system = chat.system ?? system;
temperature = chat.temperature ?? temperature; temperature = chat.temperature ?? temperature;
await tick();
hljs.highlightAll();
createCopyCodeBlockButton();
}
}; };
const deleteChatHistory = async () => { const deleteChatHistory = async () => {
@ -219,8 +264,8 @@
// Ollama functions // Ollama functions
////////////////////////// //////////////////////////
const getModelTags = async () => { const getModelTags = async (url = null) => {
const res = await fetch(`${API_BASE_URL}/tags`, { const res = await fetch(`${url === null ? API_BASE_URL : url}/tags`, {
method: 'GET', method: 'GET',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
@ -233,11 +278,13 @@
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
return { models: [] }; toast.error('Server connection failed');
return null;
}); });
console.log(res); console.log(res);
models = res.models ?? []; models = res?.models ?? [];
return res;
}; };
const submitPrompt = async (user_prompt) => { const submitPrompt = async (user_prompt) => {
@ -334,6 +381,7 @@
responseMessage.context = data.context; responseMessage.context = data.context;
messages = messages; messages = messages;
hljs.highlightAll(); hljs.highlightAll();
createCopyCodeBlockButton();
} }
} }
} }
@ -341,16 +389,10 @@
console.log(error); console.log(error);
} }
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
}
window.scrollTo({ top: document.body.scrollHeight });
if (messages.length == 2) {
await generateTitle(user_prompt);
}
await db.put('chats', { await db.put('chats', {
id: chatId, id: chatId,
title: title, title: title === '' ? 'New Chat' : title,
model: selectedModel, model: selectedModel,
system: system, system: system,
options: { options: {
@ -359,6 +401,13 @@
timestamp: Date.now(), timestamp: Date.now(),
messages: messages messages: messages
}); });
}
window.scrollTo({ top: document.body.scrollHeight });
if (messages.length == 2) {
await generateTitle(chatId, user_prompt);
}
chats = await db.getAllFromIndex('chats', 'timestamp'); chats = await db.getAllFromIndex('chats', 'timestamp');
} }
}; };
@ -430,6 +479,7 @@
responseMessage.context = data.context; responseMessage.context = data.context;
messages = messages; messages = messages;
hljs.highlightAll(); hljs.highlightAll();
createCopyCodeBlockButton();
} }
} }
} }
@ -442,7 +492,7 @@
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
await db.put('chats', { await db.put('chats', {
id: chatId, id: chatId,
title: title, title: title === '' ? 'New Chat' : title,
model: selectedModel, model: selectedModel,
system: system, system: system,
options: { options: {
@ -457,7 +507,7 @@
console.log(messages); console.log(messages);
}; };
const generateTitle = async (user_prompt) => { const generateTitle = async (_chatId, user_prompt) => {
console.log('generateTitle'); console.log('generateTitle');
const res = await fetch(`${API_BASE_URL}/generate`, { const res = await fetch(`${API_BASE_URL}/generate`, {
@ -482,8 +532,12 @@
if (res) { if (res) {
console.log(res); console.log(res);
const chat = await db.get('chats', _chatId);
await db.put('chats', { ...chat, title: res.response === '' ? 'New Chat' : res.response });
if (chat.id === chatId) {
title = res.response; title = res.response;
} }
}
}; };
</script> </script>
@ -609,7 +663,9 @@
</div> </div>
</div> </div>
{:else} {:else}
<div class="markdown-body whitespace-pre-line"> <div
class="prose max-w-full prose-invert prose-headings:my-0 prose-p:my-0 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-2 prose-ol:-my-2 prose-li:-my-2 whitespace-pre-line"
>
{@html marked.parse(message.content)} {@html marked.parse(message.content)}
</div> </div>
{/if} {/if}
@ -863,4 +919,33 @@
clip-path: inset(0 -1ch 0 0); clip-path: inset(0 -1ch 0 0);
} }
} }
pre[class*='language-'] {
position: relative;
overflow: auto;
/* make space */
margin: 5px 0;
padding: 1.75rem 0 1.75rem 1rem;
border-radius: 10px;
}
pre[class*='language-'] button {
position: absolute;
top: 5px;
right: 5px;
font-size: 0.9rem;
padding: 0.15rem;
background-color: #828282;
border: ridge 1px #7b7b7c;
border-radius: 5px;
text-shadow: #c4c4c4 0 0 2px;
}
pre[class*='language-'] button:hover {
cursor: pointer;
background-color: #bcbabb;
}
</style> </style>

View file

@ -17,8 +17,19 @@ export default {
900: '#202123', 900: '#202123',
950: '#050509' 950: '#050509'
} }
},
typography: {
DEFAULT: {
css: {
pre: false,
code: false,
'pre code': false,
'code::before': false,
'code::after': false
}
}
} }
} }
}, },
plugins: [] plugins: [require('@tailwindcss/typography')]
}; };