Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f1d6dc3
wip
swordbreaker Feb 9, 2026
369ad35
wip
swordbreaker Feb 9, 2026
c174a6d
feat: redesign media upload UI with modern animations and improved UX
Feb 10, 2026
a38ac67
refactor: modularize media processing and task management
swordbreaker Feb 11, 2026
fe5ad2d
wip
Feb 12, 2026
70974ee
wip
swordbreaker Feb 13, 2026
5a6f8af
wio
swordbreaker Feb 13, 2026
4b7724b
wip
swordbreaker Feb 13, 2026
225004e
wip
swordbreaker Feb 16, 2026
0e57dae
wip
swordbreaker Feb 18, 2026
e9c6cb3
feat: redesign transcription detail page with animations and improved…
swordbreaker Feb 18, 2026
637817b
Worked on the ui
swordbreaker Feb 19, 2026
28921dc
wip
swordbreaker Feb 20, 2026
b981aeb
Updated the edit view
swordbreaker Feb 20, 2026
aad15cd
cleanup of transcriptionService
swordbreaker Feb 20, 2026
12d0dbc
added dummy mode
swordbreaker Feb 23, 2026
23831de
wip
swordbreaker Feb 23, 2026
966b9ad
fixed the MediaPlaybackBar
swordbreaker Feb 23, 2026
31bfc7c
Squash merge ai/testing into feature/ui-redesing
swordbreaker Feb 23, 2026
57c1eda
update the ci files
swordbreaker Feb 23, 2026
e3eb7aa
Update the transcription list
swordbreaker Feb 23, 2026
eeaa603
wip
swordbreaker Feb 24, 2026
0a9ce15
added cleanupTranscriptions
swordbreaker Feb 24, 2026
83ce18c
Addd speaker stats
swordbreaker Feb 24, 2026
a46b9f9
wip
swordbreaker Feb 24, 2026
3adeee4
added a progress indication for one segement
swordbreaker Feb 24, 2026
089c038
added summary types
Feb 26, 2026
66a6e47
added support for word export
swordbreaker Feb 27, 2026
067b669
Merge branch 'main' into feature/ui-redesing
swordbreaker Feb 27, 2026
d9c3e7b
fixed pr findings
swordbreaker Feb 27, 2026
ad82090
Update bun.lock
swordbreaker Feb 27, 2026
d086146
updated the translation file
swordbreaker Feb 27, 2026
2a17981
Removed unused component
swordbreaker Feb 27, 2026
23333ca
bumpt version to 0.6.0
swordbreaker Feb 27, 2026
7825e3a
fixed pr findings
swordbreaker Feb 27, 2026
fa72e53
Delete KeyboardShortcutsHint.vue
swordbreaker Feb 27, 2026
b9f0703
fixed bun errors
swordbreaker Feb 27, 2026
dbb99ad
fixed console warnings
Mar 2, 2026
df4f783
wip
Mar 2, 2026
ac38b36
added onboarding
Mar 2, 2026
c897ad8
Add language selection to summary feature
Mar 2, 2026
135c047
wipo
Mar 2, 2026
bf7cf08
Merge branch 'feature/summary-language-selection' into feature/ui-red…
Mar 3, 2026
eb25a46
fixed some ui bugs and added summary language
Mar 3, 2026
88721f9
bumpt to v1.0.0
Mar 3, 2026
3e57e29
fixed pr finding s
Mar 3, 2026
e3dc40e
Merge branch 'main' into feature/ui-redesing
Mar 3, 2026
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
API_URL='http://localhost:8000'
DUMMY=false
GITHUB_TOKEN='YOUR_GITHUB_TOKEN'

LOGGER_LAYER_URI=github:DCC-BS/nuxt-layers/pino-logger
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- main

