diff --git a/.agent/rules/fluid-ui-style-guide.md b/.agent/rules/fluid-ui-style-guide.md deleted file mode 100644 index 578c74a8..00000000 --- a/.agent/rules/fluid-ui-style-guide.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -trigger: always_on ---- - -The Fluid UI Style Guide - -1. Deference & Negative Space - - Principle: The UI must recede; content must dominate. - - Action: Maximize white space. Avoid heavy borders or boxes to separate content. Use translucency (blur) for background layers to maintain context without visual noise. - -2. Hierarchy via Typography - - Principle: Group information using font weight and size, not lines. - - Action: Establish a strict scale (e.g., Large Bold for titles, Medium for headers, Regular for body). Use 100% black for primary text and grey/opacity (e.g., 60%) for secondary text to guide the eye. - -3. The 44pt Touch Standard - - Principle: Accuracy allows for speed. - - Action: All interactive tap targets must be at least 44x44 points, regardless of the visual icon size. Place primary navigation in the bottom "thumb zone." - -4. Physics-Based Motion - - Principle: Objects have mass and friction; nothing moves linearly. - - Action: Use Spring Animations (configure mass, stiffness, damping) instead of ease-in/out. Ensure all animations are interruptible (if a user grabs a moving object, it stops instantly). - -5. Color as Function - - Principle: Color indicates interactivity, not decoration. - - Action: Select one "Tint Color" (Accent) for buttons, links, and active states. Keep the structural UI monochrome (whites, greys, blacks). - -6. Direct Manipulation (Gestures) - - Principle: Users should manipulate the object, not a proxy for the object. - - Action: Prioritize swipes (to delete/go back) and pinches over tap-based buttons. Ensure the animation tracks 1:1 with the user's finger during the gesture. - -7. Depth & The Z-Axis - - Principle: Interfaces are stacked layers, not flat planes. - - Action: Use shadows and dimming to indicate elevation. When a modal or sheet appears, the background layer should scale down slightly or darken to push it "back" in Z-space. - -8. Instant Multisensory Feedback - - Principle: Every interaction requires acknowledgment. - - Action: Response latency must be <100ms. Pair visual state changes (highlights/press states) with subtle haptic feedback (tactile bumps) for confirmation. - -9. Zero Dead Ends (Empty States) - - Principle: An empty screen is a broken experience. - - Action: Never leave a container blank. Design illustrative "Empty States" that explain what belongs there and provide a direct button to create that content. - -10. Functional Consistency - - Principle: Predictability reduces cognitive load. - - Action: Reuse system paradigms. If it looks like a switch, it must toggle. If it looks like a search bar, it must filter. Do not create custom controls if a standard system control exists. \ No newline at end of file diff --git a/.agent/rules/go-rules.md b/.agent/rules/go-rules.md deleted file mode 100644 index 979c3a59..00000000 --- a/.agent/rules/go-rules.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -trigger: always_on ---- - -The "10 Commandments" of Go Backend Development - - Accept Interfaces, Return Structs. Keep functions flexible by accepting behaviors (interfaces) and robust by returning data (structs). Let the consumer define the interface. - - Propagate Context Everywhere. Pass context.Context as the first argument to every I/O function. It is essential for handling cancellation, timeouts, and tracing in distributed systems. - - Wrap, Don't Hide, Errors. Never ignore errors. Wrap them with fmt.Errorf("%w", err) to add context (traceability) while allowing the caller to unwrap and inspect the root cause. - - Package by Feature. Avoid models and controllers folders. Organize code by business domain (e.g., billing, auth) to ensure high cohesion and avoid circular dependencies. - - Limit Concurrency. Never spawn unlimited goroutines per request. Use Worker Pools or semaphores to cap concurrency and prevent memory exhaustion under load. - - Pre-allocate Memory. Use make([]T, 0, cap) when the size is known. Avoiding repeated array resizing reduces CPU overhead and garbage collection pressure. - - Avoid Global State. Ban global variables and init() side effects. Use explicit Dependency Injection to make your server testable, modular, and thread-safe. - - Pool Hot Objects. Use sync.Pool for frequently allocated short-lived objects (buffers, contexts). This drastically reduces Garbage Collection pauses in high-throughput systems. - - Log Structurally. Abandon text logs. Use log/slog to emit JSON logs with key-value pairs (order_id=123), making your production logs queryable and actionable. - - Write Table-Driven Tests. Utilize Go’s idiom of table-driven testing to cover multiple edge cases cleanly and efficiently without duplicating test logic. \ No newline at end of file diff --git a/.agent/rules/react-rules.md b/.agent/rules/react-rules.md deleted file mode 100644 index c8f21517..00000000 --- a/.agent/rules/react-rules.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -trigger: always_on ---- - -1. **Separate Server State from Client State.** - Never store API data in Redux or Context. Use **TanStack Query** (or SWR) for caching and fetching, reserving **Zustand** or Context strictly for UI state (e.g., themes, modals). - -2. **Organize by Feature, Not File Type.** - Abandon `/components` and `/hooks` folders. **Colocate** related code (api, hooks, components) into feature-specific directories (e.g., `features/user-profile`) to ensure maintainability as the codebase grows. - -3. **Derive State, Do Not Sync It.** - Never use `useEffect` to update a state variable based on another state variable. Calculate the derived value directly in the render body to eliminate "glitches" and extra render cycles. - -4. **Preserve Referential Equality.** - Wrap objects and functions passed to memoized children in **`useMemo`** and **`useCallback`**. Stable references prevent expensive, unnecessary re-renders of child components. - -5. **Eliminate Render-Fetch Waterfalls.** - Do not chain data fetching (Parent fetches -> Renders -> Child fetches). Use route-level loaders or parallel queries to fetch all required data immediately. - -6. **Prefer Composition Over Prop Drilling.** - Avoid passing data through five layers of components. Use **Component Composition** (passing components as `children` or props) to make the hierarchy flat and efficient. - -7. **Virtualize Long Lists.** - Never render large datasets directly to the DOM. Use **TanStack Virtual** or `react-window` to render only the visible items, ensuring the browser remains responsive. - -8. **Lazy Load Routes.** - Implement **Code Splitting** using `React.lazy` and `Suspense` for route-level components. This ensures users only download the JavaScript required for the current page. - -9. **Enforce Strict TypeScript.** - Treat `any` as a compile error. Define explicit interfaces for all props and API responses to guarantee safe refactoring and self-documenting code. - -10. **Isolate Logic in Custom Hooks.** - Keep UI components pure (JSX only). Abstract complex state logic, side effects, and listeners into **Custom Hooks** to ensure logic is testable, reusable, and readable. \ No newline at end of file diff --git a/.agent/rules/scriberr-design-system.md b/.agent/rules/scriberr-design-system.md deleted file mode 100644 index 79d0f6ce..00000000 --- a/.agent/rules/scriberr-design-system.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -trigger: always_on ---- - -**Scriberr Premium Design System**. - -This is a comprehensive set of design guidelines for the **Scriberr Design System**. These rules are tailored for your designer to ensure the "Raised/Floating" aesthetic works perfectly with a **Pure White** background and your specific **Orange Gradient** branding. - ---- - -### **Scriberr Design System Guidelines** - -#### **1. Depth & Elevation (The "Floating" Strategy)** -Since the main background is **Pure White (`#FFFFFF`)**, we cannot rely on background contrast to separate cards. We must rely on **Physics-Based Shadowing** and **Micro-Borders**. - -* **The "Resting" State (Default Cards)** - * **Concept:** Objects should not look like they are flying; they should look like thick cardstock resting on a desk. - * **Guideline:** Use a **Dual-Shadow approach**: - 1. *Ambient Shadow:* A very tight, low-blur shadow to ground the object (e.g., `0px 2px 4px`). - 2. *Key Light Shadow:* A larger, very soft shadow to suggest depth (e.g., `0px 10px 20px`). - * **The "Micro-Border":** Every floating white card **must** have a `1px` solid border in a very pale grey (`rgba(0,0,0,0.06)`). This prevents the "ghosting" effect where white cards blend invisibly into the white background on low-quality monitors. - -* **The "Active" State (Hover/Lift)** - * **Guideline:** When a user hovers over a card (like a list item), do not change the background color. Instead, **lift** the card. - * **Action:** Increase the shadow spread and slightly nudge the element up (`translateY(-2px)`). This mimics tactile feedback. - -#### **2. Color Usage (The 60-30-10 Rule)** -Your **Orange Gradient** (`#FFAB40` → `#FF3D00`) is high-energy. It must be the "jewel" of the interface, not the wallpaper. - -* **60% Neutral (The Canvas):** - * **Light Mode:** Strictly Pure White (`#FFFFFF`). - * **Dark Mode:** Deep Carbon (`#0A0A0A`). -* **30% Structure (Text & Lines):** - * Use shades of **Neutral Grey** (e.g., `#171717` to `#737373`). - * *Constraint:* Never use pure black (`#000000`) for text. It creates a harsh "strobe" effect against white backgrounds. -* **10% Accent (The Logo Colors):** - * **Primary Action:** Use the **Orange Gradient** for the single most important action on a screen (e.g., "New Recording," "Save"). - * **Secondary Action:** Use the **Solid Middle Orange** (`#FF6D20`) for links or active icons. - * **Tertiary/Backgrounds:** Use a "Washed Orange" (`rgba(255, 171, 64, 0.08)`) for active states in menus or selected items to tie them to the brand without overwhelming the eye. - -#### **3. Typography & Hierarchy** -To maintain the "Sleek/Premium" feel, hierarchy is established through **Weight**, not just Size. - -* **Headings:** Use a geometric sans-serif (like Inter, DM Sans, or Plus Jakarta). - * *Style:* Bold or ExtraBold. - * *Color:* Darkest Grey (`#171717`). -* **Body Text:** - * *Style:* Regular. - * *Color:* Medium Grey (`#525252`). - * *Constraint:* Keep line-height generous (approx 1.5 to 1.6) to maintain the "airiness" of the white background. -* **Metadata (Dates, file sizes):** - * *Style:* Medium Weight, Smaller Size. - * *Color:* Light Grey (`#A3A3A3`). - * *Transformation:* Use Uppercase + Wide Letter Spacing (Tracking) occasionally for labels (e.g., "AUDIO FILES") to add elegance. - -#### **4. Iconography** -Icons must bridge the gap between your detailed logo and the minimal UI. - -* **Style:** Use **Rounded** or **Soft-Edge** strokes (2px width). Avoid sharp, jagged icons. -* **Coloring Strategy:** - * *Inactive Icons:* Slate Grey (`#A3A3A3`). - * *Active Icons:* Solid Orange (`#FF6D20`). - * *Feature Icons:* If you need a large icon (e.g., empty state placeholder), apply the **Brand Gradient** to the icon stroke for a premium touch. - -#### **5. Dark Mode Specifics (The "Achromatic" Rule)** -Since you requested **no blues**, the dark mode must rely on "Surface Lightness." - -* **The "Rim Light" Technique:** - * In the absence of shadows, every card in Dark Mode must have a `1px` border of `rgba(255, 255, 255, 0.08)`. This mimics light catching the edge of the plastic/metal. -* **Elevation Mapping:** - * *Background:* `#0A0A0A` (Deepest) - * *Card Level 1:* `#141414` (Slightly Lighter) - * *Card Level 2 (Modals/Dropdowns):* `#1F1F1F` (Lighter still) -* **Text Contrast:** - * Do not use pure white (`#FFFFFF`) text on dark backgrounds; it causes "halation" (blurring). Use `#EDEDED` (93% White). - -#### **6. Interactive Components** - -* **Buttons:** - * *Primary:* Full Gradient background. Text is White. - * *Secondary:* White background, Grey border, Dark text. Hover border changes to Orange. - * *Ghost:* Transparent background, Orange text. -* **Inputs (Text Fields):** - * *Default:* Light Grey background (`#F9FAFB`) with no border. This feels softer than a harsh outlined box. - * *Focus:* White background + Orange Border + Orange "Glow" shadow. - -#### **7. Status Colors (Harmonization)** -Standard "Traffic Light" colors often clash with custom branding. Use this adjusted palette to harmonize with your Orange logo: - -* **Success:** **Teal/Emerald** (`#10B981`). (Standard Green fights with Orange; Teal complements it). -* **Error:** **Warm Red** (`#EF4444`). (Standard bright red is too aggressive; Warm red matches the "heat" of the orange). -* **Warning:** **Amber** (`#F59E0B`). (Naturally fits the palette). - - - -#### **7. The CSS Theme Block** - -The custom theme has been added to the index.css. Use these specifiers to style all UI components. All style setting values should be defined centrally in index.css -Colors should never be hardcoded. -A set of carefully designed components are there in @/components/ui folder. try to reuse them as much as you can for consistency. If what you need is not there then feel free to build a custom one. \ No newline at end of file diff --git a/.agent/workflows/check-build.md b/.agent/workflows/check-build.md deleted file mode 100644 index b3046051..00000000 --- a/.agent/workflows/check-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: ---- - -Run build.sh in the project root and verify it builds successfully without errors \ No newline at end of file diff --git a/.agent/workflows/commit-changes.md b/.agent/workflows/commit-changes.md deleted file mode 100644 index 4e2e1e84..00000000 --- a/.agent/workflows/commit-changes.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -description: commit changes ---- - -Update .gitignore if needed -Stage all required files -Commit all changes with a good description that answers what or why diff --git a/internal/api/chat_handlers.go b/internal/api/chat_handlers.go index 6b024742..e4c0ab4b 100644 --- a/internal/api/chat_handlers.go +++ b/internal/api/chat_handlers.go @@ -573,13 +573,17 @@ func (h *Handler) SendChatMessage(c *gin.Context) { } // Set up streaming response with context info headers - c.Header("Content-Type", "text/plain") - c.Header("Cache-Control", "no-cache") + c.Header("Content-Type", "text/plain; charset=utf-8") + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Connection", "keep-alive") + c.Header("Transfer-Encoding", "chunked") + c.Header("X-Accel-Buffering", "no") // Disable nginx buffering c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Expose-Headers", "X-Context-Used, X-Context-Limit, X-Messages-Trimmed") c.Header("X-Context-Used", fmt.Sprintf("%d", currentTokenCount)) c.Header("X-Context-Limit", fmt.Sprintf("%d", contextWindow)) c.Header("X-Messages-Trimmed", fmt.Sprintf("%d", trimmedCount)) + c.Status(http.StatusOK) // Start the response immediately // Stream the response ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Minute) diff --git a/internal/api/summarize_handlers.go b/internal/api/summarize_handlers.go index 3f0a4ab7..0c40582c 100644 --- a/internal/api/summarize_handlers.go +++ b/internal/api/summarize_handlers.go @@ -55,10 +55,13 @@ func (h *Handler) Summarize(c *gin.Context) { start := time.Now() log.Printf("[summarize] start transcription_id=%s provider=%s model=%s content_len=%d", req.TranscriptionID, provider, req.Model, len(req.Content)) - // Stream response - c.Header("Content-Type", "text/event-stream") - c.Header("Cache-Control", "no-cache") + // Stream response with proper headers for real-time delivery + c.Header("Content-Type", "text/plain; charset=utf-8") + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Connection", "keep-alive") + c.Header("Transfer-Encoding", "chunked") + c.Header("X-Accel-Buffering", "no") // Disable nginx buffering + c.Status(http.StatusOK) // Start response immediately // Allow longer generation time for large transcripts and smaller models ctx, cancel := context.WithTimeout(c.Request.Context(), 60*time.Minute) diff --git a/prompt.txt b/prompt.txt deleted file mode 100644 index 28ea6e34..00000000 --- a/prompt.txt +++ /dev/null @@ -1,4 +0,0 @@ -We are building an audio transcription app called Scriberr using Go and React. -I want you to analyze and udnerstand the react frontend code in web/frontend and fix ALL lint errors in the code. - Do not break any form function or feature.. UI and UX should remain exactly the same. All features should work. -DO NOT INTRODUCE REGRESSIONS OR BUGS diff --git a/web/frontend/src/components/ChatSessionsSidebar.tsx b/web/frontend/src/components/ChatSessionsSidebar.tsx index 7bc8eefb..f34b6f80 100644 --- a/web/frontend/src/components/ChatSessionsSidebar.tsx +++ b/web/frontend/src/components/ChatSessionsSidebar.tsx @@ -174,10 +174,12 @@ export function ChatSessionsSidebar({ - + - +
+ +
New Chat Session

