add voxtype
This commit is contained in:
22
flake.lock
generated
22
flake.lock
generated
@@ -675,6 +675,7 @@
|
|||||||
"tonearm": "tonearm",
|
"tonearm": "tonearm",
|
||||||
"turny": "turny",
|
"turny": "turny",
|
||||||
"voidauth": "voidauth",
|
"voidauth": "voidauth",
|
||||||
|
"voxtype": "voxtype",
|
||||||
"workout-sync": "workout-sync"
|
"workout-sync": "workout-sync"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -872,6 +873,27 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"voxtype": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_7",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1772443545,
|
||||||
|
"narHash": "sha256-oD3lameQXilKcgxQORR2l0+iDbnCO61+mjYD3MEVbuQ=",
|
||||||
|
"owner": "peteonrails",
|
||||||
|
"repo": "voxtype",
|
||||||
|
"rev": "d011f3ff074a6a14c14e75fefb375a408e9e8887",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "peteonrails",
|
||||||
|
"repo": "voxtype",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"workout-sync": {
|
"workout-sync": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|||||||
@@ -56,6 +56,10 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||||
inputs.flake-parts.follows = "flake-parts";
|
inputs.flake-parts.follows = "flake-parts";
|
||||||
};
|
};
|
||||||
|
voxtype = {
|
||||||
|
url = "github:peteonrails/voxtype";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
../gnome
|
../gnome
|
||||||
./dnote.nix
|
./dnote.nix
|
||||||
../modules/zed
|
../modules/zed
|
||||||
|
../modules/voxtype
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
inputs.hastebin.nixosModules.hm
|
inputs.hastebin.nixosModules.hm
|
||||||
inputs.agenix.homeManagerModules.default
|
inputs.agenix.homeManagerModules.default
|
||||||
|
inputs.voxtype.homeManagerModules.default
|
||||||
];
|
];
|
||||||
|
|
||||||
# This value determines the Home Manager release that your configuration is
|
# This value determines the Home Manager release that your configuration is
|
||||||
@@ -94,6 +96,18 @@
|
|||||||
'')
|
'')
|
||||||
pkgs-unstable.tidal-hifi
|
pkgs-unstable.tidal-hifi
|
||||||
inputs.tonearm.packages.${pkgs.stdenv.hostPlatform.system}.tonearm
|
inputs.tonearm.packages.${pkgs.stdenv.hostPlatform.system}.tonearm
|
||||||
|
(writeShellScriptBin "voxtoggle" ''
|
||||||
|
status=$(${lib.getExe config.programs.voxtype.package} status)
|
||||||
|
pid=$(cat ''${XDG_RUNTIME_DIR}/voxtype/pid)
|
||||||
|
|
||||||
|
if [[ "$status" == "stopped" ]]; then
|
||||||
|
exit 1
|
||||||
|
elif [[ "$status" == "recording" ]]; then
|
||||||
|
kill -SIGUSR2 "$pid"
|
||||||
|
else
|
||||||
|
kill -SIGUSR1 "$pid"
|
||||||
|
fi
|
||||||
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
programs = {
|
programs = {
|
||||||
@@ -427,6 +441,51 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
defaultEditor = true;
|
defaultEditor = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
voxtype = {
|
||||||
|
enable = true;
|
||||||
|
package = inputs.voxtype.packages.${pkgs.stdenv.hostPlatform.system}.vulkan;
|
||||||
|
model.name = "large-v3-turbo";
|
||||||
|
service.enable = true;
|
||||||
|
settings = {
|
||||||
|
hotkey.enabled = false;
|
||||||
|
whisper.language = "auto";
|
||||||
|
output.notification = {
|
||||||
|
on_recording_start = false;
|
||||||
|
on_recording_stop = false;
|
||||||
|
on_transcription = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
postProcessing = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
model = "qwen3:4b-instruct";
|
||||||
|
commonInstructions = "no quotes, no emojis, no explanations";
|
||||||
|
prompts = [
|
||||||
|
{
|
||||||
|
title = "Clean up";
|
||||||
|
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "Make a title";
|
||||||
|
instructions = "Make a concise and descriptive title for this dictation. Output ONLY the title";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "Summarize";
|
||||||
|
instructions = "Summarize this dictation in a few sentences. Output ONLY the summary";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "Commit message";
|
||||||
|
instructions = "Write a concise and descriptive git commit message for this dictation. Output ONLY the commit message";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "Translate to English";
|
||||||
|
instructions = "Translate this dictation to English. Remove filler words, fix grammar and punctuation. Output ONLY the translation";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
gnome = {
|
gnome = {
|
||||||
|
|||||||
92
home/modules/voxtype/default.nix
Normal file
92
home/modules/voxtype/default.nix
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
osConfig,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
pkgs-unstable,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.programs.voxtype;
|
||||||
|
postProcessUnwrapped =
|
||||||
|
pkgs.runCommand "voxtype-post-process-unwrapped"
|
||||||
|
{
|
||||||
|
code = ./post-process.cr;
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs-unstable.crystal
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
crystal build $code -o $out/bin/voxtype-post-process
|
||||||
|
'';
|
||||||
|
|
||||||
|
postProcess = pkgs.symlinkJoin {
|
||||||
|
name = "voxtype-post-process";
|
||||||
|
paths = [ postProcessUnwrapped ];
|
||||||
|
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
|
||||||
|
postBuild = ''
|
||||||
|
wrapProgram $out/bin/voxtype-post-process \
|
||||||
|
--set OLLAMA_PORT ${toString osConfig.services.ollama.port} \
|
||||||
|
--set WALKER_BIN ${lib.getExe config.services.walker.package}
|
||||||
|
'';
|
||||||
|
meta.mainProgram = "voxtype-post-process";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.programs.voxtype = {
|
||||||
|
postProcessing = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "Enable post-processing of transcriptions";
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
model = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "The ollama model to use for post-processing";
|
||||||
|
};
|
||||||
|
commonInstructions = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "no quotes, no emojis, no explanations";
|
||||||
|
description = "Instructions to include in every post-processing prompt";
|
||||||
|
};
|
||||||
|
prompts = lib.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
title = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "A title for this prompt, used in the selector";
|
||||||
|
};
|
||||||
|
instructions = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Instructions to include in the post-processing prompt, in addition to the common instructions";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [
|
||||||
|
{
|
||||||
|
title = "Clean up";
|
||||||
|
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.postProcessing.enable {
|
||||||
|
xdg.configFile."voxtype/post-processing.json".text = builtins.toJSON cfg.postProcessing.settings;
|
||||||
|
programs.voxtype.settings.output.post_process = {
|
||||||
|
command = lib.getExe postProcess;
|
||||||
|
timeout_ms = 5 * 60 * 1000; # 5 minutes
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
54
home/modules/voxtype/post-process.cr
Normal file
54
home/modules/voxtype/post-process.cr
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
require "json"
|
||||||
|
require "http/client"
|
||||||
|
|
||||||
|
struct OllamaResponse
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
getter response : String
|
||||||
|
end
|
||||||
|
|
||||||
|
struct Prompt
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
getter title : String
|
||||||
|
getter instructions : String
|
||||||
|
end
|
||||||
|
|
||||||
|
struct Config
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
getter model : String
|
||||||
|
getter prompts : Array(Prompt)
|
||||||
|
@[JSON::Field(key: "commonInstructions")]
|
||||||
|
getter common_instructions : String
|
||||||
|
end
|
||||||
|
|
||||||
|
config_path = "#{ENV.fetch("XDG_CONFIG_HOME", "~/.config")}/voxtype/post-processing.json"
|
||||||
|
config = File.open(config_path) { |file| Config.from_json(file) }
|
||||||
|
client = HTTP::Client.new("localhost", ENV.fetch("OLLAMA_PORT", "11434").to_i)
|
||||||
|
|
||||||
|
prompt_selection = Process.run(ENV["WALKER_BIN"], ["--dmenu"]) do |process|
|
||||||
|
config.prompts.each do |prompt|
|
||||||
|
process.input.puts prompt.title
|
||||||
|
end
|
||||||
|
|
||||||
|
process.input.close
|
||||||
|
process.output.gets_to_end.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
instructions = config.prompts.find { |prompt| prompt.title == prompt_selection }.try(&.instructions) || prompt_selection
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
model: config.model,
|
||||||
|
prompt: "#{instructions} - #{config.common_instructions}:\n\n#{STDIN.gets_to_end.chomp}",
|
||||||
|
think: false,
|
||||||
|
stream: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/generate", body: payload.to_json) do |response|
|
||||||
|
if response.status_code == 200
|
||||||
|
puts OllamaResponse.from_json(response.body_io).response.strip
|
||||||
|
else
|
||||||
|
abort "Ollama API error: #{response.status_code} #{response.body}"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -51,10 +51,12 @@ in
|
|||||||
|
|
||||||
ollama = {
|
ollama = {
|
||||||
enable = true;
|
enable = true;
|
||||||
acceleration = "rocm";
|
package = pkgs-unstable.ollama-vulkan;
|
||||||
environmentVariables = {
|
syncModels = true;
|
||||||
HSA_OVERRIDE_GFX_VERSION = "11.0.3";
|
loadModels = [
|
||||||
};
|
"qwen3:4b-instruct"
|
||||||
|
"qwen3:8b"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
borgbackup.jobs.root = {
|
borgbackup.jobs.root = {
|
||||||
@@ -139,4 +141,31 @@ in
|
|||||||
environment.etc."1password/custom_allowed_browsers".text = ''
|
environment.etc."1password/custom_allowed_browsers".text = ''
|
||||||
vivaldi
|
vivaldi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
systemd.services.ollama-keep-alive =
|
||||||
|
let
|
||||||
|
ollamaURL = "http://localhost:${toString config.services.ollama.port}/api/generate";
|
||||||
|
payload = {
|
||||||
|
model = lib.elemAt config.services.ollama.loadModels 0;
|
||||||
|
keep_alive = -1;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
enable = true;
|
||||||
|
description = "Keep Ollama primary model loaded by pinging it";
|
||||||
|
after = [
|
||||||
|
"ollama.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
bindsTo = [ "ollama.service" ];
|
||||||
|
wantedBy = [
|
||||||
|
"multi-user.target"
|
||||||
|
"ollama.service"
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.curl}/bin/curl -s '${ollamaURL}' -d '${builtins.toJSON payload}'";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user