feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
137
apps/web/src/modules/tts/composer/hooks/use-composer.tsx
Normal file
137
apps/web/src/modules/tts/composer/hooks/use-composer.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useForm, useFormContext } from "react-hook-form";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
import { MODELS } from "@turbostarter/ai/tts/constants";
|
||||
import { ttsSchema } from "@turbostarter/ai/tts/schema";
|
||||
import { useDebounceCallback } from "@turbostarter/shared/hooks";
|
||||
|
||||
import { useTts } from "~/modules/tts/use-tts";
|
||||
|
||||
import type {
|
||||
TtsOptionsPayload,
|
||||
TtsPayload,
|
||||
} from "@turbostarter/ai/tts/schema";
|
||||
import type { Voice } from "@turbostarter/ai/tts/types";
|
||||
import type { WatchObserver } from "react-hook-form";
|
||||
|
||||
interface TtsComposerState {
|
||||
text: string;
|
||||
options: TtsOptionsPayload;
|
||||
setText: (text: string) => void;
|
||||
setOptions: (options: Partial<TtsOptionsPayload>) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
model: MODELS[0].id,
|
||||
voice: {
|
||||
id: "",
|
||||
speed: 1,
|
||||
stability: 0.5,
|
||||
similarity: 0.75,
|
||||
boost: false,
|
||||
},
|
||||
};
|
||||
|
||||
const useTtsComposerStore = create<TtsComposerState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
text: "",
|
||||
options: DEFAULT_OPTIONS,
|
||||
setText: (text) => set({ text }),
|
||||
setOptions: (options) =>
|
||||
set((state) => ({
|
||||
options: { ...state.options, ...options },
|
||||
})),
|
||||
reset: () =>
|
||||
set({
|
||||
text: "",
|
||||
options: DEFAULT_OPTIONS,
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: "tts-options",
|
||||
partialize: (state) => ({ options: state.options }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
interface UseComposerProps {
|
||||
voices: Voice[];
|
||||
}
|
||||
|
||||
export const useComposer = ({ voices }: UseComposerProps) => {
|
||||
const { speak } = useTts();
|
||||
const { options, reset, setOptions, setText } = useTtsComposerStore();
|
||||
|
||||
const newForm = useForm({
|
||||
resolver: zodResolver(ttsSchema),
|
||||
defaultValues: {
|
||||
text: "",
|
||||
options,
|
||||
},
|
||||
});
|
||||
const contextForm = useFormContext<TtsPayload>();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const form = contextForm ?? newForm;
|
||||
|
||||
useEffect(() => {
|
||||
if (voices.length && !options.voice.id) {
|
||||
const newOptions = {
|
||||
...options,
|
||||
voice: {
|
||||
...options.voice,
|
||||
id: voices[0]?.id ?? "",
|
||||
},
|
||||
};
|
||||
setOptions(newOptions);
|
||||
form.setValue("options", newOptions);
|
||||
}
|
||||
}, [voices, options, setOptions, form]);
|
||||
|
||||
const sync: WatchObserver<TtsPayload> = useCallback(
|
||||
(values) => {
|
||||
setText(values.text ?? "");
|
||||
setOptions({
|
||||
...(values.options ?? DEFAULT_OPTIONS),
|
||||
voice: {
|
||||
...(values.options?.voice ?? DEFAULT_OPTIONS.voice),
|
||||
id: values.options?.voice?.id ?? "",
|
||||
},
|
||||
});
|
||||
},
|
||||
[setText, setOptions],
|
||||
);
|
||||
|
||||
const debouncedSync = useDebounceCallback(sync, 500);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = form.watch(debouncedSync);
|
||||
return () => subscription.unsubscribe();
|
||||
}, [form, debouncedSync]);
|
||||
|
||||
const onSubmit = (input: TtsPayload) => {
|
||||
form.resetField("text");
|
||||
speak.mutate(input);
|
||||
};
|
||||
|
||||
const resetVoiceSettings = () => {
|
||||
form.setValue("options.voice", {
|
||||
...DEFAULT_OPTIONS.voice,
|
||||
id: options.voice.id,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
setText,
|
||||
onSubmit,
|
||||
reset,
|
||||
resetVoiceSettings,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user