Skip to content
Merged
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
17 changes: 16 additions & 1 deletion apps/web/__tests__/unit/generate-ai-title.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ vi.mock("workflow", () => ({

vi.mock("server-only", () => ({}));

import { shouldReplaceVideoTitle } from "@/workflows/generate-ai";
import {
getAiLanguageInstruction,
shouldReplaceVideoTitle,
} from "@/workflows/generate-ai";

describe("shouldReplaceVideoTitle", () => {
it("replaces default Cap titles", () => {
Expand Down Expand Up @@ -84,3 +87,15 @@ describe("shouldReplaceVideoTitle", () => {
).toBe(false);
});
});

describe("getAiLanguageInstruction", () => {
it("uses transcript language when auto-detect is selected", () => {
expect(getAiLanguageInstruction("auto")).toContain(
"same language as the transcript",
);
});

it("uses the selected language name", () => {
expect(getAiLanguageInstruction("es")).toContain("Spanish");
});
});
88 changes: 88 additions & 0 deletions apps/web/__tests__/unit/transcribe-language.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, expect, it, vi } from "vitest";

vi.mock("@cap/database", () => ({
db: vi.fn(),
}));

vi.mock("@cap/env", () => ({
serverEnv: vi.fn(() => ({})),
}));

vi.mock("@cap/utils", () => ({
userIsPro: vi.fn(),
}));

vi.mock("@cap/web-backend", () => ({
Storage: {},
}));

vi.mock("@deepgram/sdk", () => ({
createClient: vi.fn(),
}));

vi.mock("@/lib/audio-enhance", () => ({
ENHANCED_AUDIO_CONTENT_TYPE: "audio/mpeg",
ENHANCED_AUDIO_EXTENSION: "mp3",
enhanceAudioFromUrl: vi.fn(),
}));

vi.mock("@/lib/audio-extract", () => ({
checkHasAudioTrack: vi.fn(),
extractAudioFromUrl: vi.fn(),
}));

vi.mock("@/lib/generate-ai", () => ({
startAiGeneration: vi.fn(),
}));

vi.mock("@/lib/media-client", () => ({
checkHasAudioTrackViaMediaServer: vi.fn(),
extractAudioViaMediaServer: vi.fn(),
isMediaServerConfigured: vi.fn(),
probeVideoViaMediaServer: vi.fn(),
}));

vi.mock("@/lib/server", () => ({
runPromise: vi.fn(),
}));

vi.mock("@/lib/transcribe-utils", () => ({
formatToWebVTT: vi.fn(),
}));

vi.mock("@/lib/video-storage", () => ({
decodeStorageVideo: vi.fn(),
}));

vi.mock("workflow", () => ({
FatalError: class FatalError extends Error {},
}));

import {
AI_GENERATION_LANGUAGES,
isAiGenerationLanguage,
parseAiGenerationLanguage,
} from "@cap/web-domain";
import { getDeepgramTranscriptionOptions } from "@/workflows/transcribe";

describe("AI generation language support", () => {
it("does not expose unsupported transcription languages", () => {
expect(AI_GENERATION_LANGUAGES).not.toHaveProperty("pa");
expect(isAiGenerationLanguage("pa")).toBe(false);
expect(parseAiGenerationLanguage("pa")).toBe("auto");
});

it("constrains Deepgram auto-detection to detectable languages", () => {
expect(getDeepgramTranscriptionOptions("auto")).toMatchObject({
model: "nova-3",
detect_language: expect.arrayContaining(["en", "es", "zh"]),
});
});

it("passes explicit languages to Deepgram", () => {
expect(getDeepgramTranscriptionOptions("zh")).toMatchObject({
model: "nova-3",
language: "zh",
});
});
});
28 changes: 27 additions & 1 deletion apps/web/actions/organization/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import { organizations } from "@cap/database/schema";
import { userIsPro } from "@cap/utils";
import {
AI_GENERATION_LANGUAGE_AUTO,
type AiGenerationLanguage,
isAiGenerationLanguage,
} from "@cap/web-domain";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { requireOrganizationSettingsManager } from "./authorization";
Expand All @@ -17,6 +22,7 @@ type OrganizationSettingsInput = {
disableComments?: boolean;
hideShareableLinkCapLogo?: boolean;
shareableLinkUseOrganizationIcon?: boolean;
aiGenerationLanguage?: AiGenerationLanguage;
};

const proOrganizationSettingKeys = [
Expand All @@ -25,8 +31,21 @@ const proOrganizationSettingKeys = [
"disableTranscript",
"hideShareableLinkCapLogo",
"shareableLinkUseOrganizationIcon",
"aiGenerationLanguage",
] as const satisfies readonly (keyof OrganizationSettingsInput)[];

const defaultProOrganizationSettings = {
disableSummary: false,
disableChapters: false,
disableTranscript: false,
hideShareableLinkCapLogo: false,
shareableLinkUseOrganizationIcon: false,
aiGenerationLanguage: AI_GENERATION_LANGUAGE_AUTO,
} as const satisfies Pick<
Required<OrganizationSettingsInput>,
(typeof proOrganizationSettingKeys)[number]
>;

const preserveProSettings = (
submittedSettings: OrganizationSettingsInput,
existingSettings: OrganizationSettingsInput | null | undefined,
Expand All @@ -35,7 +54,7 @@ const preserveProSettings = (
...Object.fromEntries(
proOrganizationSettingKeys.map((key) => [
key,
existingSettings?.[key] ?? false,
existingSettings?.[key] ?? defaultProOrganizationSettings[key],
]),
),
});
Expand All @@ -53,6 +72,13 @@ export async function updateOrganizationSettings(
throw new Error("Settings are required");
}

if (
settings.aiGenerationLanguage !== undefined &&
!isAiGenerationLanguage(settings.aiGenerationLanguage)
) {
throw new Error("Unsupported AI generation language");
}

if (!user.activeOrganizationId) {
throw new Error("Organization not found");
}
Expand Down
33 changes: 4 additions & 29 deletions apps/web/actions/videos/translation-languages.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,4 @@
export const SUPPORTED_LANGUAGES = {
en: "English",
es: "Spanish",
fr: "French",
de: "German",
pt: "Portuguese",
it: "Italian",
nl: "Dutch",
pl: "Polish",
sk: "Slovak",
ru: "Russian",
tr: "Turkish",
ja: "Japanese",
ko: "Korean",
zh: "Chinese (Simplified)",
ar: "Arabic",
hi: "Hindi",
bn: "Bengali",
ta: "Tamil",
te: "Telugu",
mr: "Marathi",
gu: "Gujarati",
pa: "Punjabi",
ur: "Urdu",
fa: "Persian",
he: "Hebrew",
} as const;

export type LanguageCode = keyof typeof SUPPORTED_LANGUAGES;
export {
type LanguageCode,
SUPPORTED_LANGUAGES,
} from "@cap/web-domain";
Loading
Loading