Files
community-plugins/deepseek_usage/panel.luau
T
Priyanshu GuptaandGitHub 8aa75342f3 add(deepseek_usage): DeepSeek API credit & balance monitor (#220)
* add(deepseek_usage): add DeepSeek usage balance monitor plugin

* add(deepseek_usage): add thumbnail.webp

* chore(deepseek_usage): update plugin id to coder/deepseek_usage

* fix(deepseek_usage): update 960x540 thumbnail, README IPC docs, and plugin.toml schema

* fix(deepseek_usage): fix panel ID, normalize ui.graph values 0-1, declare xdg-open dependency

* fix(deepseek_usage): replace thumbnail with official Noctalia generator asset
2026-08-03 16:33:41 -04:00

236 lines
6.7 KiB
Luau

--!nonstrict
-- DeepSeek Usage Panel Entry — Graphical Balance & Top-Up Dashboard
local CURRENCY_SYMBOLS = {
USD = "$",
CNY = "¥",
EUR = "€",
GBP = "£",
JPY = "¥",
}
local function formatBalance(balance: number?, currency: string?): string
if balance == nil then
return "--.--"
end
local curr = currency or "USD"
local symbol = CURRENCY_SYMBOLS[curr]
if symbol then
return string.format("%s%.2f", symbol, balance)
else
return string.format("%.2f %s", balance, curr)
end
end
local state = {
balance = nil :: number?,
currency = "USD",
lastUpdated = nil :: string?,
errorMsg = nil :: string?,
isFetching = false,
}
local history = {}
function openTopUpPage()
noctalia.runAsync("xdg-open https://platform.deepseek.com/top_up")
end
function triggerRefresh()
noctalia.state.set("deepseek.refresh_requested", true)
end
function onClosePanel()
panel.close()
end
function onOpenSettings()
noctalia.openSettings()
end
local function buildGraphValues(): ({ number }, number, number)
if #history == 0 then
return { 0 }, 0, 0
end
local minVal = history[1].balance
local maxVal = history[1].balance
for _, pt in ipairs(history) do
if pt.balance < minVal then minVal = pt.balance end
if pt.balance > maxVal then maxVal = pt.balance end
end
local range = maxVal - minVal
local values = {}
for _, pt in ipairs(history) do
table.insert(values, range > 0 and (pt.balance - minVal) / range or 0.5)
end
return values, minVal, maxVal
end
local function renderGraphSection(values: { number }, minVal: number, maxVal: number)
-- graph props unverified — adjust after hot-reload test
local graphOk, graphNode = pcall(function()
return ui.graph({
values = values,
height = 64,
color = "primary",
})
end)
local graphElement
if graphOk and type(graphNode) == "table" then
graphElement = graphNode
else
graphElement = ui.label({
text = string.format("History (%d data points)", #history),
fontSize = 11,
color = "on_surface_variant",
})
end
return ui.column({ gap = 6 }, {
ui.row({ justify = "space_between", align = "center" }, {
ui.label({ text = noctalia.tr("panel.history_title"), fontSize = 12, fontWeight = "medium", color = "on_surface_variant" }),
ui.label({ text = string.format("%d samples", #history), fontSize = 10, color = "on_surface_variant" }),
}),
ui.column({
fill = "surface_variant/0.2",
radius = 8,
padding = 8,
gap = 4,
}, {
graphElement,
ui.row({ justify = "space_between" }, {
ui.label({ text = string.format("Min: %s", formatBalance(minVal, state.currency)), fontSize = 10, color = "on_surface_variant" }),
ui.label({ text = string.format("Max: %s", formatBalance(maxVal, state.currency)), fontSize = 10, color = "on_surface_variant" }),
}),
}),
})
end
local function render()
local apiKey = noctalia.getConfig("api_key") or ""
local isUnconfigured = apiKey == ""
local values, minVal, maxVal = buildGraphValues()
local balanceDisplay = formatBalance(state.balance, state.currency)
-- Header Row
local headerRow = ui.row({ align = "center", justify = "space_between", gap = 8 }, {
ui.row({ gap = 8, align = "center" }, {
ui.glyph({ name = "wallet", size = 18, color = "primary" }),
ui.label({ text = noctalia.tr("panel.title"), fontSize = 16, fontWeight = "bold", color = "on_surface" }),
}),
ui.row({ gap = 4, align = "center" }, {
ui.button({ glyph = "refresh", variant = "ghost", onClick = "triggerRefresh", tooltip = noctalia.tr("panel.refresh_tooltip") }),
ui.button({ glyph = "close", variant = "ghost", onClick = "onClosePanel" }),
}),
})
-- Scrollable Body Items
local bodyItems = {}
-- Unconfigured State Banner
if isUnconfigured then
table.insert(bodyItems, ui.column({
fill = "surface_variant/0.5",
radius = 8,
padding = 12,
gap = 8,
align = "stretch",
}, {
ui.label({ text = noctalia.tr("panel.unconfigured_title"), fontWeight = "bold", color = "error" }),
ui.label({ text = noctalia.tr("panel.unconfigured_desc"), fontSize = 12, color = "on_surface_variant" }),
ui.button({
text = noctalia.tr("panel.open_settings"),
variant = "primary",
onClick = "onOpenSettings",
}),
}))
end
-- Error Banner
if not isUnconfigured and state.errorMsg ~= nil then
table.insert(bodyItems, ui.row({
fill = "error/0.15",
radius = 8,
padding = 10,
gap = 8,
align = "center",
}, {
ui.glyph({ name = "alert-circle", color = "error", size = 16 }),
ui.label({ text = state.errorMsg, color = "error", fontSize = 12, flexGrow = 1 }),
}))
end
-- Hero Balance Card
if not isUnconfigured then
table.insert(bodyItems, ui.column({
fill = "surface_variant/0.35",
radius = 12,
padding = 16,
align = "center",
gap = 6,
}, {
ui.label({ text = noctalia.tr("panel.current_balance"), fontSize = 12, color = "on_surface_variant" }),
ui.label({ text = balanceDisplay .. " " .. state.currency, fontSize = 28, fontWeight = "bold", color = "primary" }),
ui.label({ text = noctalia.tr("panel.wallet_info"), fontSize = 11, color = "on_surface_variant" }),
ui.spacer({ height = 4 }),
ui.button({
text = " " .. noctalia.tr("panel.add_credits") .. " ",
glyph = "external-link",
variant = "primary",
onClick = "openTopUpPage",
tooltip = noctalia.tr("panel.add_credits_tooltip"),
}),
}))
-- Graph / Trend Section
table.insert(bodyItems, renderGraphSection(values, minVal, maxVal))
end
-- Footer Status Info
local footerRow = ui.row({ justify = "space_between", align = "center" }, {
ui.label({
text = state.lastUpdated and (noctalia.tr("panel.updated_at") .. ": " .. state.lastUpdated) or "",
fontSize = 10,
color = "on_surface_variant",
}),
ui.button({
text = noctalia.tr("panel.settings_btn"),
variant = "ghost",
onClick = "onOpenSettings",
}),
})
panel.render(ui.column({ flexGrow = 1, gap = 10, padding = 12 }, {
headerRow,
ui.separator({}),
ui.scroll({ flexGrow = 1, gap = 10 }, bodyItems),
footerRow,
}))
end
function onOpen(_context)
state = noctalia.state.get("deepseek.state") or state
history = noctalia.state.get("deepseek.history") or {}
render()
end
noctalia.state.watch("deepseek.state", function(newState)
if type(newState) == "table" then
state = newState
render()
end
end)
noctalia.state.watch("deepseek.history", function(newHistory)
if type(newHistory) == "table" then
history = newHistory
render()
end
end)