@@ -268,8 +270,8 @@ export function ChatSessionsSidebar({ className={` group relative p-3 rounded-xl border cursor-pointer transition-all duration-200 pr-10 min-h-[64px] ${session.id === activeSessionId - ? 'bg-card border-[#FF6D20] shadow-md ring-1 ring-[#FF6D20]/20 z-10' - : 'bg-card border-border/60 shadow-md hover:border-primary/50 hover:bg-card/80' + ? 'bg-[var(--bg-card)] dark:bg-[#1F1F1F] border-[#FF6D20] shadow-[0_2px_4px_rgba(0,0,0,0.04),0_8px_16px_rgba(0,0,0,0.06)] dark:shadow-[0_2px_4px_rgba(0,0,0,0.3),0_8px_16px_rgba(0,0,0,0.2)] ring-1 ring-[#FF6D20]/20 z-10' + : 'bg-[var(--bg-card)] dark:bg-[#141414] border-[rgba(0,0,0,0.06)] dark:border-[rgba(255,255,255,0.08)] shadow-[0_2px_4px_rgba(0,0,0,0.04),0_8px_16px_rgba(0,0,0,0.04)] dark:shadow-[0_2px_4px_rgba(0,0,0,0.2),0_8px_16px_rgba(0,0,0,0.1)] hover:shadow-[0_4px_8px_rgba(0,0,0,0.06),0_12px_24px_rgba(0,0,0,0.06)] hover:-translate-y-0.5 hover:border-[var(--brand-solid)]/30' } `} > diff --git a/web/frontend/src/features/transcription/components/audio-detail/SummaryDialog.tsx b/web/frontend/src/features/transcription/components/audio-detail/SummaryDialog.tsx index 693a3de2..f2f3dffe 100644 --- a/web/frontend/src/features/transcription/components/audio-detail/SummaryDialog.tsx +++ b/web/frontend/src/features/transcription/components/audio-detail/SummaryDialog.tsx @@ -31,7 +31,7 @@ import { useSummaryTemplates, useSummarizer, useExistingSummary } from "@/featur import { useTranscript, useAudioDetail } from "@/features/transcription/hooks/useAudioDetail"; -import { Sparkles, Download, Copy, RefreshCw, ChevronDown } from "lucide-react"; +import { Sparkles, Download, Copy, RefreshCw, ChevronDown, FileText } from "lucide-react"; interface SummaryDialogProps { audioId: string; @@ -43,7 +43,7 @@ interface SummaryDialogProps { export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDialogProps) { const { toast } = useToast(); const { data: templates = [], isLoading: templatesLoading } = useSummaryTemplates(); - const { data: existingSummary } = useExistingSummary(audioId); + const { data: existingSummary, isLoading: summaryLoading } = useExistingSummary(audioId); const { data: transcript } = useTranscript(audioId, true); const { data: audioFile } = useAudioDetail(audioId); @@ -58,11 +58,20 @@ export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDia const selectedTemplate = templates.find(t => t.id === selectedTemplateId); // Auto-show existing summary if available and not streaming + // Wait for loading to complete to prevent blank display useEffect(() => { - if (isOpen && existingSummary && !isStreaming && !streamContent) { + if (isOpen && !summaryLoading && existingSummary?.content && !isStreaming && !streamContent) { setShowOutput(true); } - }, [isOpen, existingSummary, isStreaming, streamContent]); + }, [isOpen, existingSummary, summaryLoading, isStreaming, streamContent]); + + // Reset state when dialog closes + useEffect(() => { + if (!isOpen) { + setShowOutput(false); + setSelectedTemplateId(""); + } + }, [isOpen]); const handleStartSummary = () => { if (!selectedTemplate || !transcript) return; @@ -98,134 +107,193 @@ export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDia URL.revokeObjectURL(url); }; + // Handle close - prevent closing during streaming + const handleOpenChange = (open: boolean) => { + if (!open && isStreaming) { + // Don't allow closing during streaming + return; + } + onClose(open); + }; + if (showOutput) { - // Output View + // Output View - Redesigned with Scriberr Design System return ( -

- - - - + + + + +
+ +
Summary
- + {isStreaming ? ( <> - Generating summary... - + Generating summary + + + + + ) : ( - Summary {error ? 'failed' : 'ready'} + {error ? 'Generation failed' : 'Summary ready'} )}
-
- - - -
- -
- {error ? ( -

{error}

- ) : ( -

, - h1: ({ node, ...props }) =>

, - h2: ({ node, ...props }) =>

, - h3: ({ node, ...props }) =>

, - li: ({ node, ...props }) =>
  • , - strong: ({ node, ...props }) => , +
    + {/* Action buttons */} +
    + + + +
    + + {/* Content area - no inner card, full width, reading font */} +
    + {error ? ( +

    {error}

    + ) : isStreaming && !streamContent ? ( + /* Generating animation while waiting for first chunk */ +
    +
    +
    +
    + +
    +

    Generating summary...

    +

    This may take a moment

    +
    + ) : ( +
    +

    , + h1: ({ node, ...props }) =>

    , + h2: ({ node, ...props }) =>

    , + h3: ({ node, ...props }) =>

    , + li: ({ node, ...props }) =>
  • , + strong: ({ node, ...props }) => , + ul: ({ node, ...props }) =>
      , + ol: ({ node, ...props }) =>
        , + }} + > + {streamContent || existingSummary?.content || ""} + + {isStreaming && ( + + )} +
  • + )} + {!error && !streamContent && !existingSummary?.content && !isStreaming && ( +

    No content to display.

    + )} +
  • ); } - // Template Selector View + // Template Selector View - Redesigned with Scriberr Design System return ( - - - - Summarize Transcript - Choose a summarization template to generate insights. + + { + // Prevent closing when clicking inside popover + if (tplPopoverOpen) { + e.preventDefault(); + } + }} + > + + +
    + +
    + Summarize Transcript +
    + + Choose a summarization template to generate insights +
    - {llmReady === false && ( -
    - LLM is not configured or active. Please check settings. -
    - )} +
    + {llmReady === false && ( +
    + + LLM is not configured or active. Please check settings. +
    + )} -
    -
    +
    - + - + e.preventDefault()} + > - + - {templatesLoading ? 'Loading...' : 'No templates found'} + {templatesLoading ? 'Loading...' : 'No templates found'} {templates.map(t => ( { setSelectedTemplateId(t.id); setTplPopoverOpen(false); }} - className="rounded-sm aria-selected:bg-[var(--brand-solid)] aria-selected:text-white cursor-pointer" + className="rounded-lg py-2.5 px-3 aria-selected:bg-[var(--brand-solid)] aria-selected:text-white cursor-pointer transition-colors" >
    {t.name} @@ -246,22 +314,24 @@ export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDia

    Selected template has no model configured.

    )}
    +
    -
    - - -
    +
    + +