🐛 | Fixed widget window size and settings tab formating.

This commit is contained in:
2026-01-26 17:42:14 +02:00
parent 8219e5c1a2
commit 55917b9d1a
4 changed files with 111 additions and 81 deletions

View File

@@ -9,18 +9,17 @@ import org.kde.plasma.plasma5support as Plasma5Support
PlasmoidItem {
id: root
width: 500
height: 750
property string system_prompt: plasmoid.configuration.systemPrompt
Connections {
target: plasmoid.configuration
function onSystemPromptChanged() {
root.system_prompt = plasmoid.configuration.systemPrompt
root.clearChat()
root.system_prompt = plasmoid.configuration.systemPrompt;
root.clearChat();
}
}
property string ollamaUrl: "http://localhost:11434"
property var models: []
property string currentModel: ""
@@ -28,55 +27,59 @@ PlasmoidItem {
property string pendingMessageText: ""
property var chatHistory: []
property string chatText: ""
property bool isWaiting: false // New: Loading state
property bool isWaiting: false
function clearChat() {
chatHistory = []
chatText = ""
selectedImagePath = ""
isWaiting = false
appendMessage("system", system_prompt)
chatHistory = [];
chatText = "";
selectedImagePath = "";
isWaiting = false;
appendMessage("system", system_prompt);
}
function appendMessage(role, content, imageSource) {
var msgObj = { role: role, content: content }
chatHistory.push(msgObj)
var msgObj = {
role: role,
content: content
};
chatHistory.push(msgObj);
let userColor = Kirigami.Theme.highlightColor
let assistantColor = Kirigami.Theme.positiveTextColor
let systemColor = Kirigami.Theme.disabledTextColor
let userColor = Kirigami.Theme.highlightColor;
let assistantColor = Kirigami.Theme.positiveTextColor;
let systemColor = Kirigami.Theme.disabledTextColor;
if (chatText !== "") {
chatText += "<br>"
chatText += "<br>";
}
if (role === "user") {
chatText += `<b style="color:${userColor}">You:</b> ${content}`
chatText += `<b style="color:${userColor}">You:</b> ${content}`;
if (imageSource) {
chatText += `<br><img src="${imageSource}" width="150" style="border-radius:4px;">`
chatText += `<br><img src="${imageSource}" width="150" style="border-radius:4px;">`;
}
} else if (role === "assistant") {
chatText += `<b style="color:${assistantColor}">Ollama:</b> ${content}`
chatText += `<b style="color:${assistantColor}">Ollama:</b> ${content}`;
} else if (role === "system") {
chatText += `<i style="color:${systemColor}">System: ${content}</i>`
chatText += `<i style="color:${systemColor}">System: ${content}</i>`;
}
}
function fetchModels() {
let xhr = new XMLHttpRequest()
xhr.open("GET", ollamaUrl + "/api/tags")
let xhr = new XMLHttpRequest();
xhr.open("GET", ollamaUrl + "/api/tags");
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
let res = JSON.parse(xhr.responseText)
let list = []
let res = JSON.parse(xhr.responseText);
let list = [];
if (res.models) {
res.models.forEach(m => list.push(m.name))
models = list
if (list.length > 0 && currentModel === "") currentModel = list[0]
res.models.forEach(m => list.push(m.name));
models = list;
if (list.length > 0 && currentModel === "")
currentModel = list[0];
}
}
}
xhr.send()
};
xhr.send();
}
Plasma5Support.DataSource {
@@ -85,68 +88,71 @@ PlasmoidItem {
connectedSources: []
onNewData: (sourceName, data) => {
if (data["stdout"]) {
var base64Str = data["stdout"].toString().trim()
root.sendMessage(root.pendingMessageText, base64Str)
disconnectSource(sourceName)
var base64Str = data["stdout"].toString().trim();
root.sendMessage(root.pendingMessageText, base64Str);
disconnectSource(sourceName);
}
}
}
function processAndSendMessage(msg) {
if (!currentModel || !msg || isWaiting) return
isWaiting = true
if (selectedImagePath !== "") {
root.pendingMessageText = msg
// Robust path cleaning for Linux
let cleanPath = decodeURIComponent(selectedImagePath.toString().replace("file://", ""))
executableSource.connectSource("base64 -w 0 \"" + cleanPath + "\"")
} else {
sendMessage(msg, null)
}
if (!currentModel || !msg || isWaiting)
return;
isWaiting = true;
if (selectedImagePath !== "") {
root.pendingMessageText = msg;
// Robust path cleaning for Linux
let cleanPath = decodeURIComponent(selectedImagePath.toString().replace("file://", ""));
executableSource.connectSource("base64 -w 0 \"" + cleanPath + "\"");
} else {
sendMessage(msg, null);
}
}
function sendMessage(msg, base64Image) {
if (chatHistory.length === 0) {
appendMessage("system", system_prompt)
appendMessage("system", system_prompt);
}
// 1. Add to local UI history (this handles the visual part)
appendMessage("user", msg, base64Image ? selectedImagePath : null)
appendMessage("user", msg, base64Image ? selectedImagePath : null);
// Reset image selection immediately after UI update
selectedImagePath = ""
selectedImagePath = "";
// 2. Prepare the messages for the API call
// We map the existing history first
let requestMessages = chatHistory.map(m => ({ role: m.role, content: m.content }))
let requestMessages = chatHistory.map(m => ({
role: m.role,
content: m.content
}));
// 3. IMPORTANT: Add the image to the LAST message in the request list
if (base64Image && requestMessages.length > 0) {
requestMessages[requestMessages.length - 1].images = [base64Image]
requestMessages[requestMessages.length - 1].images = [base64Image];
}
let xhr = new XMLHttpRequest()
xhr.open("POST", ollamaUrl + "/api/chat")
xhr.setRequestHeader("Content-Type", "application/json")
let xhr = new XMLHttpRequest();
xhr.open("POST", ollamaUrl + "/api/chat");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
isWaiting = false
isWaiting = false;
if (xhr.status === 200) {
let res = JSON.parse(xhr.responseText)
appendMessage("assistant", res.message.content)
let res = JSON.parse(xhr.responseText);
appendMessage("assistant", res.message.content);
} else {
chatText += `<br><font color="orange">Error: Check if Ollama is running.</font>`
chatText += `<br><font color="orange">Error: Check if Ollama is running.</font>`;
}
}
}
};
// Now requestMessages contains the image data in the last 'user' object
xhr.send(JSON.stringify({
model: currentModel,
messages: requestMessages,
stream: false
}))
}));
}
// Hacky workaround the Kirigami theme loading bug...
@@ -157,29 +163,32 @@ PlasmoidItem {
repeat: false
onTriggered: {
if (root.chatHistory.length === 0) {
root.appendMessage("system", root.system_prompt)
root.appendMessage("system", root.system_prompt);
}
}
}
Component.onCompleted: {
fetchModels()
initTimer.start()
plasmoid.configurationRequired = true
fetchModels();
initTimer.start();
plasmoid.configurationRequired = true;
}
FileDialog {
id: fileDialog
title: "Select an Image"
nameFilters: [ "Images (*.png *.jpg *.jpeg *.webp)" ]
nameFilters: ["Images (*.png *.jpg *.jpeg *.webp)"]
onAccepted: root.selectedImagePath = selectedFile
}
fullRepresentation: ColumnLayout {
anchors.fill: parent
anchors.margins: 10
anchors.margins: 8
spacing: 8
Layout.minimumWidth: Math.max(Screen.width * .20, 400)
Layout.minimumHeight: Math.max(Screen.height * .20, 200)
// New Title Bar
RowLayout {
Layout.fillWidth: true
@@ -198,10 +207,12 @@ PlasmoidItem {
onActivated: root.currentModel = currentText
}
PlasmaComponents.Button {
icon.name: "view-refresh"; onClicked: root.fetchModels()
icon.name: "view-refresh"
onClicked: root.fetchModels()
}
PlasmaComponents.Button {
icon.name: "edit-clear-all"; onClicked: root.clearChat()
icon.name: "edit-clear-all"
onClicked: root.clearChat()
}
PlasmaComponents.Button {
@@ -212,13 +223,17 @@ PlasmoidItem {
onClicked: root.hideOnWindowDeactivate = !checked
// Tooltip to explain the button
PlasmaComponents.ToolTip { text: "Keep Open" }
PlasmaComponents.ToolTip {
text: "Keep Open"
}
}
}
}
// Horizontal Line separator
Kirigami.Separator { Layout.fillWidth: true }
Kirigami.Separator {
Layout.fillWidth: true
}
// Chat View
PlasmaComponents.ScrollView {
@@ -233,7 +248,7 @@ PlasmoidItem {
wrapMode: TextEdit.WordWrap
onTextChanged: {
// Force scroll to bottom
cursorPosition = length
cursorPosition = length;
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
@@ -268,7 +283,8 @@ PlasmoidItem {
fillMode: Image.PreserveAspectFit
}
PlasmaComponents.Label {
text: "Image attached"; Layout.fillWidth: true
text: "Image attached"
Layout.fillWidth: true
}
PlasmaComponents.Button {
icon.name: "edit-delete"
@@ -290,16 +306,16 @@ PlasmoidItem {
placeholderText: "Type message..."
enabled: !root.isWaiting
onAccepted: {
root.processAndSendMessage(text)
text = ""
root.processAndSendMessage(text);
text = "";
}
}
PlasmaComponents.Button {
icon.name: "mail-send"
enabled: !root.isWaiting && inputField.text !== ""
onClicked: {
root.processAndSendMessage(inputField.text)
inputField.text = ""
root.processAndSendMessage(inputField.text);
inputField.text = "";
}
}
}