67 lines
5.8 KiB
JavaScript
67 lines
5.8 KiB
JavaScript
import Adw from 'gi://Adw';
|
|
import Gio from 'gi://Gio';
|
|
import Gtk from 'gi://Gtk';
|
|
import { daemonCall } from './settings-editor.js';
|
|
|
|
export function obsidianSettingsPage(editor, window) {
|
|
const page = editor.page(window, 'Obsidian', ['Obsidian vault', 'Obsidian memory'], 'folder-documents-symbolic');
|
|
const pathRow = editor.rows.get('obsidianVaultPath');
|
|
const choose = new Gtk.Button({ label: 'Choose folder…', valign: Gtk.Align.CENTER });
|
|
choose.connect('clicked', () => {
|
|
const dialog = new Gtk.FileChooserNative({ title: 'Choose an empty agent vault folder', transient_for: window, action: Gtk.FileChooserAction.SELECT_FOLDER, accept_label: 'Choose' });
|
|
dialog.connect('response', (_dialog, response) => {
|
|
if (response === Gtk.ResponseType.ACCEPT) {
|
|
const selected = dialog.get_file()?.get_path();
|
|
if (selected) pathRow.text = selected;
|
|
}
|
|
dialog.destroy();
|
|
});
|
|
dialog.show();
|
|
});
|
|
pathRow.add_suffix(choose);
|
|
const group = new Adw.PreferencesGroup({ title: 'Configuration and access checks', description: 'Apply the settings above first. Initialize accepts only an empty folder or an existing Jarvis agent vault. Register the folder once using Open folder as vault in Obsidian.' });
|
|
const state = new Adw.ActionRow({ title: 'Bridge status', subtitle: 'Refresh to inspect the applied configuration.' });
|
|
const buttons = [];
|
|
const run = async args => {
|
|
if (Object.keys(editor.changes).some(key => key.startsWith('obsidian'))) throw new Error('Apply your Obsidian settings before using these controls.');
|
|
const [raw] = await daemonCall('ObsidianAction', '(s)', [JSON.stringify(args)]);
|
|
return JSON.parse(raw);
|
|
};
|
|
const describe = result => {
|
|
if (!result.enabled) return 'Disabled. Agent vault access is off.';
|
|
if (result.error) return `${result.path} · ${result.error}`;
|
|
return `${result.path} · ${result.ready ? 'Initialized' : 'Not initialized'} · Read ${result.readable ? 'OK' : 'unavailable'} · Write ${result.writable ? 'OK' : 'unavailable'} · Memory ${result.memoryEnabled ? (result.memoryVerified ? 'verified' : 'enabled') : 'off'} · ${result.files ?? 0} files${result.truncated ? ' (partial count)' : ''}${result.readWriteVerified ? ' · Read/write probe passed' : ''}`;
|
|
};
|
|
const action = (row, label, callback) => {
|
|
const button = new Gtk.Button({ label, valign: Gtk.Align.CENTER }); buttons.push(button);
|
|
button.connect('clicked', async () => {
|
|
for (const item of buttons) item.sensitive = false;
|
|
try { await callback(); } catch (error) { state.subtitle = error.message; }
|
|
finally { for (const item of buttons) item.sensitive = true; }
|
|
}); row.add_suffix(button);
|
|
};
|
|
action(state, 'Refresh', async () => { state.subtitle = describe(await run({ action: 'status' })); });
|
|
group.add(state);
|
|
const checks = new Adw.ActionRow({ title: 'Prepare and verify', subtitle: 'Verify writes, reads, and removes a temporary probe. Memory gets its own check when enabled.' });
|
|
action(checks, 'Initialize vault', async () => { state.subtitle = describe(await run({ action: 'initialize' })); });
|
|
action(checks, 'Verify access', async () => { state.subtitle = describe(await run({ action: 'verify' })); });
|
|
group.add(checks);
|
|
const open = new Adw.ActionRow({ title: 'Open vault', subtitle: 'Requires Obsidian installed and this folder registered as a vault. Folder opens the file manager.' });
|
|
action(open, 'Folder', async () => { const status = await run({ action: 'status' }); if (!status.ready) throw new Error(status.error || 'Enable and initialize the vault first'); Gio.AppInfo.launch_default_for_uri(Gio.File.new_for_path(status.path).get_uri(), null); });
|
|
action(open, 'Obsidian', async () => { const status = await run({ action: 'status' }); if (!status.uri) throw new Error(status.error || 'Enable and initialize the vault first'); Gio.AppInfo.launch_default_for_uri(status.uri, null); });
|
|
group.add(open); page.add(group);
|
|
|
|
const memory = new Adw.PreferencesGroup({ title: 'Inspect notes and memory', description: 'Browse files, search Markdown, or read a vault-relative path. Memory access follows the applied memory switch. Results stay in this window.' });
|
|
const input = new Adw.EntryRow({ title: 'Search text or relative file path' }); memory.add(input);
|
|
const inspect = new Adw.ActionRow({ title: 'Vault contents', subtitle: 'Read displays text files. Use the agent to create, edit, organize, or restore notes.' });
|
|
const view = new Gtk.TextView({ editable: false, cursor_visible: true, wrap_mode: Gtk.WrapMode.WORD_CHAR, left_margin: 12, right_margin: 12, top_margin: 8, bottom_margin: 8 });
|
|
const scroll = new Gtk.ScrolledWindow({ min_content_height: 220, max_content_height: 360, propagate_natural_height: true, has_frame: true }); scroll.set_child(view);
|
|
const show = value => view.buffer.set_text(String(value).slice(0, 64000), -1);
|
|
action(inspect, 'Files', async () => { const result = await run({ action: 'list' }); show(result.entries.map(e => `${e.type === 'folder' ? '[folder]' : `${e.bytes} B`} ${e.path}`).join('\n') + (result.truncated ? '\nMore entries omitted.' : '')); });
|
|
action(inspect, 'Search', async () => { const result = await run({ action: 'search', query: input.text }); show(result.matches.map(e => `${e.path}\n${e.snippet}`).join('\n\n') || 'No matching notes.'); });
|
|
action(inspect, 'Memory', async () => { const result = await run({ action: 'memory_search', query: input.text }); show(result.matches.map(e => `${e.path}\n${e.snippet}`).join('\n\n') || 'No matching memories.'); });
|
|
action(inspect, 'Read', async () => { if (!input.text.toLowerCase().endsWith('.md')) throw new Error('Enter a Markdown file path, such as memory/preferences.md'); const note = await run({ action: 'read', path: input.text }); show(`${note.path}\n\n${note.content}`); });
|
|
memory.add(inspect); memory.add(scroll); page.add(memory);
|
|
return page;
|
|
}
|