diff --git a/README.md b/README.md index 7fe1745..e207c17 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Murmure + # Murmure A privacy-first, open-source speech-to-text application that runs entirely on your machine, powered by a neural network via NVIDIA’s [Parakeet TDT 0.6B v3 model](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3) for fast, local transcription. Murmure turns your voice into text with no internet connection and zero data collection, and supports 25 European languages. @@ -132,10 +132,10 @@ See [CHANGELOG.md](./CHANGELOG.md). - [x] (1.8.0) feat(rules): Improve rules label to make sentences https://github.com/Kieirra/murmure/pull/163 https://github.com/Kieirra/murmure/issues/101 - [x] (1.8.0) feat(rules): Add a “?” helper in the “Replacement text” field (explain natural language input and real line breaks instead of `\n`) https://github.com/Kieirra/murmure/pull/163 - [x] (1.8.0) feat(rules): Short text correction — auto-lowercase and remove trailing punctuation for short transcriptions (1-2 words) +- [x] (1.8.0) feat(rules): Allow rule reordering https://github.com/Kieirra/murmure/pull/170 - [x] (1.8.0) fix(overlay): remove scrollbar on some configurations on windows https://github.com/Kieirra/murmure/pull/169 - [ ] (1.8.0) feat(overlay): Configure overlay size - [ ] (1.8.0) feat(rules): Add auto-send enter (https://github.com/Kieirra/murmure/pull/156) -- [ ] (1.8.0) feat(rules): Allow rule reordering https://github.com/Kieirra/murmure/issues/104 - [ ] (1.8.0) feat(llm): Allow llm mode reordering https://github.com/Kieirra/murmure/issues/104 - [ ] (1.8.0) feat(llm): Allow bypassing onboarding for people which have a remote ollama server - [ ] (1.8.0) feat(about): Improve UI (years, parakeet model name twice, etc.) diff --git a/package.json b/package.json index 50db91c..9253797 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,10 @@ "test": "cd e2e-tests && npm run test" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@lobehub/icons": "^4.1.0", "@lobehub/ui": "^4.32.2", "@radix-ui/react-checkbox": "^1.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba9d1ef..a478849 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@dnd-kit/modifiers': + specifier: ^9.0.0 + version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.2.4) '@lobehub/icons': specifier: ^4.1.0 version: 4.1.0(@lobehub/ui@4.32.2)(@types/react@19.2.10)(antd@6.2.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -4039,6 +4051,7 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.1: @@ -4048,7 +4061,7 @@ packages: glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} diff --git a/src/components/rule-card.tsx b/src/components/rule-card.tsx index 27027ff..c3d7358 100644 --- a/src/components/rule-card.tsx +++ b/src/components/rule-card.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FormattingRule } from '../features/settings/formatting-rules/types'; import { Switch } from '@/components/switch'; -import { Trash2, Copy, ChevronDown, ChevronUp, Regex } from 'lucide-react'; +import { Trash2, Copy, ChevronDown, ChevronUp, Regex, GripVertical } from 'lucide-react'; import { useTranslation } from '@/i18n'; import { Button } from './button'; import { RuleFormFields } from './rule-form-fields'; @@ -15,14 +15,16 @@ interface RuleCardProps { ) => void; onDelete: (id: string) => void; onDuplicate: (id: string) => void; + dragHandleProps?: Record; } -export const RuleCard: React.FC = ({ +export const RuleCard = ({ rule, onUpdate, onDelete, onDuplicate, -}) => { + dragHandleProps, +}: RuleCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const { t } = useTranslation(); @@ -30,7 +32,7 @@ export const RuleCard: React.FC = ({ return (
= ({ >
+ diff --git a/src/features/settings/formatting-rules/formatting-rules.tsx b/src/features/settings/formatting-rules/formatting-rules.tsx index 0c96fd2..3618498 100644 --- a/src/features/settings/formatting-rules/formatting-rules.tsx +++ b/src/features/settings/formatting-rules/formatting-rules.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { Page } from '@/components/page'; import { Typography } from '@/components/typography'; import { Switch } from '@/components/switch'; @@ -14,6 +15,63 @@ import { } from '../../../components/select'; import { NumberInput } from '@/components/number-input'; import { SettingsUI } from '@/components/settings-ui'; +import { + DndContext, + closestCenter, + DragEndEvent, + DragStartEvent, + DragOverlay, + PointerSensor, + KeyboardSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + SortableContext, + verticalListSortingStrategy, + useSortable, + sortableKeyboardCoordinates, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { FormattingRule } from './types'; + +interface SortableRuleCardProps { + rule: FormattingRule; + onUpdate: (id: string, updates: Partial>) => void; + onDelete: (id: string) => void; + onDuplicate: (id: string) => void; +} + +const SortableRuleCard = ({ rule, ...props }: SortableRuleCardProps) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: rule.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + return ( +
+ {isDragging ? ( +
+ ) : ( + + )} +
+ ); +}; export const FormattingRules = () => { const { t } = useTranslation(); @@ -25,8 +83,35 @@ export const FormattingRules = () => { updateRule, deleteRule, duplicateRule, + reorderRules, } = useFormattingRules(); + const [activeId, setActiveId] = useState(null); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { distance: 8 }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(String(event.active.id)); + }; + + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(null); + const { active, over } = event; + if (over != null && active.id !== over.id) { + reorderRules(String(active.id), String(over.id)); + } + }; + + const activeRule = activeId != null + && settings.rules.find((rule) => rule.id === activeId) || undefined; + if (isLoading) { return (
@@ -270,17 +355,40 @@ export const FormattingRules = () => {
{settings.rules.length > 0 && ( -
- {settings.rules.map((rule) => ( - - ))} -
+ + rule.id)} + strategy={verticalListSortingStrategy} + > +
+ {settings.rules.map((rule) => ( + + ))} +
+
+ + {activeRule != null && ( + + )} + +
)}
diff --git a/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts b/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts index 44f2837..2176295 100644 --- a/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts +++ b/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts @@ -134,6 +134,25 @@ export const useFormattingRules = () => { [settings, saveSettings] ); + const reorderRules = useCallback( + async (activeId: string, overId: string) => { + const oldIndex = settings.rules.findIndex((rule) => rule.id === activeId); + const newIndex = settings.rules.findIndex((rule) => rule.id === overId); + if (oldIndex === -1 || newIndex === -1) return; + + const newRules = [...settings.rules]; + const [moved] = newRules.splice(oldIndex, 1); + newRules.splice(newIndex, 0, moved); + + const newSettings = { + ...settings, + rules: newRules, + }; + await saveSettings(newSettings); + }, + [settings, saveSettings] + ); + return { settings, isLoading, @@ -142,5 +161,6 @@ export const useFormattingRules = () => { updateRule, deleteRule, duplicateRule, + reorderRules, }; }; diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 9376d99..9135c58 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -226,8 +226,9 @@ "Refresh Models": "Rafraîchir les modèles", "Rename": "Renommer", "Rename Mode": "Renommer le mode", - "Smart": "Intelligent", + "Reorder": "Réordonner", "Report a bug": "Signaler un bug", + "Smart": "Intelligent", "Requires installing Ollama (free & open source)": "Requiert l'installation d'Ollama (gratuit et open source)", "Reset Tutorial": "Réinitialiser le tutoriel", "Save": "Enregistrer",