diff --git a/contents/config/config.qml b/contents/config/config.qml
index 8503c30..01fa2d3 100644
--- a/contents/config/config.qml
+++ b/contents/config/config.qml
@@ -3,7 +3,7 @@ import org.kde.plasma.configuration
ConfigModel {
ConfigCategory {
- name: i18n("General")
+ name: "General"
icon: "configure"
source: "configGeneral.qml"
}
diff --git a/contents/config/main.xml b/contents/config/main.xml
index cb1793a..f06100d 100644
--- a/contents/config/main.xml
+++ b/contents/config/main.xml
@@ -1,8 +1,10 @@
-
+
- You are a helpful assistant. Your answers are compact.
+
+ You are a helpful assistant. Your answers are compact.
+
diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml
index 75038e0..86702c3 100644
--- a/contents/ui/configGeneral.qml
+++ b/contents/ui/configGeneral.qml
@@ -1,14 +1,26 @@
import QtQuick
+import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
-Kirigami.FormLayout {
+Kirigami.Page {
id: page
property alias cfg_systemPrompt: systemPrompt.text
- QQC2.TextArea {
- id: systemPrompt
- text: page.cfg_systemPrompt
- placeholderText: page.cfg_systemPrompt.default
+ contentItem: ColumnLayout {
+ Kirigami.Heading {
+ text: "System Prompt"
+ level: 2
+ Layout.fillWidth: true
+ }
+
+ QQC2.TextArea {
+ id: systemPrompt
+ placeholderText: "Enter the system prompt here..."
+ Layout.fillWidth: true
+ Layout.preferredHeight: 240
+ wrapMode: TextEdit.Wrap
+ clip: true
+ }
}
}
diff --git a/contents/ui/main.qml b/contents/ui/main.qml
index aa4b97e..4a90270 100644
--- a/contents/ui/main.qml
+++ b/contents/ui/main.qml
@@ -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 += "
"
+ chatText += "
";
}
if (role === "user") {
- chatText += `You: ${content}`
+ chatText += `You: ${content}`;
if (imageSource) {
- chatText += `
`
+ chatText += `
`;
}
} else if (role === "assistant") {
- chatText += `Ollama: ${content}`
+ chatText += `Ollama: ${content}`;
} else if (role === "system") {
- chatText += `System: ${content}`
+ chatText += `System: ${content}`;
}
}
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 += `
Error: Check if Ollama is running.`
+ chatText += `
Error: Check if Ollama is running.`;
}
}
- }
+ };
// 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 = "";
}
}
}