Skip to content

Commit db3eac4

Browse files
release: bump to v2.0.4 with homepage, admin UI, and changelog
Ship the homepage/sidebar redesign, RoutinAI announcement banner, admin UI polish, and release notes since v2.0.3. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a71a441 commit db3eac4

68 files changed

Lines changed: 3840 additions & 2316 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
# Changelog
22

3+
## v2.0.4 - 2026-08-12
4+
5+
### Added
6+
- Added repository branch generation tasks, admin controls, and workspace isolation for multi-branch wiki builds.
7+
- Added repository scan plan controls and startup schema updates.
8+
- Added repository tree/explorer view for browsing public repositories.
9+
- Added SITE_URL support for public site URL configuration and SEO/sitemap generation.
10+
- Added Brazilian Portuguese (pt-BR) UI translations.
11+
- Added global MCP provider endpoint.
12+
- Added RoutinAI sponsorship top announcement banner with multi-language copy and dismiss state.
13+
- Added shared admin UI building blocks (page header, data table, pagination, status badge, breadcrumbs).
14+
15+
### Changed
16+
- Redesigned the main sidebar into Discover / Workspace / Tools groups with path-based active states.
17+
- Reworked the homepage into a compact workspace layout with tighter search and actions.
18+
- Refreshed admin dashboard and list pages for clearer hierarchy and denser operational layouts.
19+
- Localized admin UI and added language toggle to the admin header.
20+
- Improved sitemap generation with branch and language support.
21+
- Updated wiki generation workflow, including segmented content generation and repository page layout polish.
22+
23+
### Fixed
24+
- Fixed nested `.env` key loading and stale wiki model bindings for DeepSeek setups.
25+
- Fixed Gemini `finish_reason` normalization for the OpenAI SDK path.
26+
- Prevented incremental updates from wiping the wiki catalog; added regression coverage.
27+
- Fixed ZIP upload multipart/form-data handling.
28+
- Fixed source file links for non-GitHub hosts (e.g. Azure DevOps).
29+
- Fixed chat stream keepalive and code-block rendering issues.
30+
- Hardened branch generation locking/mutations and backfilled SQLite schema.
31+
- Escaped sitemap XML URLs.
32+
- Improved document generation error handling so partial failures do not always fail the whole repository job.
33+
- Fixed local Git source branch handling.
34+
335
## v2.0.3 - 2026-05-30
436

537
### Added

‎Directory.Packages.props‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<TimeStamp>$([System.DateTime]::UtcNow.ToString("yyyyMMdd"))</TimeStamp>
5-
<Version>2.0.3</Version>
5+
<Version>2.0.4</Version>
66
<!-- 项目信息 -->
77
<Product>OpenDeepWiki</Product>
88
<Title>OpenDeepWiki - AI驱动的代码知识库</Title>

‎web/app/(main)/page.tsx‎

