Refactor current recording component (#691)
* upgrade deps * update buttons order * auto assess when modal open * display assess score in recording list * display assess mark on transcription list * auto scroll to current segment * toast may close * update ui components
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"markdown-it-sub": "^2.0.0",
|
||||
"markdown-it-sup": "^2.0.0",
|
||||
"mermaid": "^10.9.1",
|
||||
"sass": "^1.77.5",
|
||||
"sass": "^1.77.6",
|
||||
"vitepress": "^1.2.3",
|
||||
"vitepress-plugin-mermaid": "^2.0.16",
|
||||
"vue": "^3.4.29"
|
||||
|
||||
@@ -52,17 +52,17 @@
|
||||
"@types/intl-tel-input": "^18.1.4",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/mark.js": "^8.11.12",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/node": "^20.14.6",
|
||||
"@types/rails__actioncable": "^6.1.10",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/validator": "^13.12.0",
|
||||
"@types/wavesurfer.js": "^6.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.0",
|
||||
"@typescript-eslint/parser": "^7.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"electron": "^31.0.1",
|
||||
"electron": "^31.0.2",
|
||||
"electron-playwright-helpers": "^1.7.1",
|
||||
"eslint": "^9.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
@@ -79,41 +79,41 @@
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"zx": "^8.1.2"
|
||||
"zx": "^8.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@andrkrn/ffprobe-static": "^5.2.0",
|
||||
"@electron-forge/publisher-s3": "^7.4.0",
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@langchain/community": "^0.2.11",
|
||||
"@langchain/google-genai": "^0.0.17",
|
||||
"@langchain/community": "^0.2.12",
|
||||
"@langchain/google-genai": "^0.0.20",
|
||||
"@mozilla/readability": "^0.5.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-aspect-ratio": "^1.0.3",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.0",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.0",
|
||||
"@radix-ui/react-hover-card": "^1.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-menubar": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@rails/actioncable": "7.1.3",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-menubar": "^1.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.0",
|
||||
"@radix-ui/react-progress": "^1.1.0",
|
||||
"@radix-ui/react-radio-group": "^1.2.0",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.0",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.0",
|
||||
"@rails/actioncable": "7.1.3-4",
|
||||
"@sentry/electron": "^5.1.0",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@vidstack/react": "^1.11.22",
|
||||
"@vidstack/react": "^1.11.23",
|
||||
"ahoy.js": "^0.4.4",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.7.2",
|
||||
@@ -142,7 +142,7 @@
|
||||
"html-to-text": "^9.0.5",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"i18next": "^23.11.5",
|
||||
"intl-tel-input": "^23.0.12",
|
||||
"intl-tel-input": "^23.1.0",
|
||||
"js-md5": "^0.8.3",
|
||||
"langchain": "^0.2.5",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -150,7 +150,7 @@
|
||||
"mark.js": "^8.11.1",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.38.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"openai": "^4.51.0",
|
||||
"openai": "^4.52.0",
|
||||
"pitchfinder": "^2.3.2",
|
||||
"postcss": "^8.4.38",
|
||||
"proxy-agent": "^6.4.0",
|
||||
@@ -174,6 +174,6 @@
|
||||
"update-electron-app": "^3.0.0",
|
||||
"wavesurfer.js": "^7.7.15",
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.23.0"
|
||||
"zod-to-json-schema": "^3.23.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class RecordingsHandler {
|
||||
throw new Error(t("models.recording.notFound"));
|
||||
}
|
||||
|
||||
const assessment = await recording.assess(language)
|
||||
const assessment = await recording.assess(language);
|
||||
return assessment.toJSON();
|
||||
}
|
||||
|
||||
@@ -307,6 +307,20 @@ class RecordingsHandler {
|
||||
targetId,
|
||||
targetType,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: PronunciationAssessment,
|
||||
attributes: [
|
||||
[
|
||||
Sequelize.fn(
|
||||
"MAX",
|
||||
Sequelize.col("pronunciation_score")
|
||||
),
|
||||
"pronunciationScore",
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
attributes: [
|
||||
"targetId",
|
||||
"targetType",
|
||||
|
||||
@@ -39,7 +39,7 @@ function App() {
|
||||
<AISettingsProvider>
|
||||
<DbProvider>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster richColors position="top-center" />
|
||||
<Toaster richColors closeButton position="top-center" />
|
||||
<Tooltip id="global-tooltip" />
|
||||
<TranslateWidget />
|
||||
<LookupWidget />
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetClose,
|
||||
SheetTitle,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
GitCompareIcon,
|
||||
@@ -500,6 +501,34 @@ export const MediaCurrentRecording = () => {
|
||||
setIsRecording={setIsRecording}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={detailIsOpen ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
id="media-pronunciation-assessment-button"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("pronunciationAssessment")}
|
||||
className={
|
||||
layout?.name === "sm" ? "hidden" : "rounded-full w-8 h-8 p-0"
|
||||
}
|
||||
onClick={() => setDetailIsOpen(true)}
|
||||
>
|
||||
<GaugeCircleIcon
|
||||
className={`w-4 h-4
|
||||
${
|
||||
currentRecording.pronunciationAssessment
|
||||
? currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 80
|
||||
? "text-green-500"
|
||||
: currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 60
|
||||
? "text-yellow-600"
|
||||
: "text-red-500"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={isComparing ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
@@ -514,19 +543,6 @@ export const MediaCurrentRecording = () => {
|
||||
<GitCompareIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={isSelectingRegion ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("selectRegion")}
|
||||
className={
|
||||
layout?.name === "sm" ? "hidden" : "rounded-full w-8 h-8 p-0"
|
||||
}
|
||||
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
|
||||
>
|
||||
<TextCursorInputIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
@@ -545,27 +561,10 @@ export const MediaCurrentRecording = () => {
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={toggleCompare}
|
||||
onClick={() => setDetailIsOpen(true)}
|
||||
>
|
||||
<GitCompareIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("compare")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
|
||||
>
|
||||
<TextCursorInputIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("selectRegion")}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => setDetailIsOpen(true)}
|
||||
>
|
||||
<GaugeCircleIcon
|
||||
className={`w-4 h-4 mr-4
|
||||
<GaugeCircleIcon
|
||||
className={`w-4 h-4 mr-4
|
||||
${
|
||||
currentRecording.pronunciationAssessment
|
||||
? currentRecording.pronunciationAssessment
|
||||
@@ -578,10 +577,27 @@ export const MediaCurrentRecording = () => {
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<span>{t("pronunciationAssessment")}</span>
|
||||
</DropdownMenuItem>
|
||||
/>
|
||||
<span>{t("pronunciationAssessment")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={toggleCompare}
|
||||
>
|
||||
<GitCompareIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("compare")}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
data-tooltip-content={t("selectRegion")}
|
||||
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
|
||||
>
|
||||
<TextCursorInputIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("selectRegion")}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => setIsSharing(true)}
|
||||
@@ -625,6 +641,9 @@ export const MediaCurrentRecording = () => {
|
||||
displayClose={false}
|
||||
>
|
||||
<SheetHeader className="flex items-center justify-center -mt-4 mb-2">
|
||||
<SheetTitle className="hidden">
|
||||
{t("pronunciationAssessment")}
|
||||
</SheetTitle>
|
||||
<SheetClose>
|
||||
<ChevronDownIcon />
|
||||
</SheetClose>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
MediaPlayerProviderContext,
|
||||
} from "@renderer/context";
|
||||
import {
|
||||
GaugeCircleIcon,
|
||||
LoaderIcon,
|
||||
MicIcon,
|
||||
MoreHorizontalIcon,
|
||||
@@ -90,6 +91,28 @@ export const MediaRecordings = () => {
|
||||
<span>{formatDuration(recording.duration, "ms")}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{recording.pronunciationAssessment?.result && (
|
||||
<div
|
||||
className={`flex items-center space-x-1
|
||||
${
|
||||
recording.pronunciationAssessment
|
||||
? recording.pronunciationAssessment
|
||||
.pronunciationScore >= 80
|
||||
? "text-green-500"
|
||||
: recording.pronunciationAssessment
|
||||
.pronunciationScore >= 60
|
||||
? "text-yellow-600"
|
||||
: "text-red-500"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
>
|
||||
<GaugeCircleIcon className="w-4 h-4" />
|
||||
<span className="text-xs font-mono">
|
||||
{recording.pronunciationAssessment.pronunciationScore}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatDateTime(recording.createdAt)}
|
||||
</span>
|
||||
|
||||
@@ -24,13 +24,15 @@ export const MediaTabs = () => {
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div
|
||||
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-[1] grid gap-4 ${media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
|
||||
}`}
|
||||
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-[1] grid gap-4 ${
|
||||
media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
|
||||
}`}
|
||||
>
|
||||
{media.mediaType === "Video" && (
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "provider" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${
|
||||
tab === "provider" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("provider")}
|
||||
>
|
||||
{t("player")}
|
||||
@@ -38,22 +40,25 @@ export const MediaTabs = () => {
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "transcription" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${
|
||||
tab === "transcription" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("transcription")}
|
||||
>
|
||||
{t("transcription")}
|
||||
</div>
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "recordings" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${
|
||||
tab === "recordings" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("recordings")}
|
||||
>
|
||||
{t("myRecordings")}
|
||||
</div>
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "info" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${
|
||||
tab === "info" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("info")}
|
||||
>
|
||||
{t("mediaInfo")}
|
||||
@@ -67,7 +72,7 @@ export const MediaTabs = () => {
|
||||
<MediaRecordings />
|
||||
</div>
|
||||
<div className={tab === "transcription" ? "" : "hidden"}>
|
||||
<MediaTranscription />
|
||||
<MediaTranscription display={tab === "transcription"} />
|
||||
</div>
|
||||
<div className={tab === "info" ? "" : "hidden"}>
|
||||
<MediaInfoPanel />
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -121,6 +122,7 @@ export const MediaTranscriptionReadButton = (props: {
|
||||
onPointerDownOutside={(event) => event.preventDefault()}
|
||||
className="max-w-screen-md xl:max-w-screen-lg h-5/6 flex flex-col p-0"
|
||||
>
|
||||
<DialogTitle className="hidden">{t("readThrough")}</DialogTitle>
|
||||
<ScrollArea className="flex-1 px-6 pt-4">
|
||||
<div className="select-text mx-auto w-full max-w-prose">
|
||||
<h3 className="font-bold text-xl my-4">{media.name}</h3>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
MicIcon,
|
||||
PencilLineIcon,
|
||||
SquareMenuIcon,
|
||||
GaugeCircleIcon,
|
||||
} from "lucide-react";
|
||||
import { AlignmentResult } from "echogarden/dist/api/API.d.js";
|
||||
import { formatDuration } from "@renderer/lib/utils";
|
||||
@@ -28,7 +29,8 @@ import {
|
||||
MediaTranscriptionGenerateButton,
|
||||
} from "@renderer/components";
|
||||
|
||||
export const MediaTranscription = () => {
|
||||
export const MediaTranscription = (props: { display?: boolean }) => {
|
||||
const { display } = props;
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const {
|
||||
decoded,
|
||||
@@ -69,6 +71,21 @@ export const MediaTranscription = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToCurrentSegment = () => {
|
||||
if (!containerRef?.current) return;
|
||||
if (!decoded) return;
|
||||
if (!display) return;
|
||||
|
||||
setTimeout(() => {
|
||||
containerRef.current
|
||||
?.querySelector(`#segment-${currentSegmentIndex}`)
|
||||
?.scrollIntoView({
|
||||
block: "center",
|
||||
inline: "center",
|
||||
} as ScrollIntoViewOptions);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!transcription?.result) return;
|
||||
|
||||
@@ -81,18 +98,8 @@ export const MediaTranscription = () => {
|
||||
}, [transcription?.result]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef?.current) return;
|
||||
if (!decoded) return;
|
||||
|
||||
setTimeout(() => {
|
||||
containerRef.current
|
||||
?.querySelector(`#segment-${currentSegmentIndex}`)
|
||||
?.scrollIntoView({
|
||||
block: "center",
|
||||
inline: "center",
|
||||
} as ScrollIntoViewOptions);
|
||||
}, 300);
|
||||
}, [decoded, currentSegmentIndex, transcription, containerRef]);
|
||||
scrollToCurrentSegment();
|
||||
}, [display, decoded, currentSegmentIndex, transcription, containerRef]);
|
||||
|
||||
if (!transcription?.result?.timeline) {
|
||||
return null;
|
||||
@@ -182,9 +189,10 @@ export const MediaTranscription = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs opacity-50">#{index + 1}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
{(recordingStats || []).findIndex(
|
||||
(s) => s.referenceId === index
|
||||
) !== -1 && <MicIcon className="w-3 h-3 text-sky-500" />}
|
||||
<RecordingStatsRemark
|
||||
stats={recordingStats}
|
||||
referenceId={index}
|
||||
/>
|
||||
{(notesStats || []).findIndex(
|
||||
(s) => s.segment?.segmentIndex === index
|
||||
) !== -1 && <PencilLineIcon className="w-3 h-3 text-sky-500" />}
|
||||
@@ -200,3 +208,34 @@ export const MediaTranscription = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RecordingStatsRemark = (props: {
|
||||
stats: SegementRecordingStatsType;
|
||||
referenceId: number;
|
||||
}) => {
|
||||
const { stats = [], referenceId } = props;
|
||||
const stat = stats.find((s) => s.referenceId === referenceId);
|
||||
if (!stat) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{stat.pronunciationAssessment?.pronunciationScore && (
|
||||
<GaugeCircleIcon
|
||||
className={`w-3 h-3
|
||||
${
|
||||
stat.pronunciationAssessment
|
||||
? stat.pronunciationAssessment.pronunciationScore >= 80
|
||||
? "text-green-500"
|
||||
: stat.pronunciationAssessment.pronunciationScore >=
|
||||
60
|
||||
? "text-yellow-600"
|
||||
: "text-red-500"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<MicIcon className="w-3 h-3 text-sky-500" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuItem,
|
||||
Separator,
|
||||
DialogTitle,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
SettingsIcon,
|
||||
@@ -172,6 +173,9 @@ export const Sidebar = () => {
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-screen-md xl:max-w-screen-lg h-5/6 p-0">
|
||||
<DialogTitle className="hidden">
|
||||
{t("sidebar.preferences")}
|
||||
</DialogTitle>
|
||||
<Preferences />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
PronunciationAssessmentScoreResult,
|
||||
} from "@renderer/components";
|
||||
import { Separator, ScrollArea } from "@renderer/components/ui";
|
||||
import { useState, useContext } from "react";
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
@@ -29,12 +29,19 @@ export const RecordingDetail = (props: {
|
||||
const [assessing, setAssessing] = useState(false);
|
||||
|
||||
const assess = () => {
|
||||
if (assessing) return;
|
||||
if (result) return;
|
||||
|
||||
setAssessing(true);
|
||||
EnjoyApp.recordings.assess(recording.id, learningLanguage).finally(() => {
|
||||
setAssessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
assess();
|
||||
}, [recording]);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="mb-6 px-4">
|
||||
|
||||
@@ -18,7 +18,7 @@ const AlertDialogOverlay = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -36,7 +36,7 @@ const AlertDialogContent = React.forwardRef<
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -38,13 +38,13 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
|
||||
3
enjoy/src/types/recording.d.ts
vendored
3
enjoy/src/types/recording.d.ts
vendored
@@ -23,4 +23,7 @@ type SegementRecordingStatsType = {
|
||||
referenceText?: string;
|
||||
count: number;
|
||||
duration: number;
|
||||
pronunciationAssessment?: {
|
||||
pronunciationScore: number;
|
||||
};
|
||||
}[];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"start:enjoy": "echo 'Please use `yarn enjoy:start` instead'",
|
||||
"enjoy:add": "yarn workspace enjoy add",
|
||||
"enjoy:remove": "yarn workspace enjoy remove",
|
||||
"enjoy:dev": "yarn workspace enjoy dev",
|
||||
"enjoy:start": "yarn workspace enjoy start",
|
||||
"enjoy:test": "yarn workspace enjoy test",
|
||||
|
||||
Reference in New Issue
Block a user