jobs:
build:
ci:
runs-on: ubuntu-latest
steps:
- uses: DCC-BS/ci-workflows/actions/node-bun-biome-playwright@v8
Expand All @@ -20,6 +20,5 @@ jobs:
run_playwright: "false"
install_method: "bun"
build_command: "bun run build"
test_command: "bunx playwright test"
artifact_name: "playwright-report"
artifact_retention_days: 30
- name: test
run: bun run test
8 changes: 0 additions & 8 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
name: Build and Publish Docker Image
on:
workflow_dispatch:
inputs:
version_bump:
description: Version bump type
required: true
default: patch
type: choice
options: [major, minor, patch]

permissions:
contents: write
Expand All @@ -18,7 +11,6 @@ jobs:
uses: DCC-BS/ci-workflows/.github/workflows/publish-docker.yml@v8
secrets: inherit
with:
release_type: ${{ inputs.version_bump }} # major|minor|patch
version_project_type: "node" # or "python"
registry: ghcr.io
image_name: ghcr.io/${{ github.repository }}
Expand Down
1 change: 1 addition & 0 deletions .nuxtrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setups.@nuxt/test-utils="4.0.0"
132 changes: 132 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Transcribo Frontend - Agent Coding Guidelines

## Build & Development Commands

```bash
# Install dependencies (Bun required, not npm)
bun install

# Development server
bun run dev

# Build for production
bun run build

# Start production server
bun run start

# Generate static site
bun run generate

# Linting & Formatting
bun run lint # Biome format --write
bun run check # Biome check --fix

# Docker
bun run docker:dev # Start dev container
bun run docker:dev:down # Stop dev container
bun run docker:prod # Start production container
bun run docker:prod:down # Stop production container
```

## Package Manager
**Use `bun` for all package operations, NOT npm.**

## Code Style Guidelines

### TypeScript & Vue
- **Always use Composition API** (`<script setup lang="ts">`)
- **Never use `any` type** - use `unknown` with validation or specific types
- **Never use non-null assertion `!`** - structure code so TypeScript understands nullable values
- **Always use semicolons** - terminate all statements
- **Prefer undefined over null** - use `ref(undefined)` not `ref(null)`
- **Non-null refs** - initialize refs with values, not null
- **Explicit types** - function parameters and return types must be typed
- **Function declarations** - use `function foo()` not `const foo = () => {}`
- **Async/await** - prefer over promise chains

### Naming Conventions
- **Pages**: kebab-case (`user-profile.vue`)
- **Components**: PascalCase (`UserProfileCard.vue`)
- **Composables**: camelCase with `use` prefix (`useCounter.ts`)
- **Server API**: kebab-case (`user-account.get.ts`)
- **Utils/Services**: camelCase (`formatDate.ts`)

### Import Aliases
```typescript
~/ // app/ directory
~~/ // root directory
~~/server // server directory
```

### UI & Styling
- **Use Nuxt UI components** instead of custom implementations
- **Use Tailwind CSS** for styling (native, no custom CSS when possible)
- **Use Lucide icons** (auto-imported via Nuxt UI)
- **Use motion** for animations (auto-imported via `motion-v`)

### Formatting (Biome)
- 4 space indentation
- Double quotes for strings
- Semicolons required
- `organizeImports` enabled

## Key Conventions from Cursor/Copilot Rules
- Use `===` and `!==` for equality
- No `console` in production code
- No `@ts-ignore` - always fix type errors
- No TypeScript enums
- No reassigning function parameters
- No unused imports/variables (ignored in .vue files)
- Use `for-of` instead of `forEach`
- Use `Array.isArray()` instead of `instanceof Array`
- Throw Error objects with messages
- Handle async errors properly with try/catch