Lines changed: 68 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function Home() {
2626
const [isFormOpen, setIsFormOpen] = useState(false);
2727
const [isIntegrationsOpen, setIsIntegrationsOpen] = useState(false);
2828
const [keyword, setKeyword] = useState("");
29-
const { isScrolled } = useScrollPosition(100);
29+
const { isScrolled } = useScrollPosition(80);
3030

3131
const handleSubmitSuccess = useCallback(() => {
3232
setIsFormOpen(false);
@@ -50,78 +50,88 @@ export default function Home() {
5050
visible: isScrolled,
5151
}}
5252
>
53-
<div className="flex flex-1 flex-col p-4">
54-
{/* Hero Section with Main Search Box */}
55-
<div className="flex flex-col items-center justify-center py-12">
56-
<div className="w-full max-w-2xl space-y-8">
57-
<h1 className="text-center text-4xl font-medium tracking-tight text-foreground">
58-
{t("home.title")}
59-
</h1>
60-
{/* Main Search Box with fade animation */}
53+
<div className="flex min-h-0 flex-1 flex-col">
54+
<section className="relative overflow-hidden border-b border-border/70">
55+
<div
56+
aria-hidden
57+
className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,color-mix(in_oklab,var(--foreground)_6%,transparent),transparent_58%)]"
58+
/>
59+
<div className="relative mx-auto flex w-full max-w-[1400px] flex-col gap-5 px-4 py-6 sm:px-6 lg:px-8">
60+
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
61+
<div className="min-w-0 space-y-1.5">
62+
<p className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
63+
{t("sidebar.explore")}
64+
</p>
65+
<h1 className="text-2xl font-semibold tracking-tight text-foreground sm:text-[1.75rem]">
66+
{t("home.title")}
67+
</h1>
68+
<p className="max-w-xl text-sm text-muted-foreground">
69+
{t("home.subtitle")}
70+
</p>
71+
</div>
72+
73+
<div className="flex flex-wrap items-center gap-2">
74+
<Dialog open={isFormOpen} onOpenChange={setIsFormOpen}>
75+
<Button
76+
className="h-9 gap-2 rounded-lg bg-teal-600 px-3.5 text-white hover:bg-teal-500"
77+
onClick={handleAddRepoClick}
78+
>
79+
<Plus className="h-4 w-4" />
80+
{t("home.addPrivateRepo")}
81+
</Button>
82+
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-2xl">
83+
{user && (
84+
<RepositorySubmitForm onSuccess={handleSubmitSuccess} />
85+
)}
86+
</DialogContent>
87+
</Dialog>
88+
<Button
89+
variant="outline"
90+
className="h-9 gap-2 rounded-lg border-border/80 bg-background/60"
91+
>
92+
<Flame className="h-4 w-4 text-orange-500" />
93+
{t("home.exploreTrending")}
94+
</Button>
95+
<Button
96+
variant="ghost"
97+
className="h-9 gap-2 rounded-lg text-muted-foreground hover:text-foreground"
98+
onClick={() => setIsIntegrationsOpen(true)}
99+
>
100+
<Puzzle className="h-4 w-4" />
101+
<span className="hidden sm:inline">{t("home.mcpIntegrationShort")}</span>
102+
<span className="sm:hidden">MCP</span>
103+
</Button>
104+
</div>
105+
</div>
106+
61107
<div
62108
className={cn(
63-
"relative transition-all duration-250 ease-in-out",
109+
"relative max-w-3xl transition-all duration-250 ease-out",
64110
isScrolled
65-
? "opacity-0 -translate-y-2 pointer-events-none"
66-
: "opacity-100 translate-y-0 pointer-events-auto"
111+
? "pointer-events-none -translate-y-1 opacity-0"
112+
: "translate-y-0 opacity-100"
67113
)}
68114
>
69-
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground">
70-
<Search className="h-5 w-5" />
71-
</div>
115+
<Search className="pointer-events-none absolute top-1/2 left-3.5 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
72116
<Input
73117
value={keyword}
74118
onChange={(e) => setKeyword(e.target.value)}
75119
placeholder={t("home.searchPlaceholder")}
76120
maxLength={100}
77-
className="h-14 rounded-full pl-12 text-lg shadow-sm transition-all hover:shadow-md focus-visible:ring-2 focus-visible:ring-primary/20 bg-secondary/50 border-transparent"
121+
className="h-11 rounded-xl border-border/70 bg-background/80 pl-10 text-sm shadow-none transition-[border-color,box-shadow] focus-visible:border-teal-500/40 focus-visible:ring-teal-500/15"
78122
/>
79123
</div>
80-
81-
<div className="flex flex-wrap items-center justify-center gap-3">
82-
<Dialog open={isFormOpen} onOpenChange={setIsFormOpen}>
83-
<Button
84-
variant="secondary"
85-
className="gap-2 rounded-full h-10 px-6 bg-teal-500/10 text-teal-500 hover:bg-teal-500/20 hover:text-teal-400 border border-teal-500/20"
86-
onClick={handleAddRepoClick}
87-
>
88-
<Plus className="h-4 w-4" />
89-
{t("home.addPrivateRepo")}
90-
</Button>
91-
<DialogContent className="sm:max-w-2xl max-h-[85vh] overflow-y-auto">
92-
{user && (
93-
<RepositorySubmitForm
94-
onSuccess={handleSubmitSuccess}
95-
/>
96-
)}
97-
</DialogContent>
98-
</Dialog>
99-
<Button variant="secondary" className="gap-2 rounded-full h-10 px-6 bg-blue-500/10 text-blue-500 hover:bg-blue-500/20 hover:text-blue-400 border border-blue-500/20">
100-
<Flame className="h-4 w-4" />
101-
{t("home.exploreTrending")}
102-
</Button>
103-
</div>
104-
<div className="flex justify-center">
105-
<Button
106-
variant="ghost"
107-
className="gap-2 text-muted-foreground hover:text-foreground"
108-
onClick={() => setIsIntegrationsOpen(true)}
109-
>
110-
<Puzzle className="h-4 w-4" />
111-
{t("home.mcpIntegration")}
112-
</Button>
113-
</div>
114-
<IntegrationsDialog
115-
open={isIntegrationsOpen}
116-
onOpenChange={setIsIntegrationsOpen}
117-
/>
118124
</div>
119-
</div>
120125

