mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2025-02-05 02:19:16 +00:00
Added new stuff
This commit is contained in:
parent
732a2ba96b
commit
0119a3df53
212
web/src/components/ChangelogPopup.vue
Normal file
212
web/src/components/ChangelogPopup.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="showChangelogPopup" class="changelog-popup-overlay">
|
||||||
|
<div class="changelog-popup">
|
||||||
|
<div class="changelog-header">
|
||||||
|
<h2>What's New in LoLLMs</h2>
|
||||||
|
<button class="close-button" @click="closePopup">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="changelog-content markdown-body" v-html="parsedChangelogContent"></div>
|
||||||
|
<div class="changelog-footer">
|
||||||
|
<button class="understood-button" @click="handleUnderstand">
|
||||||
|
I understood
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import DOMPurify from 'dompurify'; // For security
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChangelogPopup',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showChangelogPopup: false,
|
||||||
|
changelogContent: '',
|
||||||
|
currentVersion: '0.0.0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
parsedChangelogContent() {
|
||||||
|
// Convert markdown to HTML and sanitize
|
||||||
|
return DOMPurify.sanitize(marked(this.changelogContent));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.checkChangelogUpdate();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async checkChangelogUpdate() {
|
||||||
|
try {
|
||||||
|
// Get current changelog
|
||||||
|
const changelogResponse = await axios.get("/get_changelog");
|
||||||
|
this.changelogContent = changelogResponse.data;
|
||||||
|
|
||||||
|
// Extract version from changelog
|
||||||
|
let res = await axios.get('/get_lollms_webui_version', {});
|
||||||
|
if (res) {
|
||||||
|
res = res.data
|
||||||
|
if(res.version_type != "") {
|
||||||
|
this.$store.state.version = `${res.version_main}.${res.version_secondary} ${res.version_type} (${res.version_codename})`
|
||||||
|
} else {
|
||||||
|
this.$store.state.version = `${res.version_main}.${res.version_secondary} (${res.version_codename})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentVersion = this.$store.state.version
|
||||||
|
console.log("checkChangelogUpdate")
|
||||||
|
console.log(this.$store.state.version)
|
||||||
|
|
||||||
|
// Get last viewed version
|
||||||
|
const lastViewedResponse = await axios.get("/get_last_viewed_changelog_version");
|
||||||
|
const lastViewedVersion = lastViewedResponse.data;
|
||||||
|
|
||||||
|
// Show popup if versions don't match
|
||||||
|
if (this.currentVersion !== lastViewedVersion) {
|
||||||
|
console.log("Showing changelog")
|
||||||
|
this.showChangelogPopup = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking changelog:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleUnderstand() {
|
||||||
|
try {
|
||||||
|
await axios.post("/set_last_viewed_changelog_version", {
|
||||||
|
client_id: this.$store.state.client_id,
|
||||||
|
version: this.currentVersion
|
||||||
|
});
|
||||||
|
this.closePopup();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error setting changelog version:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closePopup() {
|
||||||
|
this.showChangelogPopup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.changelog-popup-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-popup {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-header {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-content {
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.understood-button {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown Styles */
|
||||||
|
.markdown-body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body h1,
|
||||||
|
.markdown-body h2,
|
||||||
|
.markdown-body h3,
|
||||||
|
.markdown-body h4,
|
||||||
|
.markdown-body h5,
|
||||||
|
.markdown-body h6 {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body h1 { font-size: 2em; }
|
||||||
|
.markdown-body h2 { font-size: 1.5em; }
|
||||||
|
.markdown-body h3 { font-size: 1.25em; }
|
||||||
|
|
||||||
|
.markdown-body p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body ul,
|
||||||
|
.markdown-body ol {
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body code {
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
background-color: rgba(27,31,35,0.05);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body pre {
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body blockquote {
|
||||||
|
padding: 0 1em;
|
||||||
|
color: #6a737d;
|
||||||
|
border-left: 0.25em solid #dfe2e5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
223
web/src/components/ThinkingBlock.vue
Normal file
223
web/src/components/ThinkingBlock.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<!-- ThinkingBlock.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="my-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
@click="toggle"
|
||||||
|
class="group flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors duration-200 rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
:aria-expanded="isOpen"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-4 h-4 transition-transform duration-200"
|
||||||
|
:class="{ 'rotate-90': isOpen }"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span v-if="isThinking" class="flex items-center gap-2">
|
||||||
|
Thinking
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<div v-for="i in 3" :key="i"
|
||||||
|
class="w-1 h-1 bg-blue-500 rounded-full animate-pulse"
|
||||||
|
:style="{ animationDelay: `${(i-1)*150}ms` }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span v-else>AI Thoughts</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="!isThinking && content"
|
||||||
|
@click="downloadMarkdown"
|
||||||
|
class="px-2 py-1 text-xs font-medium text-gray-600 hover:text-gray-900 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||||
|
title="Download as Markdown"
|
||||||
|
>
|
||||||
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition duration-200 ease-out"
|
||||||
|
enter-from-class="transform scale-95 opacity-0"
|
||||||
|
enter-to-class="transform scale-100 opacity-100"
|
||||||
|
leave-active-class="transition duration-150 ease-in"
|
||||||
|
leave-from-class="transform scale-100 opacity-100"
|
||||||
|
leave-to-class="transform scale-95 opacity-0"
|
||||||
|
>
|
||||||
|
<div v-if="isOpen" class="mt-2 text-gray-600">
|
||||||
|
<div
|
||||||
|
ref="contentContainer"
|
||||||
|
class="p-4 rounded-lg prose prose-sm max-w-none overflow-y-auto max-h-[500px]"
|
||||||
|
>
|
||||||
|
<div v-if="$slots.default">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div v-else v-html="renderedContent"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
highlight: function(code, lang) {
|
||||||
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
|
return hljs.highlight(code, { language: lang }).value;
|
||||||
|
}
|
||||||
|
return hljs.highlightAuto(code).value;
|
||||||
|
},
|
||||||
|
breaks: true,
|
||||||
|
gfm: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ThinkingBlock',
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isDone: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isOpen: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isThinking() {
|
||||||
|
return !this.isDone
|
||||||
|
},
|
||||||
|
renderedContent() {
|
||||||
|
return DOMPurify.sanitize(marked.parse(this.content));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
content: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
if (this.$refs.contentContainer) {
|
||||||
|
const container = this.$refs.contentContainer;
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
downloadMarkdown() {
|
||||||
|
const blob = new Blob([this.content], { type: 'text/markdown' });
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', 'ai_thoughts.md');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Base markdown styles */
|
||||||
|
.prose {
|
||||||
|
@apply text-inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h1 {
|
||||||
|
@apply text-2xl font-bold mb-4 mt-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h2 {
|
||||||
|
@apply text-xl font-bold mb-3 mt-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h3 {
|
||||||
|
@apply text-lg font-bold mb-2 mt-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose p {
|
||||||
|
@apply mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ul {
|
||||||
|
@apply list-disc pl-5 mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ol {
|
||||||
|
@apply list-decimal pl-5 mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose code {
|
||||||
|
@apply px-1 py-0.5 rounded text-sm font-mono bg-opacity-10 bg-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre {
|
||||||
|
@apply p-4 rounded-lg overflow-x-auto mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre code {
|
||||||
|
@apply bg-transparent p-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose blockquote {
|
||||||
|
@apply pl-4 border-l-4 border-gray-300 italic my-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a {
|
||||||
|
@apply text-blue-600 hover:text-blue-800 underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
.prose::-webkit-scrollbar {
|
||||||
|
@apply w-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose::-webkit-scrollbar-track {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-300 rounded-full hover:bg-gray-400 transition-colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scrolling */
|
||||||
|
.prose {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user