From 7c34ba2eb436e71be73ae666f1166e6b2eaa8093 Mon Sep 17 00:00:00 2001
From: agent4701 <90219335+agent4701@users.noreply.github.com>
Date: Wed, 19 Nov 2025 19:51:28 +0100
Subject: [PATCH] Multi-user capable and integrated branding options.
Multi-user capable and integrated branding options.
---
.gitignore | 4 +-
Dockerfile | 15 +-
Dockerfile.cuda | 8 -
README.md | 5 -
cmd/server/main.go | 31 +-
docker-compose.build.yml | 1 +
docs/assets/index-CTEra42u.js | 2 +-
...m providers for chat and summarization.png | Bin 0 -> 256774 bytes
go.mod | 1 +
internal/api/chat_handlers.go | 252 ++-
internal/api/handlers.go | 1414 ++++++++++++-----
internal/api/notes_handlers.go | 164 +-
internal/api/router.go | 37 +-
internal/api/summarize_handlers.go | 52 +-
internal/api/summary_handlers.go | 114 +-
internal/config/config.go | 38 +-
internal/database/database.go | 176 +-
internal/models/summary.go | 17 +-
internal/models/transcription.go | 129 +-
.../transcription/adapters/canary_adapter.go | 14 +-
.../adapters/parakeet_adapter.go | 394 +----
.../adapters/pyannote_adapter.go | 28 +-
.../adapters/sortformer_adapter.go | 23 +-
.../adapters/whisperx_adapter.go | 16 +-
internal/transcription/quick_transcription.go | 11 +
internal/transcription/registry/registry.go | 43 -
internal/web/static.go | 18 +
pkg/middleware/auth.go | 151 +-
run_tests.sh | 33 +-
...m providers for chat and summarization.png | Bin 0 -> 256774 bytes
tests/api_handlers_test.go | 21 +-
tests/queue_test.go | 25 +-
tests/security_test.go | 10 +-
web/frontend/src/App.tsx | 3 +
web/frontend/src/components/Header.tsx | 43 +-
.../components/TranscriptionConfigDialog.tsx | 4 +-
web/frontend/src/contexts/AuthContext.tsx | 95 +-
web/frontend/src/contexts/RouterContext.tsx | 6 +-
web/frontend/src/pages/Admin.tsx | 472 ++++++
web/landing/dist/assets/index-CTEra42u.js | 2 +-
...m providers for chat and summarization.png | Bin 0 -> 256774 bytes
...m providers for chat and summarization.png | Bin 0 -> 256774 bytes
web/landing/src/components/Alternating.tsx | 2 +-
43 files changed, 2432 insertions(+), 1442 deletions(-)
create mode 100644 docs/screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png
create mode 100644 screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png
create mode 100644 web/frontend/src/pages/Admin.tsx
create mode 100644 web/landing/dist/screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png
create mode 100644 web/landing/public/screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png
diff --git a/.gitignore b/.gitignore
index fb01f17b5..3e2e4b954 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,4 @@ debug_whisperx_parsing.go
debug_parsing.go
scriberr-optimized
tests/database_test.db-shm
-
-# Project documentation (local only)
-project-docs
+tests
diff --git a/Dockerfile b/Dockerfile
index 238f899b0..6935b5abd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -23,20 +23,23 @@ RUN cd frontend \
FROM golang:1.24-bookworm AS go-builder
WORKDIR /src
-# Pre-cache modules
+# Pre-cache modules for better build caching
COPY go.mod go.sum ./
RUN go mod download
# Copy source
COPY . .
+# Ensure go.sum is up-to-date if go.mod changed later in the build cache chain and download sums
+RUN go mod tidy && go mod download
+
# Copy built UI into embed path
RUN rm -rf internal/web/dist && mkdir -p internal/web
COPY --from=ui-builder /web/frontend/dist internal/web/dist
# Build binary (arch matches builder platform)
RUN CGO_ENABLED=0 \
- go build -o /out/scriberr cmd/server/main.go
+ go build -mod=mod -o /out/scriberr cmd/server/main.go
########################
@@ -68,14 +71,6 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
&& chmod 755 /usr/local/bin/uv \
&& uv --version
-# Install Deno (JavaScript runtime required for yt-dlp YouTube downloads)
-# YouTube now requires JS execution for video cipher decryption
-# See: https://github.com/yt-dlp/yt-dlp/issues/14404
-RUN curl -fsSL https://deno.land/install.sh | sh \
- && cp /root/.deno/bin/deno /usr/local/bin/deno \
- && chmod 755 /usr/local/bin/deno \
- && deno --version
-
# Create default user (will be modified at runtime if needed)
RUN groupadd -g 1000 appuser \
&& useradd -m -u 1000 -g 1000 appuser \
diff --git a/Dockerfile.cuda b/Dockerfile.cuda
index c91ebacc4..24a4d6ab8 100644
--- a/Dockerfile.cuda
+++ b/Dockerfile.cuda
@@ -71,14 +71,6 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
&& chmod 755 /usr/local/bin/uv \
&& uv --version
-# Install Deno (JavaScript runtime required for yt-dlp YouTube downloads)
-# YouTube now requires JS execution for video cipher decryption
-# See: https://github.com/yt-dlp/yt-dlp/issues/14404
-RUN curl -fsSL https://deno.land/install.sh | sh \
- && cp /root/.deno/bin/deno /usr/local/bin/deno \
- && chmod 755 /usr/local/bin/deno \
- && deno --version
-
# Create default user (will be modified at runtime if needed)
# Use 10001 to avoid conflicts with existing users in CUDA base image
RUN groupadd -g 10001 appuser \
diff --git a/README.md b/README.md
index 9240ecb8c..f5de87a30 100644
--- a/README.md
+++ b/README.md
@@ -190,11 +190,6 @@ See the full guide: https://scriberr.app/docs/diarization.html
-## Summarization (Ollama)
-
-Scribber uses different models from Ollama (local, open-source and free) or OpenAi (online, propietary, paid) in order to automatically summarize the transcriptions. To connect, just go to settings and introduce either the Ollama port or the OpenAI API.
-A common error is that if Ollama has been installed through Docker, rather then connecting via "http://localhost:11434" you sohuld instead connect through "http://host.docker.internal:11434" (change the port to whichever you have used, automatically uses that one). That way Scriberr directly connects to the Docker, avoiding a "Failed to fetch model" error and alike.
-
## API
Scriberr exposes a clean REST API for most features (transcription, chat, notes, summaries, admin, and more). Authentication supports JWT or API keys depending on endpoint.
diff --git a/cmd/server/main.go b/cmd/server/main.go
index bca9d9922..200c2fef6 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -7,7 +7,6 @@ import (
"net/http"
"os"
"os/signal"
- "path/filepath"
"syscall"
"time"
@@ -17,11 +16,10 @@ import (
"scriberr/internal/database"
"scriberr/internal/queue"
"scriberr/internal/transcription"
- "scriberr/internal/transcription/adapters"
- "scriberr/internal/transcription/registry"
"scriberr/pkg/logger"
_ "scriberr/api-docs" // Import generated Swagger docs
+ _ "scriberr/internal/transcription/adapters" // Import adapters for auto-registration
)
// Version information (set by GoReleaser)
@@ -75,9 +73,6 @@ func main() {
logger.Startup("config", "Loading configuration")
cfg := config.Load()
- // Register adapters with config-based paths
- registerAdapters(cfg)
-
// Initialize database
logger.Startup("database", "Connecting to database")
if err := database.Initialize(cfg.DatabasePath); err != nil {
@@ -161,27 +156,3 @@ func main() {
logger.Info("Server stopped")
}
-
-// registerAdapters registers all transcription and diarization adapters with config-based paths
-func registerAdapters(cfg *config.Config) {
- logger.Info("Registering adapters with environment path", "whisperx_env", cfg.WhisperXEnv)
-
- // Shared environment path for NVIDIA models (NeMo-based)
- nvidiaEnvPath := filepath.Join(cfg.WhisperXEnv, "parakeet")
-
- // Register transcription adapters
- registry.RegisterTranscriptionAdapter("whisperx",
- adapters.NewWhisperXAdapter(cfg.WhisperXEnv))
- registry.RegisterTranscriptionAdapter("parakeet",
- adapters.NewParakeetAdapter(nvidiaEnvPath))
- registry.RegisterTranscriptionAdapter("canary",
- adapters.NewCanaryAdapter(nvidiaEnvPath)) // Shares with Parakeet
-
- // Register diarization adapters
- registry.RegisterDiarizationAdapter("pyannote",
- adapters.NewPyAnnoteAdapter(nvidiaEnvPath)) // Shares with Parakeet
- registry.RegisterDiarizationAdapter("sortformer",
- adapters.NewSortformerAdapter(nvidiaEnvPath)) // Shares with Parakeet
-
- logger.Info("Adapter registration complete")
-}
diff --git a/docker-compose.build.yml b/docker-compose.build.yml
index 62c42f357..125e1bf58 100644
--- a/docker-compose.build.yml
+++ b/docker-compose.build.yml
@@ -22,6 +22,7 @@ services:
volumes:
- ./scriberr-data:/app/data
- ./env-data:/app/whisperx-env
+ - ./Transferordner:/app/Transferordner:ro
restart: unless-stopped
volumes:
diff --git a/docs/assets/index-CTEra42u.js b/docs/assets/index-CTEra42u.js
index ac0e1a091..bbce048e1 100644
--- a/docs/assets/index-CTEra42u.js
+++ b/docs/assets/index-CTEra42u.js
@@ -1 +1 @@
-import{j as e,r as i,G as n,c as o,R as l}from"./styles-DGJLjUaE.js";import{W as c}from"./Window-BJy5jSY4.js";function a({className:s=""}){return e.jsx("img",{src:"/scriberr-logo.png",alt:"Scriberr",className:`w-auto select-none ${s}`})}function d(){const[s,r]=i.useState(!1);return e.jsxs("header",{className:"sticky top-0 z-40 bg-white/80 backdrop-blur shadow-soft",children:[e.jsxs("div",{className:"container-narrow py-4 flex items-center justify-between",children:[e.jsx("a",{href:"#",className:"flex items-center gap-3",children:e.jsx(a,{className:"h-8 sm:h-10"})}),e.jsxs("nav",{className:"hidden md:flex items-center gap-6 text-sm text-gray-600",children:[e.jsx("a",{href:"/docs/intro.html",className:"hover:text-gray-900",children:"Docs"}),e.jsx("a",{href:"/changelog.html",className:"hover:text-gray-900",children:"Changelog"}),e.jsx("a",{href:"/api.html",className:"hover:text-gray-900",children:"API"})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{className:"md:hidden inline-flex items-center justify-center rounded-md border border-gray-200 bg-white px-2.5 py-1.5 text-gray-700 hover:bg-gray-50","aria-label":"Menu",onClick:()=>r(t=>!t),children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:"size-5",children:e.jsx("path",{d:"M4 7h16M4 12h16M4 17h16"})})}),e.jsx(n,{})]})]}),s&&e.jsx("div",{className:"md:hidden border-t border-gray-200 bg-white",children:e.jsxs("div",{className:"container-narrow py-3 flex flex-col gap-2 text-sm text-gray-700",children:[e.jsx("a",{href:"/docs/intro.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"Docs"}),e.jsx("a",{href:"/changelog.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"Changelog"}),e.jsx("a",{href:"/api.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"API"})]})})]})}function m(){return e.jsx("section",{className:"relative",children:e.jsxs("div",{className:"container-narrow section text-center",children:[e.jsx("span",{className:"eyebrow mb-3 inline-block",children:"Self-hosted offline audio transcription"}),e.jsx("h1",{className:"headline flex justify-center mb-4",children:e.jsx(a,{className:"h-16 sm:h-20 md:h-24 lg:h-28 xl:h-32"})}),e.jsx("p",{className:"subcopy mt-3 mx-auto max-w-2xl",children:"Transcribe audio locally into text - Summarize and Chat with your audio. No GPU Required."}),e.jsxs("div",{className:"mt-8 flex items-center justify-center gap-3",children:[e.jsx("a",{href:"/docs/installation.html",className:"button-primary",children:"Get Started"}),e.jsx("a",{href:"#features",className:"button-ghost",children:"Learn more"})]}),e.jsx("div",{className:"mt-6 flex justify-center",children:e.jsx("a",{href:"https://ko-fi.com/H2H41KQZA3",target:"_blank",rel:"noopener noreferrer",onClick:s=>{s.preventDefault(),window.open("https://ko-fi.com/H2H41KQZA3","_blank","noopener,noreferrer")},children:e.jsx("img",{height:"36",style:{border:"0px",height:"36px"},src:"https://storage.ko-fi.com/cdn/kofi6.png?v=6",alt:"Buy Me a Coffee at ko-fi.com"})})}),e.jsx("div",{className:"mt-12 max-w-5xl mx-auto",children:e.jsxs("div",{className:"rounded-3xl shadow-soft overflow-hidden bg-white hover-lift",children:[e.jsxs("div",{className:"flex items-center gap-2 px-3 py-2 bg-gray-100",children:[e.jsx("span",{className:"size-3 rounded-full bg-red-400/80"}),e.jsx("span",{className:"size-3 rounded-full bg-yellow-400/80"}),e.jsx("span",{className:"size-3 rounded-full bg-green-400/80"})]}),e.jsx("img",{src:"/screenshots/scriberr-homepage.png",alt:"Scriberr homepage",className:"w-full object-cover"})]})}),e.jsxs("div",{className:"mt-6 flex items-center justify-center gap-4 text-xs text-gray-500",children:[e.jsx("span",{children:"Privacy preserving"}),e.jsx("span",{children:"Mobile ready"}),e.jsx("span",{children:"Fast and responsive"})]})]})})}const h=[{title:"Precise transcription",desc:"Tweak advanced transcription parameters to get the best quality output"},{title:"Built-in recorder",desc:"Capture audio directly in-app and transcribe instantly."},{title:"Summarize & chat",desc:"Extract key points or chat over transcripts using LLMs."},{title:"Lightweight notes",desc:"Highlight, annotate, and tag important moments as you listen/read."},{title:"Speaker diarization",desc:"Identify and label distinct speakers in your audio."},{title:"Profiles & presets",desc:"Save configurations for different audio scenarios."}];function x({name:s}){const r="size-4";switch(s){case"Precise transcription":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M12 3v10a3 3 0 1 1-6 0V8"}),e.jsx("path",{d:"M19 10v3a7 7 0 0 1-14 0"})]});case"Built-in recorder":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("rect",{x:"9",y:"9",width:"6",height:"6",rx:"3"}),e.jsx("circle",{cx:"12",cy:"12",r:"9"})]});case"Summarize & chat":return e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:e.jsx("path",{d:"M21 15a4 4 0 0 1-4 4H7l-4 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"})});case"Lightweight notes":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M9 7h6M9 12h6M9 17h6"}),e.jsx("rect",{x:"5",y:"3",width:"14",height:"18",rx:"2"})]});case"Speaker diarization":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("circle",{cx:"7",cy:"8",r:"3"}),e.jsx("path",{d:"M2 19a5 5 0 0 1 10 0"}),e.jsx("circle",{cx:"17",cy:"10",r:"2.5"}),e.jsx("path",{d:"M13 19c.5-2.5 2.5-4 5-4"})]});case"Profiles & presets":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M6 3v12"}),e.jsx("path",{d:"M12 3v18"}),e.jsx("path",{d:"M18 3v8"})]});default:return e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:e.jsx("path",{d:"M12 6v12m6-6H6"})})}}function p(){return e.jsxs("section",{id:"features",className:"container-narrow section",children:[e.jsxs("div",{className:"text-center mb-12",children:[e.jsx("span",{className:"eyebrow",children:"Capabilities"}),e.jsx("h2",{className:"text-2xl md:text-3xl font-semibold mt-2",children:"Key Features"}),e.jsx("p",{className:"subcopy mt-2",children:"Curated set of features to manage and work with transcripts."})]}),e.jsx("div",{className:"grid sm:grid-cols-2 lg:grid-cols-3 gap-6",children:h.map(s=>e.jsxs("article",{className:"rounded-2xl p-6 bg-gray-50 shadow-soft hover-lift",children:[e.jsxs("div",{className:"mb-3 flex items-center gap-3",children:[e.jsx("span",{className:"inline-flex items-center justify-center size-9 rounded-xl bg-gray-100 text-gray-600 shadow-subtle",children:e.jsx(x,{name:s.title})}),e.jsx("h3",{className:"font-medium text-base md:text-lg text-gray-900",children:s.title})]}),e.jsx("p",{className:"subcopy",children:s.desc})]},s.title))})]})}const u=[{title:"Transcript view",desc:"Minimal reading experience with timestamps with playback follow along that highlights currently playing word.",img:"scriberr-transcript page.png",bullets:["Jump from audio timestamp to corresponding word","Jump from text to corresponding audio segment","View transcript as paragraph or as timestamped/speaker segments"]},{title:"Record right in Scriberr",desc:"Capture audio directly in-app and transcribe",img:"scriberr-inbuilt audio recorder for directly recording and transcribing audio within the app.png",bullets:[]},{title:"Summaries at a glance",desc:"Turn long recordings into brief, actionable summaries you can scan in seconds.",img:"scriberr-summarize transcripts.png",bullets:["Write your own custom prompts for summarization","Supports both Ollama/OpenAI (needs API Key) LLM providers","Save multiple summarization presets to reuse quickly"]},{title:"Annotate transcripts",desc:"Highlight important moments, jot down concise notes, and keep insights attached to the exact timestamp.",img:"scriberr-annotate transcript and take notes.png",bullets:["Highlight text to add a note","Timestamped notes allow jumping to exact segment"]},{title:"Advanced controls",desc:"Fine-tune model settings, language, and diarization for optimal results",img:"scriberr-fine tune advanced transcription parameters as you see fit to improve transcription quality.png",bullets:["Language hints and temperature","Diarization and VAD options","Profiles for repeatable setups"]},{title:"Bring your own providers",desc:"Use OpenAI or local models via Ollama for summaries and chat — your keys, your choice.",img:"scriberr-Ollama openAI llm providers for chat and summarization.png",bullets:["Works with OpenAI or Ollama"]},{title:"Export transcripts",desc:"Download your transcripts in multiple formats",img:"scriberr-download-transcript-in-different-formats.png",bullets:["Export to TXT, Markdown, JSON","Keep timestamps and speaker info"]},{title:"Chat with your transcript",desc:"Ask questions about your recording, extract insights, and clarify details without scrubbing through audio.",img:"scriberr-chat-with-your-recording-transcript.png",bullets:["Works with OpenAI or Ollama"]},{title:"API keys and REST API",desc:"Manage API keys and use the full REST API to build automations or integrate Scriberr into your own applications.",img:"scriberr-api-key-management.png",bullets:["Secure API key management","Endpoints for transcription, chat, notes and more"]},{title:"Transcribe YouTube videos",desc:"Paste a YouTube link to transcribe the audio directly — no downloads required.",img:"scriberr-youtube-video.png",bullets:["Grab insights from talks, podcasts and lectures","Works with summarization and notes"]}];function g(){return e.jsxs("section",{id:"details",className:"container-narrow section",children:[e.jsxs("div",{className:"text-center mb-12",children:[e.jsx("span",{className:"eyebrow",children:"Details"}),e.jsx("h2",{className:"text-2xl md:text-3xl font-semibold mt-2",children:"A closer look"})]}),e.jsx("div",{className:"space-y-16 md:space-y-24",children:u.map((s,r)=>e.jsxs("div",{className:"grid md:grid-cols-2 gap-8 md:gap-10 items-center",children:[e.jsx("div",{className:r%2===0?"order-1 md:order-1":"order-2 md:order-2",children:e.jsxs("div",{children:[e.jsx("h3",{className:"text-xl md:text-2xl font-semibold text-gray-900",children:s.title}),e.jsx("p",{className:"subcopy mt-2",children:s.desc}),s.bullets&&e.jsx("ul",{className:"mt-4 space-y-2 text-sm text-gray-600 list-disc list-inside",children:s.bullets.map(t=>e.jsx("li",{children:t},t))})]})}),e.jsx("div",{className:r%2===0?"order-2 md:order-2":"order-1 md:order-1",children:e.jsx(c,{src:`/screenshots/${s.img}`,alt:s.title})})]},s.title))})]})}function f(){return e.jsx("footer",{className:"mt-8 bg-gray-50",children:e.jsxs("div",{className:"container-narrow py-10 text-center text-sm text-gray-700",children:[e.jsxs("p",{children:["If you like Scriberr, consider giving the project a star on"," ",e.jsx("a",{href:"https://github.com/rishikanthc/scriberr",target:"_blank",rel:"noreferrer",className:"text-blue-600 hover:text-blue-700 underline-offset-2 hover:underline",children:"GitHub"}),"."]}),e.jsx("div",{className:"mt-4 flex justify-center",children:e.jsx("a",{href:"https://ko-fi.com/H2H41KQZA3",target:"_blank",rel:"noopener noreferrer",children:e.jsx("img",{height:"36",style:{border:"0px",height:"36px"},src:"https://storage.ko-fi.com/cdn/kofi6.png?v=6",alt:"Buy Me a Coffee at ko-fi.com"})})})]})})}function j(){return e.jsxs("div",{className:"min-h-screen flex flex-col",children:[e.jsx(d,{}),e.jsxs("main",{className:"flex-1",children:[e.jsx(m,{}),e.jsx(p,{}),e.jsx(g,{})]}),e.jsx(f,{})]})}const b=o(document.getElementById("root"));b.render(e.jsx(l.StrictMode,{children:e.jsx(j,{})}));
+import{j as e,r as i,G as n,c as o,R as l}from"./styles-DGJLjUaE.js";import{W as c}from"./Window-BJy5jSY4.js";function a({className:s=""}){return e.jsx("img",{src:"/scriberr-logo.png",alt:"Scriberr",className:`w-auto select-none ${s}`})}function d(){const[s,r]=i.useState(!1);return e.jsxs("header",{className:"sticky top-0 z-40 bg-white/80 backdrop-blur shadow-soft",children:[e.jsxs("div",{className:"container-narrow py-4 flex items-center justify-between",children:[e.jsx("a",{href:"#",className:"flex items-center gap-3",children:e.jsx(a,{className:"h-8 sm:h-10"})}),e.jsxs("nav",{className:"hidden md:flex items-center gap-6 text-sm text-gray-600",children:[e.jsx("a",{href:"/docs/intro.html",className:"hover:text-gray-900",children:"Docs"}),e.jsx("a",{href:"/changelog.html",className:"hover:text-gray-900",children:"Changelog"}),e.jsx("a",{href:"/api.html",className:"hover:text-gray-900",children:"API"})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{className:"md:hidden inline-flex items-center justify-center rounded-md border border-gray-200 bg-white px-2.5 py-1.5 text-gray-700 hover:bg-gray-50","aria-label":"Menu",onClick:()=>r(t=>!t),children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:"size-5",children:e.jsx("path",{d:"M4 7h16M4 12h16M4 17h16"})})}),e.jsx(n,{})]})]}),s&&e.jsx("div",{className:"md:hidden border-t border-gray-200 bg-white",children:e.jsxs("div",{className:"container-narrow py-3 flex flex-col gap-2 text-sm text-gray-700",children:[e.jsx("a",{href:"/docs/intro.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"Docs"}),e.jsx("a",{href:"/changelog.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"Changelog"}),e.jsx("a",{href:"/api.html",className:"hover:text-gray-900",onClick:()=>r(!1),children:"API"})]})})]})}function m(){return e.jsx("section",{className:"relative",children:e.jsxs("div",{className:"container-narrow section text-center",children:[e.jsx("span",{className:"eyebrow mb-3 inline-block",children:"Self-hosted offline audio transcription"}),e.jsx("h1",{className:"headline flex justify-center mb-4",children:e.jsx(a,{className:"h-16 sm:h-20 md:h-24 lg:h-28 xl:h-32"})}),e.jsx("p",{className:"subcopy mt-3 mx-auto max-w-2xl",children:"Transcribe audio locally into text - Summarize and Chat with your audio. No GPU Required."}),e.jsxs("div",{className:"mt-8 flex items-center justify-center gap-3",children:[e.jsx("a",{href:"/docs/installation.html",className:"button-primary",children:"Get Started"}),e.jsx("a",{href:"#features",className:"button-ghost",children:"Learn more"})]}),e.jsx("div",{className:"mt-6 flex justify-center",children:e.jsx("a",{href:"https://ko-fi.com/H2H41KQZA3",target:"_blank",rel:"noopener noreferrer",onClick:s=>{s.preventDefault(),window.open("https://ko-fi.com/H2H41KQZA3","_blank","noopener,noreferrer")},children:e.jsx("img",{height:"36",style:{border:"0px",height:"36px"},src:"https://storage.ko-fi.com/cdn/kofi6.png?v=6",alt:"Buy Me a Coffee at ko-fi.com"})})}),e.jsx("div",{className:"mt-12 max-w-5xl mx-auto",children:e.jsxs("div",{className:"rounded-3xl shadow-soft overflow-hidden bg-white hover-lift",children:[e.jsxs("div",{className:"flex items-center gap-2 px-3 py-2 bg-gray-100",children:[e.jsx("span",{className:"size-3 rounded-full bg-red-400/80"}),e.jsx("span",{className:"size-3 rounded-full bg-yellow-400/80"}),e.jsx("span",{className:"size-3 rounded-full bg-green-400/80"})]}),e.jsx("img",{src:"/screenshots/scriberr-homepage.png",alt:"Scriberr homepage",className:"w-full object-cover"})]})}),e.jsxs("div",{className:"mt-6 flex items-center justify-center gap-4 text-xs text-gray-500",children:[e.jsx("span",{children:"Privacy preserving"}),e.jsx("span",{children:"Mobile ready"}),e.jsx("span",{children:"Fast and responsive"})]})]})})}const h=[{title:"Precise transcription",desc:"Tweak advanced transcription parameters to get the best quality output"},{title:"Built-in recorder",desc:"Capture audio directly in-app and transcribe instantly."},{title:"Summarize & chat",desc:"Extract key points or chat over transcripts using LLMs."},{title:"Lightweight notes",desc:"Highlight, annotate, and tag important moments as you listen/read."},{title:"Speaker diarization",desc:"Identify and label distinct speakers in your audio."},{title:"Profiles & presets",desc:"Save configurations for different audio scenarios."}];function x({name:s}){const r="size-4";switch(s){case"Precise transcription":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M12 3v10a3 3 0 1 1-6 0V8"}),e.jsx("path",{d:"M19 10v3a7 7 0 0 1-14 0"})]});case"Built-in recorder":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("rect",{x:"9",y:"9",width:"6",height:"6",rx:"3"}),e.jsx("circle",{cx:"12",cy:"12",r:"9"})]});case"Summarize & chat":return e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:e.jsx("path",{d:"M21 15a4 4 0 0 1-4 4H7l-4 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"})});case"Lightweight notes":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M9 7h6M9 12h6M9 17h6"}),e.jsx("rect",{x:"5",y:"3",width:"14",height:"18",rx:"2"})]});case"Speaker diarization":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("circle",{cx:"7",cy:"8",r:"3"}),e.jsx("path",{d:"M2 19a5 5 0 0 1 10 0"}),e.jsx("circle",{cx:"17",cy:"10",r:"2.5"}),e.jsx("path",{d:"M13 19c.5-2.5 2.5-4 5-4"})]});case"Profiles & presets":return e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:[e.jsx("path",{d:"M6 3v12"}),e.jsx("path",{d:"M12 3v18"}),e.jsx("path",{d:"M18 3v8"})]});default:return e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",className:r,children:e.jsx("path",{d:"M12 6v12m6-6H6"})})}}function p(){return e.jsxs("section",{id:"features",className:"container-narrow section",children:[e.jsxs("div",{className:"text-center mb-12",children:[e.jsx("span",{className:"eyebrow",children:"Capabilities"}),e.jsx("h2",{className:"text-2xl md:text-3xl font-semibold mt-2",children:"Key Features"}),e.jsx("p",{className:"subcopy mt-2",children:"Curated set of features to manage and work with transcripts."})]}),e.jsx("div",{className:"grid sm:grid-cols-2 lg:grid-cols-3 gap-6",children:h.map(s=>e.jsxs("article",{className:"rounded-2xl p-6 bg-gray-50 shadow-soft hover-lift",children:[e.jsxs("div",{className:"mb-3 flex items-center gap-3",children:[e.jsx("span",{className:"inline-flex items-center justify-center size-9 rounded-xl bg-gray-100 text-gray-600 shadow-subtle",children:e.jsx(x,{name:s.title})}),e.jsx("h3",{className:"font-medium text-base md:text-lg text-gray-900",children:s.title})]}),e.jsx("p",{className:"subcopy",children:s.desc})]},s.title))})]})}const u=[{title:"Transcript view",desc:"Minimal reading experience with timestamps with playback follow along that highlights currently playing word.",img:"scriberr-transcript page.png",bullets:["Jump from audio timestamp to corresponding word","Jump from text to corresponding audio segment","View transcript as paragraph or as timestamped/speaker segments"]},{title:"Record right in Scriberr",desc:"Capture audio directly in-app and transcribe",img:"scriberr-inbuilt audio recorder for directly recording and transcribing audio within the app.png",bullets:[]},{title:"Summaries at a glance",desc:"Turn long recordings into brief, actionable summaries you can scan in seconds.",img:"scriberr-summarize transcripts.png",bullets:["Write your own custom prompts for summarization","Supports both Ollama/OpenAI (needs API Key) LLM providers","Save multiple summarization presets to reuse quickly"]},{title:"Annotate transcripts",desc:"Highlight important moments, jot down concise notes, and keep insights attached to the exact timestamp.",img:"scriberr-annotate transcript and take notes.png",bullets:["Highlight text to add a note","Timestamped notes allow jumping to exact segment"]},{title:"Advanced controls",desc:"Fine-tune model settings, language, and diarization for optimal results",img:"scriberr-fine tune advanced transcription parameters as you see fit to improve transcription quality.png",bullets:["Language hints and temperature","Diarization and VAD options","Profiles for repeatable setups"]},{title:"Bring your own providers",desc:"Use OpenAI or local models via Ollama for summaries and chat — your keys, your choice.",img:"scriberr-Ollama:openAI llm providers for chat and summarization.png",bullets:["Works with OpenAI or Ollama"]},{title:"Export transcripts",desc:"Download your transcripts in multiple formats",img:"scriberr-download-transcript-in-different-formats.png",bullets:["Export to TXT, Markdown, JSON","Keep timestamps and speaker info"]},{title:"Chat with your transcript",desc:"Ask questions about your recording, extract insights, and clarify details without scrubbing through audio.",img:"scriberr-chat-with-your-recording-transcript.png",bullets:["Works with OpenAI or Ollama"]},{title:"API keys and REST API",desc:"Manage API keys and use the full REST API to build automations or integrate Scriberr into your own applications.",img:"scriberr-api-key-management.png",bullets:["Secure API key management","Endpoints for transcription, chat, notes and more"]},{title:"Transcribe YouTube videos",desc:"Paste a YouTube link to transcribe the audio directly — no downloads required.",img:"scriberr-youtube-video.png",bullets:["Grab insights from talks, podcasts and lectures","Works with summarization and notes"]}];function g(){return e.jsxs("section",{id:"details",className:"container-narrow section",children:[e.jsxs("div",{className:"text-center mb-12",children:[e.jsx("span",{className:"eyebrow",children:"Details"}),e.jsx("h2",{className:"text-2xl md:text-3xl font-semibold mt-2",children:"A closer look"})]}),e.jsx("div",{className:"space-y-16 md:space-y-24",children:u.map((s,r)=>e.jsxs("div",{className:"grid md:grid-cols-2 gap-8 md:gap-10 items-center",children:[e.jsx("div",{className:r%2===0?"order-1 md:order-1":"order-2 md:order-2",children:e.jsxs("div",{children:[e.jsx("h3",{className:"text-xl md:text-2xl font-semibold text-gray-900",children:s.title}),e.jsx("p",{className:"subcopy mt-2",children:s.desc}),s.bullets&&e.jsx("ul",{className:"mt-4 space-y-2 text-sm text-gray-600 list-disc list-inside",children:s.bullets.map(t=>e.jsx("li",{children:t},t))})]})}),e.jsx("div",{className:r%2===0?"order-2 md:order-2":"order-1 md:order-1",children:e.jsx(c,{src:`/screenshots/${s.img}`,alt:s.title})})]},s.title))})]})}function f(){return e.jsx("footer",{className:"mt-8 bg-gray-50",children:e.jsxs("div",{className:"container-narrow py-10 text-center text-sm text-gray-700",children:[e.jsxs("p",{children:["If you like Scriberr, consider giving the project a star on"," ",e.jsx("a",{href:"https://github.com/rishikanthc/scriberr",target:"_blank",rel:"noreferrer",className:"text-blue-600 hover:text-blue-700 underline-offset-2 hover:underline",children:"GitHub"}),"."]}),e.jsx("div",{className:"mt-4 flex justify-center",children:e.jsx("a",{href:"https://ko-fi.com/H2H41KQZA3",target:"_blank",rel:"noopener noreferrer",children:e.jsx("img",{height:"36",style:{border:"0px",height:"36px"},src:"https://storage.ko-fi.com/cdn/kofi6.png?v=6",alt:"Buy Me a Coffee at ko-fi.com"})})})]})})}function j(){return e.jsxs("div",{className:"min-h-screen flex flex-col",children:[e.jsx(d,{}),e.jsxs("main",{className:"flex-1",children:[e.jsx(m,{}),e.jsx(p,{}),e.jsx(g,{})]}),e.jsx(f,{})]})}const b=o(document.getElementById("root"));b.render(e.jsx(l.StrictMode,{children:e.jsx(j,{})}));
diff --git a/docs/screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png b/docs/screenshots/scriberr-Ollama_openAI llm providers for chat and summarization.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c925fec011700f25e001f88843c1a90e90e90f9
GIT binary patch
literal 256774
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