121-
{/* Public Repository List Section */}
122-
<div className="w-full max-w-6xl mx-auto mt-8">
126+
<IntegrationsDialog
127+
open={isIntegrationsOpen}
128+
onOpenChange={setIsIntegrationsOpen}
129+
/>
130+
</section>
131+
132+
<section className="mx-auto w-full max-w-[1400px] flex-1 px-4 py-5 sm:px-6 lg:px-8">
123133
<PublicRepositoryList keyword={keyword} />
124-
</div>
134+
</section>
125135
</div>
126136
</AppLayout>
127137
);

‎web/app/admin/admin-layout-client.tsx‎

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
"use client";
22

33
import React, { useEffect } from "react";
4+
import Link from "next/link";
45
import { useRouter } from "next/navigation";
6+
import { Home, Loader2, LogOut } from "lucide-react";
7+
58
import { useAuth } from "@/contexts/auth-context";
69
import { AdminSidebar } from "@/components/admin/admin-sidebar";
10+
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
711
import { LanguageToggle } from "@/components/language-toggle";
12+
import { ThemeToggle } from "@/components/theme-toggle";
813
import { useTranslations } from "@/hooks/use-translations";
914
import {
1015
SidebarInset,
1116
SidebarProvider,
1217
SidebarTrigger,
1318
} from "@/components/animate-ui/components/radix/sidebar";
19+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
20+
import { Button } from "@/components/ui/button";
21+
import {
22+
DropdownMenu,
23+
DropdownMenuContent,
24+
DropdownMenuItem,
25+
DropdownMenuLabel,
26+
DropdownMenuSeparator,
27+
DropdownMenuTrigger,
28+
} from "@/components/ui/dropdown-menu";
1429
import { Separator } from "@/components/ui/separator";
15-
import { Loader2 } from "lucide-react";
1630

