Skip to content

Commit d0f71b8

Browse files
committed
Allow the UI to show version number and give the option of updating.
1 parent b8e6add commit d0f71b8

4 files changed

Lines changed: 89 additions & 13 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kady"
3-
version = "0.2.4"
3+
version = "0.2.5"
44
description = "Kady — K-Dense BYOK Agent"
55
readme = "README.md"
66
requires-python = ">=3.13"

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web",
3-
"version": "0.1.0",
3+
"version": "0.2.4",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

web/src/app/page.tsx

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ModelSelector, DEFAULT_MODEL, type Model } from "@/components/model-sel
3131
import { SkillsSelector, buildSkillsContext, type Skill } from "@/components/skills-selector";
3232
import { ProvenancePanel } from "@/components/provenance-panel";
3333
import { WorkflowsPanel } from "@/components/workflows-panel";
34+
import { APP_VERSION, useUpdateCheck } from "@/lib/version";
3435
import { useAgent, type ActivityItem } from "@/lib/use-agent";
3536
import { useConfig } from "@/lib/use-config";
3637
import { useSkills } from "@/lib/use-skills";
@@ -559,6 +560,7 @@ export default function ChatPage() {
559560
const isStreaming = status === "streaming" || status === "submitted";
560561
const sandbox = useSandbox(isStreaming);
561562
const config = useConfig();
563+
const { updateAvailable } = useUpdateCheck();
562564
const { skills: allSkills } = useSkills();
563565
const [copiedId, setCopiedId] = useState<string | null>(null);
564566
const [panelOpen, setPanelOpen] = useState(true);
@@ -711,17 +713,30 @@ export default function ChatPage() {
711713
<div className="flex h-dvh flex-col">
712714
{/* Header */}
713715
<header className="relative flex items-center justify-between border-b px-6 py-3">
714-
<a href="https://www.k-dense.ai" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2">
715-
<Image
716-
src="/brand/kdense-logo.png"
717-
alt="K-Dense BYOK"
718-
width={120}
719-
height={28}
720-
className="h-7 w-auto object-contain"
721-
priority
722-
/>
723-
<span className="text-sm font-semibold tracking-tight text-foreground/80">BYOK</span>
724-
</a>
716+
<div className="flex items-center gap-2">
717+
<a href="https://www.k-dense.ai" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2">
718+
<Image
719+
src="/brand/kdense-logo.png"
720+
alt="K-Dense BYOK"
721+
width={120}
722+
height={28}
723+
className="h-7 w-auto object-contain"
724+
priority
725+
/>
726+
<span className="text-sm font-semibold tracking-tight text-foreground/80">BYOK</span>
727+
</a>
728+
<span className="text-[11px] text-muted-foreground/60">v{APP_VERSION}</span>
729+
{updateAvailable && (
730+
<a
731+
href="https://github.com/K-Dense-AI/k-dense-byok"
732+
target="_blank"
733+
rel="noopener noreferrer"
734+
className="text-[11px] font-medium text-blue-500 hover:text-blue-400 transition-colors"
735+
>
736+
Update available
737+
</a>
738+
)}
739+
</div>
725740
<p className="absolute left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/60 tracking-wide select-none">
726741
Brought to you by K-Dense, Inc.
727742
</p>

web/src/lib/version.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
5+
export const APP_VERSION = "0.2.4";
6+
7+
const GITHUB_REPO = "K-Dense-AI/k-dense-byok";
8+
const CACHE_KEY = "kdense-update-check";
9+
10+
interface UpdateCheckResult {
11+
updateAvailable: boolean;
12+
latestVersion: string | null;
13+
}
14+
15+
function compareSemver(current: string, latest: string): boolean {
16+
const parse = (v: string) => v.split(".").map(Number);
17+
const [cMajor, cMinor, cPatch] = parse(current);
18+
const [lMajor, lMinor, lPatch] = parse(latest);
19+
if (lMajor !== cMajor) return lMajor > cMajor;
20+
if (lMinor !== cMinor) return lMinor > cMinor;
21+
return lPatch > cPatch;
22+
}
23+
24+
export function useUpdateCheck(): UpdateCheckResult {
25+
const [result, setResult] = useState<UpdateCheckResult>({
26+
updateAvailable: false,
27+
latestVersion: null,
28+
});
29+
30+
useEffect(() => {
31+
const cached = sessionStorage.getItem(CACHE_KEY);
32+
if (cached) {
33+
try {
34+
setResult(JSON.parse(cached));
35+
return;
36+
} catch {
37+
sessionStorage.removeItem(CACHE_KEY);
38+
}
39+
}
40+
41+
fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`)
42+
.then((res) => {
43+
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
44+
return res.json();
45+
})
46+
.then((data) => {
47+
const tag: string = data.tag_name ?? "";
48+
const latestVersion = tag.replace(/^v/, "");
49+
const updateAvailable =
50+
latestVersion.length > 0 && compareSemver(APP_VERSION, latestVersion);
51+
const value = { updateAvailable, latestVersion };
52+
sessionStorage.setItem(CACHE_KEY, JSON.stringify(value));
53+
setResult(value);
54+
})
55+
.catch(() => {
56+
// Network error or rate limit — silently ignore
57+
});
58+
}, []);
59+
60+
return result;
61+
}

0 commit comments

Comments
 (0)