## Project Structure
```
app/
components/ # Vue components (PascalCase)
composables/ # use* functions (camelCase) + other composables
pages/ # Routes (kebab-case)
utils/ # Framework-agnostic utilities
services/ # API/business logic (e.g., indexDbService.ts)
types/ # TypeScript definitions
plugins/ # Nuxt plugins (e.g., api.ts, konvaPlugin.client.ts)
layouts/ # Page layouts (default.vue, edit.vue)
assets/ # Static assets (CSS, images, disclaimer.html)
server/
api/ # API endpoints (kebab-case with HTTP method suffix)
utils/ # Server utilities
changelogs/ # Version changelog markdown files
tsconfig.json # Server-specific TypeScript config
public/
ios/ # PWA icons for iOS
icons.json # PWA icon configuration
```

## Key Dependencies
- **Nuxt UI 4.x** - Component library
- **@ffmpeg/ffmpeg** - Audio/video processing in browser
- **dexie** - IndexedDB wrapper for local storage
- **konva / vue-konva** - Canvas rendering for timeline editor
- **ts-pattern** - Pattern matching
- **zod** - Runtime type validation
- **motion-v** - Vue animations

## Nuxt Features
- Auto-imports enabled for composables, utils, components
- TypeScript strict mode enabled
- Pinia for state management
- Nuxt UI for components
- i18n for internationalization (en, de)
- PWA support via @vite-pwa/nuxt
- Extends GitHub layers: backend_communication, health_check, feedback-control, logger

## Testing
- No test framework currently configured
- Tests should be added when implementing new features

## Before Committing
1. Run `bun run check` - fix all linting issues
2. Run `bun run build` - ensure production build works
16 changes: 3 additions & 13 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,10 @@ function handleKeyDown(event: KeyboardEvent) {
<template>
<NuxtPwaManifest />
<Changelogs />
<FeedbackControl />
<Disclaimer
app-name="Transcribo"
:postfixHTML="disclaimerText"
confirmation-text="Ich habe die Hinweise gelesen und verstanden und bestätige, dass ich Transcribo ausschliesslich unter Einhaltung der genannten Richtlinien verwende."
/>
<Disclaimer app-name="Transcribo" :postfixHTML="disclaimerText"
confirmation-text="Ich habe die Hinweise gelesen und verstanden und bestätige, dass ich Transcribo ausschliesslich unter Einhaltung der genannten Richtlinien verwende." />
<UApp>
<DialogView
:is-open="isOpen"
:title="title"
:message="message"
:on-confirm="onSubmit"
:on-cancel="onClose"
/>
<DialogView :is-open="isOpen" :title="title" :message="message" :on-confirm="onSubmit" :on-cancel="onClose" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
Expand Down
10 changes: 3 additions & 7 deletions app/components/AudioRecordingView.client.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const { isReady, abandonedRecording } =
logger: debugLog,
});

const audioRecorder = ref<typeof AudioRecorder>();
const shouldRecord = ref(false);
const isRecording = ref(false);
const audioBlob = ref<Blob | undefined>(undefined);
Expand All @@ -27,6 +26,7 @@ function onRecordingStopped(file: Blob, _: string) {
isRecording.value = false;
audioBlob.value = file;
userRecording.value = true;
emitAudio();
}