1731
export default function AdminLayoutClient({
1832
children,
1933
}: {
2034
children: React.ReactNode;
2135
}) {
2236
const router = useRouter();
23-
const { user, isLoading } = useAuth();
37+
const { user, isLoading, logout } = useAuth();
2438
const t = useTranslations();
2539

2640
const isAdmin = user?.roles?.includes("Admin") ?? false;
@@ -43,20 +57,69 @@ export default function AdminLayoutClient({
4357
return null;
4458
}
4559

60+
const userInitial = (user?.name || "?").charAt(0).toUpperCase();
61+
4662
return (
4763
<SidebarProvider defaultOpen={true}>
4864
<AdminSidebar />
4965
<SidebarInset>
5066
<header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
5167
<SidebarTrigger className="-ml-1" />
52-
<Separator orientation="vertical" className="mr-2 h-4" />
53-
<h1 className="text-sm font-semibold">{t("common.adminPanel")}</h1>
54-
<div className="ml-auto flex items-center gap-2">
68+
<Separator orientation="vertical" className="mr-1 h-4" />
69+
<AdminBreadcrumb />
70+
<div className="ml-auto flex items-center gap-1">
71+
<ThemeToggle />
5572
<LanguageToggle />
73+
<DropdownMenu>
74+
<DropdownMenuTrigger asChild>
75+
<Button
76+
variant="ghost"
77+
size="icon"
78+
className="ml-1 h-9 w-9 rounded-full"
79+
>
80+
<Avatar className="h-7 w-7">
81+
<AvatarImage src={user?.avatar} alt={user?.name} />
82+
<AvatarFallback className="text-xs">
83+
{userInitial}
84+
</AvatarFallback>
85+
</Avatar>
86+
</Button>
87+
</DropdownMenuTrigger>
88+
<DropdownMenuContent align="end" className="w-56">
89+
<DropdownMenuLabel className="font-normal">
90+
<div className="flex flex-col gap-0.5">
91+
<span className="truncate text-sm font-medium">
92+
{user?.name}
93+
</span>
94+
<span className="truncate text-xs text-muted-foreground">
95+
{user?.email}
96+
</span>
97+
</div>
98+
</DropdownMenuLabel>
99+
<DropdownMenuSeparator />
100+
<DropdownMenuItem asChild>
101+
<Link href="/">
102+
<Home className="mr-2 h-4 w-4" />
103+
{t("common.backToHome")}
104+
</Link>
105+
</DropdownMenuItem>
106+
<DropdownMenuSeparator />
107+
<DropdownMenuItem
108+
variant="destructive"
109+
onClick={() => {
110+
logout();
111+
router.push("/");
112+
}}
113+
>
114+
<LogOut className="mr-2 h-4 w-4" />
115+
{t("common.logout")}
116+
</DropdownMenuItem>
117+
</DropdownMenuContent>
118+
</DropdownMenu>
56119
</div>
57120
</header>
58121
<main className="flex-1 overflow-auto">
59-
<div className="container mx-auto p-6">
122+
<div className="mx-auto w-full max-w-screen-2xl p-4 md:p-6">
60123
{children}
61124
</div>
62125
</main>

‎web/app/admin/api-keys/page.tsx‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from "lucide-react";
2525
import { toast } from "sonner";
2626
import { useTranslations } from "@/hooks/use-translations";
27+
import { PageHeader } from "@/components/admin/page-header";
2728
import { useLocale } from "next-intl";
2829

2930
export default function AdminApiKeysPage() {
@@ -118,24 +119,23 @@ export default function AdminApiKeysPage() {
118119
};
119120

120121
return (
121-
<div className="space-y-6">
122-
{/* Header */}
123-
<div className="flex items-center justify-between">
124-
<div>
125-
<h1 className="text-2xl font-bold">{t("admin.apiKeys.title")}</h1>
126-
<p className="text-sm text-muted-foreground">{t("admin.apiKeys.subtitle")}</p>
127-
</div>
128-
<div className="flex gap-2">
129-
<Button variant="outline" onClick={fetchData}>
130-
<RefreshCw className="mr-2 h-4 w-4" />
131-
{t("admin.common.refresh")}
132-
</Button>
133-
<Button onClick={openCreateDialog}>
134-
<Plus className="mr-2 h-4 w-4" />
135-
{t("admin.apiKeys.createKey")}
136-
</Button>
137-
</div>
138-
</div>
122+
<div className="space-y-5">
123+
<PageHeader
124+
title={t("admin.apiKeys.title")}
125+
description={t("admin.apiKeys.subtitle")}
126+
actions={
127+
<>
128+
<Button variant="outline" onClick={fetchData}>
129+
<RefreshCw className="mr-1.5 h-4 w-4" />
130+
{t("admin.common.refresh")}
131+
</Button>
132+
<Button onClick={openCreateDialog}>
133+
<Plus className="mr-1.5 h-4 w-4" />
134+
{t("admin.apiKeys.createKey")}
135+
</Button>
136+
</>
137+
}
138+
/>
139139

140140
{/* API Keys Table */}
141141
{loading ? (

0 commit comments

Comments
 (0)