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