function emitAudio(): void {
Expand Down Expand Up @@ -62,10 +62,6 @@ const audioSessionActions = computed(() => [
t("pages.index.recordAudio") }}</UButton>
</div>
<div v-if="isReady && shouldRecord">
<AudioRecorder ref="audioRecorder" :logger="debugLog" auto-start :show-result="true"
@recording-stopped="onRecordingStopped" />
<div v-if="audioBlob" class="flex flex-col justify-center items-center mt-4">
<UButton @click="emitAudio">{{ t("audioRecorder.useRecording") }}</UButton>
</div>
<AudioRecorder :logger="debugLog" auto-start :show-result="true" @recording-stopped="onRecordingStopped" />
</div>
</template>
</template>
30 changes: 30 additions & 0 deletions app/components/EditorModeSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { motion } from "motion-v";
import { UButton } from "#components";

type EditorMode = "view" | "summary" | "edit" | "statistics";

const UButtonMotion = motion.create(UButton)

const mode = defineModel<EditorMode>({ default: "view" });

const { t } = useI18n();

const modes: { value: EditorMode; icon: string; label: string }[] = [
{ value: "view", icon: "i-lucide-eye", label: "viewer" },
{ value: "summary", icon: "i-lucide-sparkles", label: "summary" },
{ value: "edit", icon: "i-lucide-square-pen", label: "editor" },
{ value: "statistics", icon: "i-lucide-bar-chart-2", label: "statistics" },
];
</script>

<template>
<UFieldGroup>
<UButtonMotion v-for="(m, index) in modes" :key="m.value" :variant="mode === m.value ? 'solid' : 'soft'"
color="primary" :whileHover="{ scale: 1.02 }" :whileTap="{ scale: 0.98 }"
:transition="{ type: 'spring' as const, stiffness: 400, damping: 17 }" @click="mode = m.value">
<UIcon :name="m.icon" class="w-4 h-4" />
<span>{{ t(`mode.${m.label}`) }}</span>
</UButtonMotion>
</UFieldGroup>
</template>
29 changes: 21 additions & 8 deletions app/components/ExportToolbar.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
<script lang="ts" setup>
import type { ExportOptions } from "~/composables/export";
import type { StoredTranscription } from "~/types/storedTranscription";

interface InputProps {
transcription: StoredTranscription
}

const props = defineProps<InputProps>();

const { t } = useI18n();
const { exportAsText, exportAsSrt, exportAsJson } = useExport();
const transcriptionStore = useTranscriptionsStore();
const { exportAsText, exportAsSrt, exportAsJson, exportAsDocx } = useExport();

// Export options state
const exportOptions = ref<ExportOptions>({
const exportOptions = ref<Omit<ExportOptions, "transcription">>({
withSpeakers: true,
withTimestamps: true,
mergeSegments: false,
Expand All @@ -15,15 +21,19 @@ const exportOptions = ref<ExportOptions>({

// Functions to handle exports
function handleTextExport(): void {
exportAsText(exportOptions.value);
exportAsText({ ...exportOptions.value, transcription: props.transcription });
}

function handleSubtitleExport(): void {
exportAsSrt(exportOptions.value.withSpeakers);
exportAsSrt(props.transcription, exportOptions.value.withSpeakers);
}

function handleJsonExport(): void {
exportAsJson();
exportAsJson(props.transcription);
}

async function handleDocxExport(): Promise<void> {
await exportAsDocx({ ...exportOptions.value, transcription: props.transcription });
}
</script>

Expand Down Expand Up @@ -63,8 +73,7 @@ function handleJsonExport(): void {
</div>

<!-- Meeting summary toggle (text only) -->
<div v-if="transcriptionStore.currentTranscription?.summary"
class="flex items-center justify-between mb-3">
<div v-if="props.transcription.summary" class="flex items-center justify-between mb-3">
<div class="flex flex-col">
<span class="text-sm">{{ t('export.withSummary') }}</span>
<span class="text-xs text-gray-500">{{ t('export.textOnly') }}</span>
Expand All @@ -91,6 +100,10 @@ function handleJsonExport(): void {
<!-- Json Format -->
<UButton block variant="ghost" color="primary" icon="i-lucide-file-braces"
:label="t('export.formats.json')" @click="handleJsonExport" class="justify-start" />

<!-- Docx Format -->
<UButton block variant="ghost" color="primary" icon="i-lucide-file-type"
:label="t('export.formats.docx')" @click="handleDocxExport" class="justify-start" />
</div>
</div>
</template>
Expand Down
15 changes: 15 additions & 0 deletions app/components/HContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup lang="ts">

</script>

<template>
<div class="flex flex-col m-2 ring-1 ring-default rounded-md shadow-[2px_2px_1px_1px_#0000000D]">
<div class="border-b border-default p-2">
<slot name="top" />
</div>

<div class="flex grow min-h-0 p-2">
<slot />
</div>
</div>
</template>
Loading
Loading