feat(kon): scaffold hybrid modular workspace
- Cargo workspace with 6 domain crates: core, audio, transcription, ai-formatting, storage, cloud-providers - Minimal Tauri shell (lib.rs + main.rs) with plugin registration - Svelte 5 frontend copied from Ramble v0.2 - All crates compile as empty stubs - App identifier: uk.co.corbel.kon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
352
src/lib/stores/page.svelte.js
Normal file
352
src/lib/stores/page.svelte.js
Normal file
@@ -0,0 +1,352 @@
|
||||
/** @type {{ current: string, status: string, statusColor: string, activeProfile: string, recording: boolean, timerText: string, handedness: string, taskSidebarOpen: boolean }} */
|
||||
export const page = $state({
|
||||
current: "dictation",
|
||||
status: "Ready",
|
||||
statusColor: "#7ec89a",
|
||||
activeProfile: "None",
|
||||
recording: false,
|
||||
timerText: "00:00",
|
||||
handedness: "Right",
|
||||
taskSidebarOpen: false,
|
||||
});
|
||||
|
||||
// ---- Settings (persisted to localStorage) ----
|
||||
|
||||
const SETTINGS_KEY = "ramble_settings";
|
||||
|
||||
const defaults = {
|
||||
engine: "whisper",
|
||||
modelSize: "Base",
|
||||
language: "en",
|
||||
device: "auto",
|
||||
formatMode: "Smart",
|
||||
removeFillers: true,
|
||||
antiHallucination: true,
|
||||
britishEnglish: true,
|
||||
autoCopy: true,
|
||||
includeTimestamps: true,
|
||||
theme: "Dark",
|
||||
fontSize: 14,
|
||||
llmModelSize: "small",
|
||||
llmEnabled: false,
|
||||
saveAudio: false,
|
||||
outputFolder: "",
|
||||
globalHotkey: "Ctrl+Shift+R",
|
||||
};
|
||||
|
||||
function loadSettings() {
|
||||
try {
|
||||
const raw = localStorage.getItem(SETTINGS_KEY);
|
||||
if (raw) return { ...defaults, ...JSON.parse(raw) };
|
||||
} catch {}
|
||||
return { ...defaults };
|
||||
}
|
||||
|
||||
export const settings = $state(loadSettings());
|
||||
|
||||
/** Save current settings to localStorage */
|
||||
export function saveSettings() {
|
||||
try {
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ---- Profiles (persisted to localStorage) ----
|
||||
|
||||
const PROFILES_KEY = "ramble_profiles";
|
||||
|
||||
function loadProfiles() {
|
||||
try {
|
||||
const raw = localStorage.getItem(PROFILES_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const profiles = $state(loadProfiles());
|
||||
|
||||
export function saveProfiles() {
|
||||
try {
|
||||
localStorage.setItem(PROFILES_KEY, JSON.stringify(profiles));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ---- History (persisted to localStorage) ----
|
||||
|
||||
const HISTORY_KEY = "ramble_history";
|
||||
|
||||
function loadHistory() {
|
||||
try {
|
||||
const raw = localStorage.getItem(HISTORY_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const history = $state(loadHistory());
|
||||
|
||||
export function saveHistory() {
|
||||
try {
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function addToHistory(entry) {
|
||||
history.unshift(entry);
|
||||
if (history.length > 100) history.length = 100;
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
export function deleteFromHistory(index) {
|
||||
history.splice(index, 1);
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
// ---- Tasks (persisted to localStorage) ----
|
||||
|
||||
const TASKS_KEY = "ramble_tasks";
|
||||
|
||||
function loadTasks() {
|
||||
try {
|
||||
const raw = localStorage.getItem(TASKS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const tasks = $state(loadTasks());
|
||||
|
||||
export function saveTasks() {
|
||||
try {
|
||||
localStorage.setItem(TASKS_KEY, JSON.stringify(tasks));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function addTask(task) {
|
||||
tasks.unshift({
|
||||
id: crypto.randomUUID(),
|
||||
text: task.text,
|
||||
bucket: task.bucket || "inbox",
|
||||
listId: task.listId || null,
|
||||
context: task.context || "",
|
||||
effort: task.effort || "",
|
||||
done: false,
|
||||
doneAt: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
sourceTranscriptId: task.sourceTranscriptId || null,
|
||||
notes: "",
|
||||
});
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
}
|
||||
|
||||
export function updateTask(id, updates) {
|
||||
const idx = tasks.findIndex((t) => t.id === id);
|
||||
if (idx >= 0) {
|
||||
Object.assign(tasks[idx], updates);
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteTask(id) {
|
||||
const idx = tasks.findIndex((t) => t.id === id);
|
||||
if (idx >= 0) {
|
||||
tasks.splice(idx, 1);
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
}
|
||||
}
|
||||
|
||||
export function completeTask(id) {
|
||||
updateTask(id, { done: true, doneAt: new Date().toISOString() });
|
||||
}
|
||||
|
||||
export function uncompleteTask(id) {
|
||||
updateTask(id, { done: false, doneAt: null });
|
||||
}
|
||||
|
||||
// ---- BroadcastChannel for multi-window task sync ----
|
||||
|
||||
const taskChannel = typeof BroadcastChannel !== "undefined"
|
||||
? new BroadcastChannel("ramble_tasks")
|
||||
: null;
|
||||
|
||||
if (taskChannel) {
|
||||
taskChannel.onmessage = (event) => {
|
||||
if (event.data.type === "tasks_updated") {
|
||||
tasks.length = 0;
|
||||
tasks.push(...event.data.tasks);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function broadcastTasks() {
|
||||
if (taskChannel) {
|
||||
taskChannel.postMessage({ type: "tasks_updated", tasks: $state.snapshot(tasks) });
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Task Lists (persisted to localStorage) ----
|
||||
|
||||
const TASK_LISTS_KEY = "ramble_task_lists";
|
||||
|
||||
function loadTaskLists() {
|
||||
try {
|
||||
const raw = localStorage.getItem(TASK_LISTS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [
|
||||
{ id: "all", name: "All Tasks", builtIn: true, createdAt: null },
|
||||
{ id: "inbox", name: "Inbox", builtIn: true, createdAt: null },
|
||||
];
|
||||
}
|
||||
|
||||
export const taskLists = $state(loadTaskLists());
|
||||
|
||||
export function saveTaskLists() {
|
||||
try {
|
||||
localStorage.setItem(TASK_LISTS_KEY, JSON.stringify(taskLists));
|
||||
} catch {}
|
||||
broadcastTaskLists();
|
||||
}
|
||||
|
||||
export function addTaskList(name, profileId = null) {
|
||||
taskLists.push({
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
builtIn: false,
|
||||
profileId,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
saveTaskLists();
|
||||
}
|
||||
|
||||
export function addProfileTaskList(profileName) {
|
||||
const exists = taskLists.find((l) => l.profileId === profileName);
|
||||
if (exists) return exists.id;
|
||||
const id = crypto.randomUUID();
|
||||
taskLists.push({
|
||||
id,
|
||||
name: profileName,
|
||||
builtIn: false,
|
||||
profileId: profileName,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
saveTaskLists();
|
||||
return id;
|
||||
}
|
||||
|
||||
export function removeProfileTaskList(profileName) {
|
||||
const idx = taskLists.findIndex((l) => l.profileId === profileName);
|
||||
if (idx >= 0) {
|
||||
const listId = taskLists[idx].id;
|
||||
for (const t of tasks) {
|
||||
if (t.listId === listId) t.listId = null;
|
||||
}
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
taskLists.splice(idx, 1);
|
||||
saveTaskLists();
|
||||
}
|
||||
}
|
||||
|
||||
export function moveTaskToList(taskId, listId) {
|
||||
updateTask(taskId, { listId: listId === "inbox" || listId === "all" ? null : listId });
|
||||
}
|
||||
|
||||
export function moveTaskListToProfile(listId, profileId) {
|
||||
const list = taskLists.find((l) => l.id === listId);
|
||||
if (list && !list.builtIn) {
|
||||
list.profileId = profileId || null;
|
||||
if (profileId) list.name = profileId;
|
||||
saveTaskLists();
|
||||
}
|
||||
}
|
||||
|
||||
export function renameTaskList(id, name) {
|
||||
const list = taskLists.find((l) => l.id === id);
|
||||
if (list && !list.builtIn) {
|
||||
list.name = name;
|
||||
saveTaskLists();
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteTaskList(id) {
|
||||
const idx = taskLists.findIndex((l) => l.id === id);
|
||||
if (idx >= 0 && !taskLists[idx].builtIn) {
|
||||
for (const t of tasks) {
|
||||
if (t.listId === id) t.listId = null;
|
||||
}
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
taskLists.splice(idx, 1);
|
||||
saveTaskLists();
|
||||
}
|
||||
}
|
||||
|
||||
export function moveTaskList(id, direction) {
|
||||
const idx = taskLists.findIndex((l) => l.id === id);
|
||||
const targetIdx = idx + direction;
|
||||
if (targetIdx < 0 || targetIdx >= taskLists.length) return;
|
||||
if (taskLists[targetIdx].builtIn) return;
|
||||
[taskLists[idx], taskLists[targetIdx]] = [taskLists[targetIdx], taskLists[idx]];
|
||||
saveTaskLists();
|
||||
}
|
||||
|
||||
export function sortTaskLists(mode) {
|
||||
const builtIn = taskLists.filter((l) => l.builtIn);
|
||||
const custom = taskLists.filter((l) => !l.builtIn);
|
||||
if (mode === "alpha") {
|
||||
custom.sort((a, b) => a.name.localeCompare(b.name));
|
||||
} else if (mode === "date") {
|
||||
custom.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
||||
}
|
||||
taskLists.length = 0;
|
||||
taskLists.push(...builtIn, ...custom);
|
||||
saveTaskLists();
|
||||
}
|
||||
|
||||
// BroadcastChannel for task lists
|
||||
const taskListChannel = typeof BroadcastChannel !== "undefined"
|
||||
? new BroadcastChannel("ramble_task_lists")
|
||||
: null;
|
||||
|
||||
if (taskListChannel) {
|
||||
taskListChannel.onmessage = (event) => {
|
||||
if (event.data.type === "task_lists_updated") {
|
||||
taskLists.length = 0;
|
||||
taskLists.push(...event.data.lists);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function broadcastTaskLists() {
|
||||
if (taskListChannel) {
|
||||
taskListChannel.postMessage({ type: "task_lists_updated", lists: $state.snapshot(taskLists) });
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Templates (persisted to localStorage) ----
|
||||
|
||||
const TEMPLATES_KEY = "ramble_templates";
|
||||
|
||||
function loadTemplates() {
|
||||
try {
|
||||
const raw = localStorage.getItem(TEMPLATES_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [
|
||||
{ name: "Meeting Notes", sections: ["Attendees", "Agenda", "Discussion", "Action Items", "Next Steps"] },
|
||||
{ name: "Report", sections: ["Summary", "Background", "Findings", "Recommendations"] },
|
||||
{ name: "Interview", sections: ["Candidate", "Role", "Questions & Answers", "Assessment", "Next Steps"] },
|
||||
];
|
||||
}
|
||||
|
||||
export const templates = $state(loadTemplates());
|
||||
|
||||
export function saveTemplates() {
|
||||
try {
|
||||
localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates));
|
||||
} catch {}
|
||||
}
|
||||
Reference in New Issue
Block a user