Skip to content

Commit bbc1f41

Browse files
committed
release: v0.2.0
- Dashscope one-shot config add visual model (default qwen3.5-flash) - Local transcription model load/download separation, prevent auto-download - In-app update notification (sidebar card + release dialog) - GitHub Actions auto-release workflow - Various UI fixes and style refinements
1 parent 889410d commit bbc1f41

12 files changed

Lines changed: 517 additions & 48 deletions

File tree

.github/workflows/release.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Verify tag matches pyproject.toml
16+
run: |
17+
TAG="${{ github.ref_name }}"
18+
# Strip leading 'v'
19+
TAG_VER="${TAG#v}"
20+
TOML_VER=$(grep -E '^version\s*=' pyproject.toml | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
21+
if [ "$TAG_VER" != "$TOML_VER" ]; then
22+
echo "❌ Tag version '$TAG' does not match pyproject.toml version '$TOML_VER'"
23+
exit 1
24+
fi
25+
echo "✅ Tag $TAG matches pyproject.toml $TOML_VER"
26+
27+
- name: Create Release
28+
uses: softprops/action-gh-release@v2
29+
with:
30+
name: ${{ github.ref_name }}
31+
generate_release_notes: true
32+
make_latest: true

frontend/src/api/client.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@ async function request<T>(path: string, options?: RequestInit): Promise<T> {
1717
export const getHealth = () =>
1818
fetch("/health").then((r) => r.json()) as Promise<{ status: string }>
1919

20+
// ── Version / Update ──
21+
22+
export interface VersionInfo {
23+
version: string
24+
repo: string
25+
}
26+
27+
export const getVersion = () =>
28+
request<VersionInfo>("/version")
29+
30+
export interface GitHubRelease {
31+
tag_name: string
32+
html_url: string
33+
body: string
34+
}
35+
36+
/**
37+
* Check GitHub Releases for the latest version.
38+
* Uses a public endpoint (no auth needed, 60 req/hr per IP).
39+
*/
40+
export const checkLatestRelease = async (repo: string): Promise<GitHubRelease | null> => {
41+
try {
42+
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`)
43+
if (!res.ok) return null
44+
return res.json()
45+
} catch {
46+
return null
47+
}
48+
}
49+
2050
// ── Collections ──
2151

2252
export interface CollectionItem {

frontend/src/components/chat/source-detail-panel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Loader2, X, ChevronRight, ChevronDown, RefreshCw, Locate } from "lucide
88
import { TiptapEditor } from "@/components/ui/tiptap-editor"
99
import type { Editor } from "@tiptap/core"
1010
import { getFileChunks, getFilePreviewUrl, getDocSummary, generateDocSummary, setDocSummaryInclude, getExtractedText, type ChunkDetail, type DocSummary } from "@/api/client"
11-
import type { Source } from "@/stores/app-store"
11+
import { useAppStore, type Source } from "@/stores/app-store"
1212
import { toast } from "sonner"
1313

1414
interface SourceDetailPanelProps {
@@ -56,8 +56,10 @@ export function SourceDetailPanel({ source, onClose }: SourceDetailPanelProps) {
5656
const sourceEditorRef = useRef<Editor | null>(null)
5757

5858
const sourceName = source?.metadata?.source as string | undefined
59-
const collection = source?.metadata?.collection as string | undefined
59+
const collectionRaw = source?.metadata?.collection as string | undefined
6060
const chunkId = source?.metadata?.id as string | undefined
61+
const { collections } = useAppStore()
62+
const collection = collections.find(c => c.id === collectionRaw)?.name || collectionRaw
6163
const isPdfFile = sourceName ? _isPdf(sourceName) : false
6264

6365
const genKey = collection && sourceName ? _genKey(collection, sourceName) : null

frontend/src/components/database/info-panel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export function InfoPanel({ collection }: InfoPanelProps) {
4545
const [ingestedNotesCount, setIngestedNotesCount] = useState(0)
4646
const [docCount, setDocCount] = useState(0)
4747

48-
const { setSidebarView, setActiveMeeting, setPendingOpenFile } = useAppStore()
48+
const { setSidebarView, setActiveMeeting, setPendingOpenFile, collections } = useAppStore()
49+
50+
const collectionName = collections.find(c => c.id === collection)?.name || collection
4951

5052
useEffect(() => {
5153
setSummary(null)
@@ -153,7 +155,7 @@ export function InfoPanel({ collection }: InfoPanelProps) {
153155
setConsolidatingCollection(collection)
154156
try {
155157
const res = await triggerConsolidation(collection)
156-
toast.info(`Consolidation started for ${collection}...`)
158+
toast.info(`Consolidation started for ${collectionName}...`)
157159
const taskId = res.task?.id
158160
const targetCollection = collection
159161
if (taskId) {
@@ -166,7 +168,7 @@ export function InfoPanel({ collection }: InfoPanelProps) {
166168
setConsolidating(false)
167169
setConsolidatingCollection(null)
168170
if (task.status === "completed") {
169-
toast.success(`Consolidation complete for ${targetCollection}`)
171+
toast.success(`Consolidation complete for ${collectionName}`)
170172
if (collection === targetCollection) {
171173
fetchSummary()
172174
fetchProjectDescription()
Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import { useState } from "react"
12
import { cn } from "@/lib/utils"
23
import { useAppStore, type SidebarView } from "@/stores/app-store"
34
import { Button } from "@/components/ui/button"
5+
import { X, ArrowUpRight } from "lucide-react"
6+
import { useUpdateCheck } from "@/hooks/use-update-check"
7+
import { UpdateDialog } from "./update-dialog"
48

5-
/* Small diamond bullet — inline style overrides Tailwind SVG size rule */
9+
/* Small diamond bullet */
610
const DiamondDot = () => (
711
<svg style={{ width: "7px", height: "7px" }} viewBox="0 0 4 4" fill="currentColor" stroke="none">
812
<polygon points="2,0 4,2 2,4 0,2" />
@@ -19,47 +23,158 @@ const navItems: Array<{ view: SidebarView; label: string }> = [
1923

2024
export function Sidebar() {
2125
const { sidebarView, setSidebarView } = useAppStore()
26+
const { update, ignored, ignoreVersion, currentVersion } = useUpdateCheck()
27+
const [dialogOpen, setDialogOpen] = useState(false)
28+
const [leaving, setLeaving] = useState(false)
29+
30+
const showCard = update && !ignored && !leaving
31+
const showDot = update !== null
32+
33+
const handleIgnore = () => {
34+
setLeaving(true)
35+
setTimeout(() => {
36+
ignoreVersion()
37+
setLeaving(false)
38+
}, 250)
39+
}
2240

2341
return (
24-
<aside
25-
className="w-[172px] border-r border-border flex flex-col shrink-0 py-6 px-4 bg-background"
26-
>
27-
<nav className="flex flex-col flex-1">
28-
<div className="text-[14px] font-[300] uppercase tracking-[0.25em] text-muted-foreground mb-4">
29-
Navigate
30-
</div>
42+
<>
43+
<aside
44+
className="w-[172px] border-r border-border flex flex-col shrink-0 py-6 px-4 bg-background"
45+
>
46+
<nav className="flex flex-col flex-1">
47+
<div className="text-[14px] font-[300] uppercase tracking-[0.25em] text-muted-foreground mb-4">
48+
Navigate
49+
</div>
3150

32-
{navItems.map(({ view, label }) => (
33-
<div key={view} className="mb-0.5">
34-
<Button
35-
variant="ghost"
36-
className={cn(
37-
"w-full justify-start gap-2.5 py-2 px-0 h-auto text-xs uppercase tracking-wider relative rounded-none",
38-
"hover:bg-transparent hover:text-primary",
39-
sidebarView === view ? "font-[400] text-primary" : "font-[300] text-muted-foreground",
40-
)}
41-
onClick={() => setSidebarView(view)}
51+
{navItems.map(({ view, label }) => (
52+
<div key={view} className="mb-0.5">
53+
<Button
54+
variant="ghost"
55+
className={cn(
56+
"w-full justify-start gap-2.5 py-2 px-0 h-auto text-xs uppercase tracking-wider relative rounded-none",
57+
"hover:bg-transparent hover:text-primary",
58+
sidebarView === view ? "font-[400] text-primary" : "font-[300] text-muted-foreground",
59+
)}
60+
onClick={() => setSidebarView(view)}
61+
>
62+
<span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: "8px", height: "8px", flexShrink: 0, opacity: sidebarView === view ? 1 : 0.4, lineHeight: 0 }}>
63+
<DiamondDot />
64+
</span>
65+
{label}
66+
{sidebarView === view && (
67+
<span
68+
className="absolute bottom-0 left-0 h-[1.5px] w-5"
69+
style={{ background: "var(--ze-green)" }}
70+
/>
71+
)}
72+
</Button>
73+
</div>
74+
))}
75+
76+
{/* ── Bottom group: update card + version line ── */}
77+
<div className="mt-auto">
78+
<div
79+
className={cn(
80+
"overflow-hidden transition-all duration-300 ease-out",
81+
showCard ? "translate-x-0 opacity-100 max-h-32 mb-3" : "-translate-x-full opacity-0 max-h-0 mb-0"
82+
)}
83+
>
84+
{update && !ignored && (
85+
<div
86+
className="rounded-sm p-3"
87+
style={{
88+
border: "1px solid #1a3a2a",
89+
backgroundColor: "#faf7f2",
90+
boxShadow: "0 1px 3px rgba(0,0,0,0.04)",
91+
}}
4292
>
43-
<span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: "8px", height: "8px", flexShrink: 0, opacity: sidebarView === view ? 1 : 0.4, lineHeight: 0 }}>
44-
<DiamondDot />
93+
{/* Header row */}
94+
<div className="flex items-center justify-between mb-2">
95+
<div className="flex items-center gap-1.5">
96+
<span style={{ color: "#1a3a2a", lineHeight: 0 }}>
97+
<DiamondDot />
98+
</span>
99+
<span
100+
className="text-[10px] uppercase tracking-[0.12em]"
101+
style={{ color: "#1a3a2a", fontFamily: "var(--font-serif)" }}
102+
>
103+
New Version
104+
</span>
105+
</div>
106+
<button
107+
onClick={handleIgnore}
108+
className="text-muted-foreground/50 hover:text-muted-foreground transition-colors"
109+
title="Ignore this version"
110+
>
111+
<X className="h-3 w-3" />
112+
</button>
113+
</div>
114+
115+
{/* Version number */}
116+
<p
117+
className="text-sm font-light mb-3"
118+
style={{ fontFamily: "var(--font-serif)", color: "var(--ze-ink)" }}
119+
>
120+
{update.latestVersion}
121+
</p>
122+
123+
{/* Buttons */}
124+
<div className="flex items-center gap-2">
125+
<button
126+
onClick={handleIgnore}
127+
className="text-[10px] uppercase tracking-[0.08em] text-muted-foreground/60 hover:text-muted-foreground transition-colors"
128+
style={{ fontFamily: "var(--font-serif)" }}
129+
>
130+
Ignore
131+
</button>
132+
<button
133+
onClick={() => setDialogOpen(true)}
134+
className="text-[10px] uppercase tracking-[0.08em] font-medium transition-colors flex items-center gap-1"
135+
style={{ color: "#1a3a2a", fontFamily: "var(--font-serif)" }}
136+
>
137+
Update <ArrowUpRight className="h-2.5 w-2.5" />
138+
</button>
139+
</div>
140+
</div>
141+
)}
142+
</div>
143+
144+
{/* ── Version line ── */}
145+
<div className="pt-5 border-t border-dashed border-border">
146+
<div className="flex items-center justify-between">
147+
<span
148+
className="text-[10px] font-[300] tracking-[0.1em] text-muted-foreground/60"
149+
style={{ fontFamily: "var(--font-serif)" }}
150+
>
151+
v{currentVersion}
45152
</span>
46-
{label}
47-
{sidebarView === view && (
48-
<span
49-
className="absolute bottom-0 left-0 h-[1.5px] w-5"
50-
style={{ background: "var(--ze-green)" }}
51-
/>
153+
{showDot && (
154+
<button
155+
onClick={() => setDialogOpen(true)}
156+
className="flex items-center gap-1 text-[10px] font-[300] tracking-[0.08em] hover:opacity-80 transition-opacity"
157+
style={{ color: "#dc2626", fontFamily: "var(--font-serif)" }}
158+
title={`Update ${update.latestVersion} available`}
159+
>
160+
<span className="w-1 h-1 rounded-full" style={{ backgroundColor: "#dc2626" }} />
161+
{update.latestVersion.replace(/^v/, "")}
162+
</button>
52163
)}
53-
</Button>
164+
</div>
54165
</div>
55-
))}
56-
57-
<div className="mt-auto pt-5 border-t border-dashed border-border">
58-
<div className="text-[10px] font-[300] tracking-[0.1em] text-muted-foreground/60">
59-
SinkDuce v0.1 · RAG
60166
</div>
61-
</div>
62-
</nav>
63-
</aside>
167+
</nav>
168+
</aside>
169+
170+
{/* ── Update detail dialog ── */}
171+
{update && (
172+
<UpdateDialog
173+
open={dialogOpen}
174+
onOpenChange={setDialogOpen}
175+
update={update}
176+
/>
177+
)}
178+
</>
64179
)
65180
}

0 commit comments

Comments
 (0)