Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
66e0cf6
feat: enhance Drive layout with mobile navigation and responsive desi…
apensotti Nov 2, 2025
45daf1d
feat: implement video streaming support and error handling in VideoPr…
apensotti Nov 2, 2025
0d5b03b
feat: add rename functionality and optimistic UI updates for file sta…
apensotti Nov 2, 2025
1bb18fd
feat: implement mobile layout for folder, starred, trash pages with r…
apensotti Nov 2, 2025
8266c29
feat: add mobile floating action button to drive pages and refactor F…
apensotti Nov 2, 2025
6b02575
feat: add filename truncation for better display in FilesResultsTable…
apensotti Nov 2, 2025
b45e13b
feat: add sidebar toggle button to FilesSearchBar for improved naviga…
apensotti Nov 2, 2025
53aa45d
Merge pull request #105 from openchatui/drive-improvements
apensotti Nov 2, 2025
3e273a3
Mobile UX & Navigation: Responsive layouts, sidebar toggle, Drive roo…
apensotti Nov 3, 2025
5c04fb5
Video generation: show locally saved video in chat; enrich status end…
apensotti Nov 3, 2025
fe8f45c
RELEASE: Update v0.1.30 CHANGELOG
github-actions[bot] Nov 3, 2025
1255768
fix: update profile image URL path in POST request response
apensotti Nov 4, 2025
fc8c860
feat: implement local state management for model activation toggle
apensotti Nov 4, 2025
c612369
feat: add toast notifications for toggle actions in admin components
apensotti Nov 4, 2025
c766c00
feat: enhance usePinnedModels hook to support initial pinned models a…
apensotti Nov 4, 2025
70f0d72
fix: adjust padding in ChatInput and PromptSuggestions components for…
apensotti Nov 4, 2025
1d13e4c
fix: update max-width for message component and prevent auto-focus on…
apensotti Nov 4, 2025
a11d08c
Merge admin-panel-fixes-enhancements into dev
apensotti Nov 4, 2025
31f7f55
Merge sidebar-pinned-fix into dev
apensotti Nov 4, 2025
e45b36f
feat: integrate active Ollama models fetching and enhance model activ…
apensotti Nov 4, 2025
3687f5e
Merge documents-attachments-and-context into dev (#117)
apensotti Nov 12, 2025
ea1842a
Drive trash fixes (#118)
apensotti Nov 13, 2025
6bdd26d
Dev rebase 2025 11 13 (#119)
apensotti Nov 13, 2025
4a74d09
Dev: prepare clean merge to main by updating 4 files only (#122)
apensotti Nov 13, 2025
21e00ce
Resolve merge conflicts for main merge: keep dev versions in 4 files …
apensotti Nov 13, 2025
93ad96a
Fix/dev to main 4files (#125)
apensotti Nov 13, 2025
36bbff4
Sync dev with main (#127)
apensotti Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ node_modules/
# editor folders
.cursor/
.vscode/

# local docker compose overrides
docker-compose.override.yml
docker-compose.local.yml
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,65 @@
## [0.2.0] - 2025-11-13

### PR: [API-first v1 migration, Swagger docs, and Drive/Mobile UX upgrades](https://github.com/openchatui/openchat/pull/120)

Scope: 296 files changed, 12,179 insertions, 7,734 deletions. Direction: dev → main.

### Added
- Versioned, API-first surface under `app/api/v1/**` for Drive, Chat, Models, Users, Images, Videos, Websearch, Tasks, Activity, Code/Pyodide, and Connections.
- Swagger UI at `/docs` with dark theme and OpenAPI served from `app/api/docs/*`.
- Drive features and endpoints: roots, recent, search, signed URLs, move/rename/star/trash/restore/sync for files and folders.
- Mobile-first Drive UI: `FilesResultsTableMobile`, `DriveMobileHeader`, bottom navigation, mobile FAB and related components.
- Chat: attachments API (`app/api/v1/chat/attachments`) and streaming refinements.
- Models: active Ollama models endpoint and activation logic.
- Admin: mobile navigation improvements and enhanced config forms for audio/image/video/websearch.
- Client API wrappers in `lib/api/**` for audio, chats, code, connections, drive, images, models, users, userSettings, video, and websearch.

### Changed
- Replaced legacy Server Actions with RESTful versioned endpoints; routes standardized under `/api/v1/**` with request/response validation and auth responses.
- Consolidated data access into `lib/db/*.db.ts` modules; removed legacy repositories and `lib/db/client.ts`.
- Simplified middleware to gate admin routes; refined auth handling across the app.
- Dockerfile and `docker-compose.yml` updated for new envs and persistent SQLite path.
- Chat and model selector UI/UX adjustments; Drive preview and video streaming improvements.

### Removed
- Deprecated Server Actions in `actions/**`.
- Legacy Swagger integration in `app/swagger/*`.
- Old docs pages under `app/(main)/docs/*`.

### Breaking changes
- Routes re-namespaced to `/api/v1/**` (old non-v1 paths removed/redirected).
- Environment variables renamed: `NEXTAUTH_URL` → `AUTH_URL`, `NEXTAUTH_SECRET` → `AUTH_SECRET`.
- DAL/files reorganized under `lib/db/*.db.ts`; old repository modules removed.

### Migration notes
- Update `.env`, Dockerfile, and `docker-compose.yml` to use `AUTH_URL`/`AUTH_SECRET` and the new SQLite path (`/app/data/openchat.db`).
- Switch clients to `lib/api/**` wrappers and `/api/v1/**` endpoints; remove imports of deleted Server Actions.
- Review middleware and any custom logic referencing old auth/env names.

### Potential conflict hotspots
- `middleware.ts`, `next.config.ts`, `package.json`, `pnpm-lock.yaml`
- Route moves/removals under `app/api/**` → `app/api/v1/**`
- Deletions under `actions/**` paired with new `lib/api/**` clients
- Dockerfile and `docker-compose.yml` env changes

## [0.1.30] - 2025-11-03

### PR: [Release: Merge dev into main — Mobile UX, Drive navigation, and video features](https://github.com/openchatui/openchat/pull/110)

### Added
- Implemented mobile layouts for Drive folder, starred, and trash pages with a fixed header and responsive results list. (https://github.com/openchatui/openchat/commit/1bb18fd4331027667ac04bbaac89e8f241c6681c)
- Enhanced Drive layout with mobile navigation, including a bottom nav and responsive adjustments. (https://github.com/openchatui/openchat/commit/66e0cf67d43a07d4d49624f67c295f4de61b48d6)
- Added a mobile floating action button on Drive pages and improved the FAB menu for reuse. (https://github.com/openchatui/openchat/commit/8266c29ca59b8fa89704d8e76f3175f9156f06e8)
- Introduced rename capability and optimistic updates for starring in FilesResultsTableMobile. (https://github.com/openchatui/openchat/commit/0d5b03b72777925da57823507107cd16f9bb3db0)
- Improved mobile file name display with truncation in FilesResultsTableMobile. (https://github.com/openchatui/openchat/commit/6b0257516246ca80307e053a67d5bef6e730f52f)
- Added a sidebar toggle button to the FilesSearchBar for quicker navigation on smaller screens. (https://github.com/openchatui/openchat/commit/b45e13b5b2353750bc33abd70c10c164c9362e2b)
- Enabled video streaming and added robust error handling in the VideoPreviewer component. (https://github.com/openchatui/openchat/commit/45daf1dd1ff1868c7fb57ffd4c746e7658dd5bbc)
- Chat now shows locally saved videos for generation jobs and the status endpoint returns richer data. (https://github.com/openchatui/openchat/commit/5c04fb5cd0c9931f6e6d9473cd13d991e84c206e)
- Improved mobile UX and navigation, including Drive root switching and responsive search/results. (https://github.com/openchatui/openchat/commit/3e273a3483a6d29a2ef904d9c60247bd725d90d8)

### Changed
- Migrated Whisper transcription worker to @huggingface/transformers with WebGPU/CPU fallback and browser caching. (https://github.com/openchatui/openchat/pull/110)

## [0.1.29] - 2025-11-01

### PR: [admin mobile, auth fixes](https://github.com/openchatui/openchat/pull/104)
Expand Down
32 changes: 27 additions & 5 deletions app/(main)/drive/folder/[folderId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { listFoldersByParent, listFilesByParent, getFolderNameById, getFolderBreadcrumb, isGoogleDriveFolder } from "@/lib/modules/drive";
import { listFoldersByParent, listFilesByParent, getFolderNameById, getFolderBreadcrumb, isGoogleDriveFolder, findLocalRootFolderId, getGoogleRootFolderId } from "@/lib/modules/drive";
import { FilesSearchBar } from "@/components/drive/FilesSearchBar";
import { FilesResultsTable } from "@/components/drive/FilesResultsTable";
import { FilesResultsTableMobile } from "@/components/drive/FilesResultsTableMobile";
import { DriveMobileHeader } from "@/components/drive/DriveMobileHeader";
import { MobileDriveFab } from "@/components/drive/MobileDriveFab";

interface PageProps {
params: Promise<{ folderId: string }>
Expand All @@ -13,26 +16,45 @@ export default async function FolderPage({ params }: PageProps) {
if (!session?.user?.id) redirect("/login");
const { folderId } = await params

const [folders, files, parentName, breadcrumb, isDrive] = await Promise.all([
const [folders, files, parentName, breadcrumb, isDrive, localRootIdRaw, googleRootId] = await Promise.all([
listFoldersByParent(session.user.id, folderId),
listFilesByParent(session.user.id, folderId),
getFolderNameById(session.user.id, folderId),
getFolderBreadcrumb(session.user.id, folderId),
isGoogleDriveFolder(session.user.id, folderId),
findLocalRootFolderId(session.user.id),
getGoogleRootFolderId(session.user.id),
])
const entries = [...folders, ...files]
const localRootId = localRootIdRaw ?? ''

return (
<div className="space-y-6">
<FilesSearchBar />
<>
{/* Mobile header: fixed search + filters */}
<DriveMobileHeader localRootId={localRootId} googleRootId={googleRootId} isGoogleDriveFolder={isDrive} />
{/* Spacer to offset the fixed mobile header height */}
<div className="md:hidden h-[136px]" />

{/* Mobile results list (full-width, scrolls under header) */}
<div className="md:hidden">
<FilesResultsTableMobile entries={entries} parentName={parentName ?? undefined} />
</div>

{/* Mobile floating action button */}
<MobileDriveFab parentId={folderId} />

{/* Desktop layout */}
<div className="hidden md:block space-y-6">
<FilesSearchBar />
<FilesResultsTable
entries={entries}
parentName={parentName ?? undefined}
parentId={folderId}
breadcrumb={breadcrumb}
isGoogleDriveFolder={isDrive}
/>
</div>
</div>
</>
);
}

Expand Down
8 changes: 6 additions & 2 deletions app/(main)/drive/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FilesLeftSidebar } from "@/components/drive/FilesLeftSidebar"
import { auth } from "@/lib/auth"
import { redirect } from "next/navigation"
import { findLocalRootFolderId, getGoogleRootFolderId } from "@/lib/modules/drive"
import { DriveBottomNav } from "@/components/drive/DriveBottomNav"

export default async function DriveLayout({ children }: { children: React.ReactNode }) {
const session = await auth()
Expand All @@ -15,10 +16,13 @@ export default async function DriveLayout({ children }: { children: React.ReactN

return (
<div className="flex w-full">
<FilesLeftSidebar localRootId={localRootId} googleRootId={googleRootId} />
<main className="flex-1">
<div className="hidden md:block">
<FilesLeftSidebar localRootId={localRootId} googleRootId={googleRootId} />
</div>
<main className="flex-1 pb-16 md:pb-0">
{children}
</main>
<DriveBottomNav localRootId={localRootId} />
</div>
)
}
Expand Down
29 changes: 23 additions & 6 deletions app/(main)/drive/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { getRootFolderId, listFoldersByParent, listFilesByParent, getFolderBreadcrumb, isGoogleDriveFolder } from "@/lib/modules/drive";
import { getRootFolderId, listFoldersByParent, listFilesByParent, getFolderBreadcrumb, isGoogleDriveFolder, findLocalRootFolderId, getGoogleRootFolderId } from "@/lib/modules/drive";
import { FilesSearchBar } from "@/components/drive/FilesSearchBar";
import { FilesResultsTable } from "@/components/drive/FilesResultsTable";
import { FilesResultsTableMobile } from "@/components/drive/FilesResultsTableMobile";
import { DriveMobileHeader } from "@/components/drive/DriveMobileHeader";
import { MobileDriveFab } from "@/components/drive/MobileDriveFab";

interface FilesPageProps {
searchParams?: Promise<{ parentId?: string | string[] }>
Expand All @@ -20,19 +23,33 @@ export default async function FilesPage({ searchParams }: FilesPageProps) {
? parentId
: await getRootFolderId(session.user.id)

const [folders, files, breadcrumb, isDrive] = await Promise.all([
const [folders, files, breadcrumb, isDrive, localRootIdRaw, googleRootId] = await Promise.all([
listFoldersByParent(session.user.id, effectiveRootId),
listFilesByParent(session.user.id, effectiveRootId),
getFolderBreadcrumb(session.user.id, effectiveRootId),
isGoogleDriveFolder(session.user.id, effectiveRootId),
findLocalRootFolderId(session.user.id),
getGoogleRootFolderId(session.user.id),
])
const entries = [...folders, ...files]
const localRootId = localRootIdRaw ?? ''

return (
<div className="space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentId={effectiveRootId} breadcrumb={breadcrumb} isGoogleDriveFolder={isDrive} />
</div>
<>
{/* Mobile layout */}
<DriveMobileHeader localRootId={localRootId} googleRootId={googleRootId} isGoogleDriveFolder={isDrive} />
<div className="md:hidden h-[136px]" />
<div className="md:hidden">
<FilesResultsTableMobile entries={entries} parentName={breadcrumb?.[breadcrumb.length - 1]?.name} />
</div>
<MobileDriveFab parentId={effectiveRootId} />

{/* Desktop layout */}
<div className="hidden md:block space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentId={effectiveRootId} breadcrumb={breadcrumb} isGoogleDriveFolder={isDrive} />
</div>
</>
);
}

Expand Down
37 changes: 31 additions & 6 deletions app/(main)/drive/starred/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,44 @@ import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { FilesSearchBar } from "@/components/drive/FilesSearchBar";
import { FilesResultsTable } from "@/components/drive/FilesResultsTable";
import { listStarredEntries } from "@/lib/modules/drive";
import { listStarredEntries, getRootFolderId, findLocalRootFolderId, getGoogleRootFolderId } from "@/lib/modules/drive";
import { FilesResultsTableMobile } from "@/components/drive/FilesResultsTableMobile";
import { DriveMobileHeader } from "@/components/drive/DriveMobileHeader";
import { MobileDriveFab } from "@/components/drive/MobileDriveFab";

export default async function StarredPage() {
const session = await auth()
if (!session?.user?.id) redirect('/login')

const entries = await listStarredEntries(session.user.id)
const [entries, rootId, localRootIdRaw, googleRootId] = await Promise.all([
listStarredEntries(session.user.id),
getRootFolderId(session.user.id),
findLocalRootFolderId(session.user.id),
getGoogleRootFolderId(session.user.id),
])
const localRootId = localRootIdRaw ?? ''

return (
<div className="space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentName={"Starred"} />
</div>
<>
{/* Mobile header: fixed search + filters */}
<DriveMobileHeader localRootId={localRootId} googleRootId={googleRootId} isGoogleDriveFolder={false} />
{/* Spacer to offset the fixed mobile header height */}
<div className="md:hidden h-[136px]" />

{/* Mobile results list (full-width, scrolls under header) */}
<div className="md:hidden">
<FilesResultsTableMobile entries={entries} parentName={"Starred"} />
</div>

{/* Mobile floating action button */}
<MobileDriveFab parentId={rootId} />

{/* Desktop layout */}
<div className="hidden md:block space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentName={"Starred"} />
</div>
</>
)
}

Expand Down
36 changes: 30 additions & 6 deletions app/(main)/drive/trash/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { getTrashFolderId, listFoldersByParent, listFilesByParent } from "@/lib/modules/drive";
import { getTrashFolderId, listFoldersByParent, listFilesByParent, findLocalRootFolderId, getGoogleRootFolderId } from "@/lib/modules/drive";
import { FilesResultsTable } from "@/components/drive/FilesResultsTable";
import { FilesSearchBar } from "@/components/drive/FilesSearchBar";
import { FilesResultsTableMobile } from "@/components/drive/FilesResultsTableMobile";
import { DriveMobileHeader } from "@/components/drive/DriveMobileHeader";
import { MobileDriveFab } from "@/components/drive/MobileDriveFab";

export default async function TrashPage() {
const session = await auth();
if (!session?.user?.id) redirect("/login");

// Ensure the user's Trash system folder exists
const trashId = await getTrashFolderId(session.user.id);
const [trashId, localRootIdRaw, googleRootId] = await Promise.all([
getTrashFolderId(session.user.id),
findLocalRootFolderId(session.user.id),
getGoogleRootFolderId(session.user.id),
])
const localRootId = localRootIdRaw ?? ''

// Load only Trash folder contents (not My Drive root)
const [folders, files] = await Promise.all([
Expand All @@ -20,10 +28,26 @@ export default async function TrashPage() {
const breadcrumb = [{ id: trashId, name: 'Trash' }]

return (
<div className="space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentId={trashId} parentName="Trash" breadcrumb={breadcrumb} />
</div>
<>
{/* Mobile header: fixed search + filters */}
<DriveMobileHeader localRootId={localRootId} googleRootId={googleRootId} isGoogleDriveFolder={false} />
{/* Spacer to offset the fixed mobile header height */}
<div className="md:hidden h-[136px]" />

{/* Mobile results list (full-width, scrolls under header) */}
<div className="md:hidden">
<FilesResultsTableMobile entries={entries} parentName="Trash" />
</div>

{/* Mobile floating action button */}
<MobileDriveFab parentId={trashId} isTrash />

{/* Desktop layout */}
<div className="hidden md:block space-y-6">
<FilesSearchBar />
<FilesResultsTable entries={entries} parentId={trashId} parentName="Trash" breadcrumb={breadcrumb} />
</div>
</>
);
}

Expand Down
Loading
Loading