Widen side cards, add icon badges and graphics, add wallpaper button
This commit is contained in:
+12
-5
@@ -2,24 +2,31 @@
|
||||
|
||||
PROJECTS_DIR="$HOME/.claude/projects"
|
||||
TODAY=$(date -u '+%Y-%m-%d')
|
||||
EMPTY='{"available":false,"tokens":0,"messages":0,"cacheHitPercent":0}'
|
||||
|
||||
if [ ! -d "$PROJECTS_DIR" ]; then
|
||||
echo '{"available":false,"tokens":0,"messages":0}'
|
||||
echo "$EMPTY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FILES=$(find "$PROJECTS_DIR" -name "*.jsonl" -newermt "$TODAY")
|
||||
|
||||
if [ -z "$FILES" ]; then
|
||||
echo '{"available":true,"tokens":0,"messages":0}'
|
||||
echo '{"available":true,"tokens":0,"messages":0,"cacheHitPercent":0}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
jq -s --arg today "$TODAY" '
|
||||
[.[] | select(.timestamp != null and (.timestamp | startswith($today)) and .message.usage != null) | .message.usage] as $usages
|
||||
| ([$usages[] | .input_tokens // 0] | add // 0) as $input
|
||||
| ([$usages[] | .output_tokens // 0] | add // 0) as $output
|
||||
| ([$usages[] | .cache_creation_input_tokens // 0] | add // 0) as $cacheCreate
|
||||
| ([$usages[] | .cache_read_input_tokens // 0] | add // 0) as $cacheRead
|
||||
| ($input + $cacheCreate + $cacheRead) as $totalInput
|
||||
| {
|
||||
available: true,
|
||||
tokens: ([$usages[] | (.input_tokens // 0) + (.output_tokens // 0) + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)] | add // 0),
|
||||
messages: ($usages | length)
|
||||
tokens: ($totalInput + $output),
|
||||
messages: ($usages | length),
|
||||
cacheHitPercent: (if $totalInput > 0 then (($cacheRead / $totalInput) * 100) else 0 end)
|
||||
}
|
||||
' $FILES 2>/dev/null || echo '{"available":false,"tokens":0,"messages":0}'
|
||||
' $FILES 2>/dev/null || echo "$EMPTY"
|
||||
|
||||
+43
-5
@@ -350,20 +350,27 @@
|
||||
}
|
||||
|
||||
.qs-side-panel {
|
||||
min-width: 170px;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.widget-card {
|
||||
padding: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.widget-card-icon {
|
||||
.widget-card-header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.widget-icon-badge {
|
||||
background-color: alpha(@accent, 0.18);
|
||||
border-radius: 8px;
|
||||
padding: 5px 8px;
|
||||
color: @accent;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.widget-card-title {
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: @foreground;
|
||||
}
|
||||
@@ -384,6 +391,37 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.widget-hero-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: @foreground;
|
||||
}
|
||||
|
||||
.widget-stat-row {
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.widget-stat-icon {
|
||||
color: @color5;
|
||||
font-size: 13px;
|
||||
min-width: 18px;
|
||||
}
|
||||
|
||||
.widget-level-bar {
|
||||
min-height: 6px;
|
||||
}
|
||||
|
||||
.widget-level-bar trough {
|
||||
min-height: 6px;
|
||||
border-radius: 6px;
|
||||
background-color: alpha(@foreground, 0.15);
|
||||
}
|
||||
|
||||
.widget-level-bar block.filled {
|
||||
border-radius: 6px;
|
||||
background-color: @accent;
|
||||
}
|
||||
|
||||
.control-center {
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
+13
-13
@@ -1,22 +1,24 @@
|
||||
import { Gtk } from "ags/gtk3"
|
||||
import { createPoll } from "ags/time"
|
||||
import { shell } from "../lib/utils"
|
||||
import { CardHeader, StatRow } from "./WidgetCard"
|
||||
|
||||
type ClaudeUsage = {
|
||||
available: boolean
|
||||
tokens: number
|
||||
messages: number
|
||||
cacheHitPercent: number
|
||||
}
|
||||
|
||||
const usage = createPoll<ClaudeUsage>(
|
||||
{ available: false, tokens: 0, messages: 0 },
|
||||
{ available: false, tokens: 0, messages: 0, cacheHitPercent: 0 },
|
||||
60000,
|
||||
shell("bash $HOME/.config/ags/lib/claude-usage.sh"),
|
||||
(stdout) => {
|
||||
try {
|
||||
return JSON.parse(stdout.trim())
|
||||
} catch {
|
||||
return { available: false, tokens: 0, messages: 0 }
|
||||
return { available: false, tokens: 0, messages: 0, cacheHitPercent: 0 }
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -29,11 +31,8 @@ function formatTokens(tokens: number) {
|
||||
|
||||
export function ClaudeUsageCard() {
|
||||
return (
|
||||
<box class="panel widget-card" vertical spacing={6}>
|
||||
<box spacing={8}>
|
||||
<label class="widget-card-icon" label={""} />
|
||||
<label class="widget-card-title" label="Claude Code" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
</box>
|
||||
<box class="panel widget-card" vertical spacing={10}>
|
||||
<CardHeader icon={""} title="Claude Code" />
|
||||
<label
|
||||
class="widget-card-empty"
|
||||
label="No usage logs found"
|
||||
@@ -41,14 +40,15 @@ export function ClaudeUsageCard() {
|
||||
halign={Gtk.Align.START}
|
||||
visible={usage.as((u) => !u.available)}
|
||||
/>
|
||||
<box vertical spacing={4} visible={usage.as((u) => u.available)}>
|
||||
<box vertical spacing={8} visible={usage.as((u) => u.available)}>
|
||||
<StatRow icon={""} label="Tokens today" value={usage.as((u) => formatTokens(u.tokens))} />
|
||||
<StatRow icon={""} label="Messages today" value={usage.as((u) => `${u.messages}`)} />
|
||||
<box vertical spacing={4}>
|
||||
<box spacing={8}>
|
||||
<label class="widget-card-label" label="Tokens today" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
<label class="widget-card-value" label={usage.as((u) => formatTokens(u.tokens))} />
|
||||
<label class="widget-card-label" label="Cache hit rate" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
<label class="widget-card-value" label={usage.as((u) => `${Math.round(u.cacheHitPercent)}%`)} />
|
||||
</box>
|
||||
<box spacing={8}>
|
||||
<label class="widget-card-label" label="Messages today" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
<label class="widget-card-value" label={usage.as((u) => `${u.messages}`)} />
|
||||
<levelbar class="widget-level-bar" value={usage.as((u) => u.cacheHitPercent / 100)} />
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createState } from "ags"
|
||||
import { createPoll, interval } from "ags/time"
|
||||
import { sh, shell } from "../lib/utils"
|
||||
import { ClaudeUsageCard } from "./ClaudeUsage"
|
||||
import { CardHeader, StatRow } from "./WidgetCard"
|
||||
|
||||
export const QUICKSETTINGS_WINDOW = "quick-settings"
|
||||
|
||||
@@ -91,6 +92,9 @@ function Header() {
|
||||
<label class="cc-clock" label={clock} xalign={0} halign={Gtk.Align.START} />
|
||||
<label class="cc-date" label={today} xalign={0} halign={Gtk.Align.START} />
|
||||
</box>
|
||||
<button class="panel-icon-btn" tooltipText="Change wallpaper" onClicked={() => { closePanel(); sh("blob_wallpaper") }}>
|
||||
<label label={""} />
|
||||
</button>
|
||||
<button class="panel-icon-btn" tooltipText="Lock" onClicked={() => { closePanel(); sh("omarchy-system-lock") }}>
|
||||
<label label={""} />
|
||||
</button>
|
||||
@@ -313,28 +317,50 @@ const uptime = createPoll("", 60000, shell("uptime -p"), (stdout) =>
|
||||
|
||||
function UptimeCard() {
|
||||
return (
|
||||
<box class="panel widget-card" vertical spacing={6}>
|
||||
<box spacing={8}>
|
||||
<label class="widget-card-icon" label={""} />
|
||||
<label class="widget-card-title" label="Uptime" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
</box>
|
||||
<label class="widget-card-value" label={uptime} xalign={0} halign={Gtk.Align.START} wrap />
|
||||
<box class="panel widget-card" vertical spacing={10}>
|
||||
<CardHeader icon={""} title="Uptime" />
|
||||
<label class="widget-hero-value" label={uptime} xalign={0} halign={Gtk.Align.START} wrap />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const weather = createPoll("Loading...", 600000, shell("omarchy-weather-status"), (stdout) =>
|
||||
stdout.trim(),
|
||||
type Weather = { ok: boolean; icon: string; place: string; temp: string; wind: string }
|
||||
|
||||
function parseWeather(raw: string): Weather {
|
||||
const parts = raw.split(" · ")
|
||||
if (parts.length < 3) return { ok: false, icon: "", place: raw, temp: "", wind: "" }
|
||||
const match = parts[0].match(/^(\S+)\s*(.*)$/)
|
||||
return {
|
||||
ok: true,
|
||||
icon: match ? match[1] : "",
|
||||
place: match ? match[2].trim() : parts[0].trim(),
|
||||
temp: parts[1].replace(/^Temp\s*/, ""),
|
||||
wind: parts[2].replace(/^Wind\s*/, ""),
|
||||
}
|
||||
}
|
||||
|
||||
const weather = createPoll<Weather>(
|
||||
{ ok: false, icon: "", place: "Loading...", temp: "", wind: "" },
|
||||
600000,
|
||||
shell("omarchy-weather-status"),
|
||||
(stdout) => parseWeather(stdout.trim()),
|
||||
)
|
||||
|
||||
function WeatherCard() {
|
||||
return (
|
||||
<box class="panel widget-card" vertical spacing={6}>
|
||||
<box spacing={8}>
|
||||
<label class="widget-card-icon" label={""} />
|
||||
<label class="widget-card-title" label="Weather" xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
<box class="panel widget-card" vertical spacing={10}>
|
||||
<CardHeader icon={weather.as((w) => w.icon)} title={weather.as((w) => w.place)} />
|
||||
<label
|
||||
class="widget-card-empty"
|
||||
label="Weather unavailable"
|
||||
xalign={0}
|
||||
halign={Gtk.Align.START}
|
||||
visible={weather.as((w) => !w.ok)}
|
||||
/>
|
||||
<box vertical spacing={8} visible={weather.as((w) => w.ok)}>
|
||||
<StatRow icon={""} label="Temperature" value={weather.as((w) => w.temp)} />
|
||||
<StatRow icon={""} label="Wind" value={weather.as((w) => w.wind)} />
|
||||
</box>
|
||||
<label class="widget-card-value" label={weather} xalign={0} halign={Gtk.Align.START} wrap />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Gtk } from "ags/gtk3"
|
||||
|
||||
export function CardHeader({ icon, title }: { icon: any; title: any }) {
|
||||
return (
|
||||
<box class="widget-card-header" spacing={10}>
|
||||
<box class="widget-icon-badge">
|
||||
<label label={icon} />
|
||||
</box>
|
||||
<label class="widget-card-title" label={title} xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
export function StatRow({ icon, label, value }: { icon: string; label: string; value: any }) {
|
||||
return (
|
||||
<box class="widget-stat-row" spacing={8}>
|
||||
<label class="widget-stat-icon" label={icon} />
|
||||
<label class="widget-card-label" label={label} xalign={0} halign={Gtk.Align.START} hexpand />
|
||||
<label class="widget-card-value" label={value} />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
@@ -146,6 +146,7 @@ check_file "$SCRIPT_DIR/ags/widget/Notifications.tsx" "$HOME_DIR/.config/ags/wid
|
||||
check_file "$SCRIPT_DIR/ags/widget/QuickSettings.tsx" "$HOME_DIR/.config/ags/widget/QuickSettings.tsx" "ags/widget/QuickSettings.tsx" || check_status=1
|
||||
check_file "$SCRIPT_DIR/ags/widget/SysMonitor.tsx" "$HOME_DIR/.config/ags/widget/SysMonitor.tsx" "ags/widget/SysMonitor.tsx" || check_status=1
|
||||
check_file "$SCRIPT_DIR/ags/widget/WallPicker.tsx" "$HOME_DIR/.config/ags/widget/WallPicker.tsx" "ags/widget/WallPicker.tsx" || check_status=1
|
||||
check_file "$SCRIPT_DIR/ags/widget/WidgetCard.tsx" "$HOME_DIR/.config/ags/widget/WidgetCard.tsx" "ags/widget/WidgetCard.tsx" || check_status=1
|
||||
check_file "$SCRIPT_DIR/waybar/style.css" "$HOME_DIR/.config/waybar/style.css" "waybar/style.css" || check_status=1
|
||||
check_file "$SCRIPT_DIR/elephant/menus/blob_background_selector.lua" "$HOME_DIR/.config/elephant/menus/blob_background_selector.lua" "elephant/menus/blob_background_selector.lua" || check_status=1
|
||||
check_file "$SCRIPT_DIR/branding/about.txt" "$HOME_DIR/.config/omarchy/branding/about.txt" "branding/about.txt" || check_status=1
|
||||
|
||||
Reference in New Issue
Block a user