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
6 changes: 2 additions & 4 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
github: cjpais
custom: ["https://handy.computer/donate", "https://www.paypal.me/cjpais"]
buy_me_a_coffee: cjpais
ko_fi: cjpais
github: 0xNyk
custom: ["https://0xnyk.gumroad.com/l/dictx"]
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ Look for issues labeled `good first issue` or `help wanted` if you're new to the
## 📞 Getting Help

- **Discussions**: Ask questions in [GitHub Discussions](https://github.com/0xNyk/dictx/discussions)

## 📜 License

By contributing to Dictx, you agree that your contributions will be licensed under the MIT License. See [LICENSE](LICENSE) for details.
Expand Down
22 changes: 18 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import AccessibilityPermissions from "./components/AccessibilityPermissions";
import Footer from "./components/footer";
import Onboarding, { AccessibilityOnboarding } from "./components/onboarding";
import { Sidebar, SidebarSection, SECTIONS_CONFIG } from "./components/Sidebar";
import { ShortcutsDialog } from "./components/ui/ShortcutsDialog";
import { useSettings } from "./hooks/useSettings";
import { useSettingsStore } from "./stores/settingsStore";
import { commands } from "@/bindings";
Expand All @@ -38,6 +39,7 @@ function App() {
const [isReturningUser, setIsReturningUser] = useState(false);
const [currentSection, setCurrentSection] =
useState<SidebarSection>("general");
const [showShortcuts, setShowShortcuts] = useState(false);
const { settings, updateSetting } = useSettings();
const direction = getLanguageDirection(i18n.language);
const refreshAudioDevices = useSettingsStore(
Expand Down Expand Up @@ -72,7 +74,7 @@ function App() {
}
}, [onboardingStep, refreshAudioDevices, refreshOutputDevices]);

// Handle keyboard shortcuts for debug mode toggle
// Handle keyboard shortcuts for debug mode toggle and shortcuts dialog
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Check for Ctrl+Shift+D (Windows/Linux) or Cmd+Shift+D (macOS)
Expand All @@ -86,12 +88,20 @@ function App() {
const currentDebugMode = settings?.debug_mode ?? false;
updateSetting("debug_mode", !currentDebugMode);
}

// Show shortcuts help with "?" when no input is focused
if (
event.key === "?" &&
!event.ctrlKey &&
!event.metaKey &&
!(event.target instanceof HTMLInputElement) &&
!(event.target instanceof HTMLTextAreaElement)
) {
setShowShortcuts((prev) => !prev);
}
};

// Add event listener when component mounts
document.addEventListener("keydown", handleKeyDown);

