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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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.)
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 14 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions src/components/rule-card.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,22 +15,24 @@ interface RuleCardProps {
) => void;
onDelete: (id: string) => void;
onDuplicate: (id: string) => void;
dragHandleProps?: Record<string, unknown>;
}

export const RuleCard: React.FC<RuleCardProps> = ({
export const RuleCard = ({
rule,
onUpdate,
onDelete,
onDuplicate,
}) => {
dragHandleProps,
}: RuleCardProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const { t } = useTranslation();

const regexError = useRegexValidation(rule.trigger, rule.match_mode);

return (
<div
className={`border rounded-lg p-4 transition-all ${
className={`border rounded-lg p-4 ${
rule.enabled
? 'border-zinc-700 bg-zinc-800/25'
: 'border-zinc-800 bg-zinc-900/50 opacity-60'
Expand All @@ -39,6 +41,14 @@ export const RuleCard: React.FC<RuleCardProps> = ({
>
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3 flex-1 min-w-0">
<button
type="button"
className="cursor-grab text-zinc-600 hover:text-zinc-400 transition-colors active:cursor-grabbing p-2 -m-2"
title={t('Reorder')}
{...dragHandleProps}
>
<GripVertical className="w-4 h-4" />
</button>
<Switch
checked={rule.enabled}
onCheckedChange={(checked) =>
Expand Down
130 changes: 119 additions & 11 deletions src/features/settings/formatting-rules/formatting-rules.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { Page } from '@/components/page';
import { Typography } from '@/components/typography';
import { Switch } from '@/components/switch';
Expand All @@ -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<Omit<FormattingRule, 'id'>>) => 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 (
<div ref={setNodeRef} style={style}>
{isDragging ? (
<div className="border border-dashed border-zinc-700 rounded-lg h-[56px] bg-zinc-800/10" />
) : (
<RuleCard
rule={rule}
{...props}
dragHandleProps={{ ...attributes, ...listeners }}
/>
)}
</div>
);
};

export const FormattingRules = () => {
const { t } = useTranslation();
Expand All @@ -25,8 +83,35 @@ export const FormattingRules = () => {
updateRule,
deleteRule,
duplicateRule,
reorderRules,
} = useFormattingRules();

const [activeId, setActiveId] = useState<string | null>(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 (
<main className="space-y-8">
Expand Down Expand Up @@ -270,17 +355,40 @@ export const FormattingRules = () => {

<div className="space-y-4">
{settings.rules.length > 0 && (
<div className="space-y-3">
{settings.rules.map((rule) => (
<RuleCard
key={rule.id}
rule={rule}
onUpdate={updateRule}
onDelete={deleteRule}
onDuplicate={duplicateRule}
/>
))}
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis]}
>
<SortableContext
items={settings.rules.map((rule) => rule.id)}
strategy={verticalListSortingStrategy}
>
<div className="space-y-3">
{settings.rules.map((rule) => (
<SortableRuleCard
key={rule.id}
rule={rule}
onUpdate={updateRule}
onDelete={deleteRule}
onDuplicate={duplicateRule}
/>
))}
</div>
</SortableContext>
<DragOverlay>
{activeRule != null && (
<RuleCard
rule={activeRule}
onUpdate={updateRule}
onDelete={deleteRule}
onDuplicate={duplicateRule}
/>
)}
</DragOverlay>
</DndContext>
)}
<AddRuleSection onAdd={addRule} />
<div className="h-8" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -142,5 +161,6 @@ export const useFormattingRules = () => {
updateRule,
deleteRule,
duplicateRule,
reorderRules,
};
};
3 changes: 2 additions & 1 deletion src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down