Skip to content
Merged

sync #129

Show file tree
Hide file tree
Changes from all commits
Commits
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