// Cleanup event listener when component unmounts
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
Expand Down Expand Up @@ -198,6 +208,10 @@ function App() {
</div>
{/* Fixed footer at bottom */}
<Footer />
<ShortcutsDialog
open={showShortcuts}
onClose={() => setShowShortcuts(false)}
/>
</div>
);
}
Expand Down
21 changes: 19 additions & 2 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Cog, FlaskConical, History, Info, Sparkles, Cpu } from "lucide-react";
import { openUrl } from "@tauri-apps/plugin-opener";
import DictxTextLogo from "./icons/DictxTextLogo";
import DictxIcon from "./icons/DictxIcon";
import { useSettings } from "../hooks/useSettings";
Expand Down Expand Up @@ -95,20 +96,28 @@ export const Sidebar: React.FC<SidebarProps> = ({
return (
<div className="flex flex-col w-40 h-full border-e border-mid-gray/20 items-center px-2">
<DictxTextLogo width={120} className="m-4" />
<div className="flex flex-col w-full items-center gap-1 pt-2 border-t border-mid-gray/20">
<div className="flex flex-col w-full items-center gap-1 pt-2 border-t border-mid-gray/20 flex-1">
{availableSections.map((section) => {
const Icon = section.icon;
const isActive = activeSection === section.id;

return (
<div
key={section.id}
className={`flex gap-2 items-center p-2 w-full rounded-lg cursor-pointer transition-colors ${
role="button"
tabIndex={0}
className={`flex gap-2 items-center p-2 w-full rounded-lg cursor-pointer transition-colors focus:ring-2 focus:ring-logo-primary focus:outline-none ${
isActive
? "bg-logo-primary/80"
: "hover:bg-mid-gray/20 hover:opacity-100 opacity-85"
}`}
onClick={() => onSectionChange(section.id)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onSectionChange(section.id);
}
}}
>
<Icon width={24} height={24} className="shrink-0" />
<p
Expand All @@ -120,6 +129,14 @@ export const Sidebar: React.FC<SidebarProps> = ({
</div>
);
})}
<div className="mt-auto pb-3 w-full px-1">
<button
onClick={() => openUrl("https://0xnyk.gumroad.com/l/dictx")}
className="text-xs text-text/40 hover:text-logo-primary transition-colors cursor-pointer w-full text-center"
>
{t("sidebar.getPro")}
</button>
</div>
</div>
</div>
);
Expand Down
89 changes: 80 additions & 9 deletions src/components/icons/DictxIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,86 @@ const DictxIcon = ({
xmlns="http://www.w3.org/2000/svg"
>
{/* 9-bar waveform matching the Dictx logo */}
<rect x="1" y="9.5" width="1.5" height="5" rx="0.75" fill="currentColor" opacity="0.6" />
<rect x="3.5" y="7.5" width="1.5" height="9" rx="0.75" fill="currentColor" opacity="0.7" />
<rect x="6" y="5.5" width="1.5" height="13" rx="0.75" fill="currentColor" opacity="0.8" />
<rect x="8.5" y="4" width="1.5" height="16" rx="0.75" fill="currentColor" opacity="0.9" />
<rect x="11" y="2.5" width="1.5" height="19" rx="0.75" fill="currentColor" />
<rect x="13.5" y="4" width="1.5" height="16" rx="0.75" fill="currentColor" opacity="0.9" />
<rect x="16" y="5.5" width="1.5" height="13" rx="0.75" fill="currentColor" opacity="0.8" />
<rect x="18.5" y="7.5" width="1.5" height="9" rx="0.75" fill="currentColor" opacity="0.7" />
<rect x="21" y="9.5" width="1.5" height="5" rx="0.75" fill="currentColor" opacity="0.6" />
<rect
x="1"
y="9.5"
width="1.5"
height="5"
rx="0.75"
fill="currentColor"
opacity="0.6"
/>
<rect
x="3.5"
y="7.5"
width="1.5"
height="9"
rx="0.75"
fill="currentColor"
opacity="0.7"
/>
<rect
x="6"
y="5.5"
width="1.5"
height="13"
rx="0.75"
fill="currentColor"
opacity="0.8"
/>
<rect
x="8.5"
y="4"
width="1.5"
height="16"
rx="0.75"
fill="currentColor"
opacity="0.9"
/>
<rect
x="11"
y="2.5"
width="1.5"
height="19"
rx="0.75"
fill="currentColor"
/>
<rect
x="13.5"
y="4"
width="1.5"
height="16"
rx="0.75"
fill="currentColor"
opacity="0.9"
/>
<rect
x="16"
y="5.5"
width="1.5"
height="13"
rx="0.75"
fill="currentColor"
opacity="0.8"
/>
<rect
x="18.5"
y="7.5"
width="1.5"
height="9"
rx="0.75"
fill="currentColor"
opacity="0.7"
/>
<rect
x="21"
y="9.5"
width="1.5"
height="5"
rx="0.75"
fill="currentColor"
opacity="0.6"
/>
</svg>
);

Expand Down
45 changes: 44 additions & 1 deletion src/components/onboarding/ModelCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,24 @@ interface ModelCardProps {
onCancel?: (modelId: string) => void;
downloadProgress?: number;
downloadSpeed?: number; // MB/s
downloadedBytes?: number;
totalBytes?: number;
showRecommended?: boolean;
}

const formatEta = (seconds: number): string => {
if (seconds < 60)
return `0:${Math.round(seconds).toString().padStart(2, "0")}`;
const m = Math.floor(seconds / 60);
const s = Math.round(seconds % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
};

const formatBytes = (bytes: number): string => {
const mb = bytes / (1024 * 1024);
return mb >= 1000 ? `${(mb / 1024).toFixed(1)} GB` : `${Math.round(mb)} MB`;
};

const ModelCard: React.FC<ModelCardProps> = ({
model,
variant = "default",
Expand All @@ -67,16 +82,20 @@ const ModelCard: React.FC<ModelCardProps> = ({
onCancel,
downloadProgress,
downloadSpeed,
downloadedBytes,
totalBytes,
showRecommended = true,
}) => {
const { t } = useTranslation();
const isFeatured = variant === "featured";
const isClickable =
status === "available" || status === "active" || status === "downloadable";

// Get translated model name and description
// Get translated model name, description, and use-case hint
const displayName = getTranslatedModelName(model, t);
const displayDescription = getTranslatedModelDescription(model, t);
const useCaseKey = `onboarding.models.${model.id}.useCase`;
const useCase = t(useCaseKey, { defaultValue: "" });

const baseClasses =
"flex flex-col rounded-xl px-4 py-3 gap-2 text-left transition-all duration-200";
Expand Down Expand Up @@ -159,6 +178,9 @@ const ModelCard: React.FC<ModelCardProps> = ({
<p className="text-text/60 text-sm leading-relaxed">
{displayDescription}
</p>
{useCase && (
<p className="text-text/40 text-xs mt-0.5 italic">{useCase}</p>
)}
</div>
{(model.accuracy_score > 0 || model.speed_score > 0) && (
<div className="hidden sm:flex items-center ml-4">
Expand Down Expand Up @@ -245,6 +267,27 @@ const ModelCard: React.FC<ModelCardProps> = ({
style={{ width: `${downloadProgress}%` }}
/>
</div>
{/* Size progress line */}
{downloadedBytes !== undefined &&
totalBytes !== undefined &&
totalBytes > 0 && (
<div className="text-xs text-text/40 mt-1">
{t("onboarding.downloadSize", {
downloaded: formatBytes(downloadedBytes),
total: formatBytes(totalBytes),
})}
{downloadSpeed !== undefined &&
downloadSpeed > 0 &&
(() => {
const remaining = totalBytes - downloadedBytes;
const etaSeconds =
remaining / (downloadSpeed * 1024 * 1024);
return etaSeconds > 0 && etaSeconds < 36000
? ` — ${t("onboarding.downloadEta", { time: formatEta(etaSeconds) })}`
: "";
})()}
</div>
)}
<div className="flex items-center justify-between text-xs mt-1">
<span className="text-text/50">
{t("modelSelector.downloading", {
Expand Down
12 changes: 12 additions & 0 deletions src/components/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
return downloadStats[modelId]?.speed;
};

const getModelDownloadedBytes = (modelId: string): number | undefined => {
return downloadProgress[modelId]?.downloaded;
};

const getModelTotalBytes = (modelId: string): number | undefined => {
return downloadProgress[modelId]?.total;
};

return (
<div className="h-screen w-screen flex flex-col p-6 gap-4 inset-0">
<div className="flex flex-col items-center gap-2 shrink-0">
Expand All @@ -104,6 +112,8 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
onDownload={handleDownloadModel}
downloadProgress={getModelDownloadProgress(model.id)}
downloadSpeed={getModelDownloadSpeed(model.id)}
downloadedBytes={getModelDownloadedBytes(model.id)}
totalBytes={getModelTotalBytes(model.id)}
/>
))}

Expand All @@ -124,6 +134,8 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
onDownload={handleDownloadModel}
downloadProgress={getModelDownloadProgress(model.id)}
downloadSpeed={getModelDownloadSpeed(model.id)}
downloadedBytes={getModelDownloadedBytes(model.id)}
totalBytes={getModelTotalBytes(model.id)}
/>
))}
</div>
Expand Down
Loading
Loading