Compare commits

..

No commits in common. "main" and "v1.0.1" have entirely different histories.
main ... v1.0.1

6 changed files with 29 additions and 73 deletions

View file

@ -9,8 +9,7 @@ AI assistant plugin for [DankMaterialShell](https://github.com/AvengeMedia/DankM
|-|-|-| |-|-|-|
|![alt text](resources/attachments/chat.png) | ![alt text](resources/attachments/coding.png) | ![alt text](resources/attachments/settings.png) |![alt text](resources/attachments/chat.png) | ![alt text](resources/attachments/coding.png) | ![alt text](resources/attachments/settings.png)
## Usage
- Toggle the plugin: `dms ipc call plugins toggle nexusAi`
## Features ## Features
- **Multi-Provider Ready:** Connect to standard cloud APIs or local providers. - **Multi-Provider Ready:** Connect to standard cloud APIs or local providers.
- **Vision Support:** Models with vision can be used with image attachments, even from clipboard - **Vision Support:** Models with vision can be used with image attachments, even from clipboard
@ -20,6 +19,7 @@ AI assistant plugin for [DankMaterialShell](https://github.com/AvengeMedia/DankM
## Requirements ## Requirements
- `curl` - `curl`
- `wl-copy`
- That's it, you probably already have them - That's it, you probably already have them
## Roadmap ## Roadmap

View file

@ -1,8 +1,8 @@
{ {
"id": "nexusAi", "id": "nexusAI",
"name": "Nexus AI", "name": "Nexus AI",
"description": "AI assistant, made to be useful", "description": "AI assistant, made to be useful",
"version": "1.0.7", "version": "1.0.0",
"author": "myNicole98", "author": "myNicole98",
"type": "daemon", "type": "daemon",
"capabilities": ["slideout"], "capabilities": ["slideout"],

View file

@ -24,13 +24,10 @@ Item {
+ (dark ? "dark" : "light") + "/" + stem + ".svg").toString() + (dark ? "dark" : "light") + "/" + stem + ".svg").toString()
: "" : ""
readonly property string _alt: stem.length > 0 readonly property string _alt: stem.length > 0
&& primaryImg.status === Image.Error
? Qt.resolvedUrl("../../resources/icons/" ? Qt.resolvedUrl("../../resources/icons/"
+ (dark ? "light" : "dark") + "/" + stem + ".svg").toString() + (dark ? "light" : "dark") + "/" + stem + ".svg").toString()
: "" : ""
readonly property string _model: stem.length > 0 readonly property string _model: stem.length > 0
&& primaryImg.status === Image.Error
&& altImg.status === Image.Error
? Qt.resolvedUrl("../../resources/icons/models/" + stem + ".svg").toString() ? Qt.resolvedUrl("../../resources/icons/models/" + stem + ".svg").toString()
: "" : ""

View file

@ -558,39 +558,30 @@ Rectangle {
} }
// Streaming indicator before first content: a plain status // Streaming indicator before first content: the tumbling hex
// label breathing toward the accent — same 750 ms cadence as // sphere (same small config as the footer status row) with a
// the tool chip's in-flight pulse. // plain status label.
Text { Row {
id: promptRow id: promptRow
visible: isStreaming && content.length === 0 && thinking.length === 0 visible: isStreaming && content.length === 0 && thinking.length === 0
text: "Processing prompt" spacing: 8
font.pixelSize: 11
// 0 → 1 → 0 while the label shows; lerps the color toward HexSphereLogo {
// the accent (k = 0.55, matching breatheColor on the chips). radius: 11
property real breathe: 0 strokeWidth: 1.5
SequentialAnimation on breathe { subdivisions: 1
running: promptRow.visible yawRate: 20
loops: Animation.Infinite pitchRate: 8
NumberAnimation { interactive: false
from: 0; to: 1; duration: 750 anchors.verticalCenter: parent.verticalCenter
easing.type: Easing.InOutSine visible: promptRow.visible
}
NumberAnimation {
from: 1; to: 0; duration: 750
easing.type: Easing.InOutSine
}
} }
color: {
var k = breathe * 0.55; Text {
var base = Theme.surfaceVariantText; anchors.verticalCenter: parent.verticalCenter
var accent = Theme.primary; text: "Processing prompt"
return Qt.rgba( color: Theme.surfaceVariantText
base.r + (accent.r - base.r) * k, font.pixelSize: 11
base.g + (accent.g - base.g) * k,
base.b + (accent.b - base.b) * k,
1);
} }
} }
@ -714,7 +705,7 @@ Rectangle {
visible: isUser && bubble.content.length > 0 visible: isUser && bubble.content.length > 0
iconName: "content_copy" iconName: "content_copy"
onClicked: Quickshell.execDetached( onClicked: Quickshell.execDetached(
["dms", "cl", "copy", bubble.content]) ["wl-copy", "--", bubble.content])
} }
HoverIcon { HoverIcon {
visible: isUser visible: isUser
@ -735,7 +726,7 @@ Rectangle {
visible: !isUser && bubble.content.length > 0 visible: !isUser && bubble.content.length > 0
iconName: "content_copy" iconName: "content_copy"
onClicked: Quickshell.execDetached( onClicked: Quickshell.execDetached(
["dms", "cl", "copy", bubble.content]) ["wl-copy", "--", bubble.content])
} }
Text { Text {
@ -869,7 +860,7 @@ Rectangle {
iconName: "content_copy" iconName: "content_copy"
iconColor: Theme.surfaceVariantText iconColor: Theme.surfaceVariantText
onClicked: Quickshell.execDetached( onClicked: Quickshell.execDetached(
["dms", "cl", "copy", codeRect.rawCode]) ["wl-copy", "--", codeRect.rawCode])
} }
} }
} }

View file

@ -107,34 +107,6 @@ Rectangle {
return pruned; return pruned;
} }
// Generate a dynamic cache during model search that returns
// the same array instead of rebuild the list at every keystroke
property var _rowsCache: []
function rowsFor(filter) {
var rows = buildRows(filter);
var old = _rowsCache;
if (old.length === rows.length) {
var same = true;
for (var i = 0; i < rows.length; i++) {
var a = old[i], b = rows[i];
if (!a || !b
|| a.kind !== b.kind
|| a.model !== b.model
|| a.providerId !== b.providerId
|| a.type !== b.type
|| a.name !== b.name
|| a.pid !== b.pid
|| a.collapsed !== b.collapsed) {
same = false;
break;
}
}
if (same) return old;
}
_rowsCache = rows;
return rows;
}
// Model zone (left): label + chevron. Hover highlights the zone // Model zone (left): label + chevron. Hover highlights the zone
// (not the tune section) and shows the pointing cursor. // (not the tune section) and shows the pointing cursor.
Item { Item {
@ -280,10 +252,7 @@ Rectangle {
clip: true clip: true
spacing: 2 spacing: 2
model: selector.service model: selector.service
? selector.rowsFor(manualEntry.text) : [] ? selector.buildRows(manualEntry.text) : []
// Recycle delegates on scroll instead of destroying
// and recreating them (each row is ~30 objects).
reuseItems: true
delegate: Item { delegate: Item {
id: rowDelegate id: rowDelegate

View file

@ -33,7 +33,7 @@ Item {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
Theme.surfaceContainer.b) Theme.surfaceContainer.b, SettingsData.popupTransparency)
} }
Flickable { Flickable {
@ -54,7 +54,6 @@ Item {
// ── Header row ──────────────────────────────────────────── // ── Header row ────────────────────────────────────────────
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16
Text { Text {
text: "Settings" text: "Settings"
color: Theme.surfaceText color: Theme.surfaceText