feat(ollama): InboxApi + preload + store recheckOllama + onOllamaStatus subscriber (#1 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,10 +27,16 @@ const api: InklingApi = {
|
||||
getTrashCount: () => ipcRenderer.invoke('inbox:trashCount'),
|
||||
listExpired: () => ipcRenderer.invoke('inbox:listExpired'),
|
||||
trashExpiredBatch: (ids) => ipcRenderer.invoke('inbox:trashExpiredBatch', { ids }),
|
||||
ollamaRecheck: () => ipcRenderer.invoke('inbox:ollamaRecheck'),
|
||||
onNoteUpdated: (cb) => {
|
||||
const listener = (_e: unknown, note: Note) => cb(note);
|
||||
ipcRenderer.on('note:updated', listener);
|
||||
return () => ipcRenderer.off('note:updated', listener);
|
||||
},
|
||||
onOllamaStatus: (cb) => {
|
||||
const listener = (_e: unknown, status: { ok: boolean; reason?: string }) => cb(status);
|
||||
ipcRenderer.on('ollama:status', listener);
|
||||
return () => ipcRenderer.off('ollama:status', listener);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,13 +22,16 @@ export function App(): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
void loadInitial();
|
||||
const unsub = inboxApi.onNoteUpdated((note) => {
|
||||
const unsubNote = inboxApi.onNoteUpdated((note) => {
|
||||
upsertNote(note);
|
||||
void refreshMeta();
|
||||
});
|
||||
const unsubOllama = inboxApi.onOllamaStatus((status) => {
|
||||
useInbox.setState({ ollamaStatus: status });
|
||||
});
|
||||
const onFocus = () => { void refreshMeta(); };
|
||||
window.addEventListener('focus', onFocus);
|
||||
return () => { unsub(); window.removeEventListener('focus', onFocus); };
|
||||
return () => { unsubNote(); unsubOllama(); window.removeEventListener('focus', onFocus); };
|
||||
}, [loadInitial, refreshMeta, upsertNote]);
|
||||
|
||||
const showRecovery = continuity.showRecoveryToast && !recoveryDismissed;
|
||||
|
||||
@@ -30,6 +30,7 @@ interface InboxState {
|
||||
loadExpired: () => Promise<void>;
|
||||
trashExpiredBatch: (ids: string[]) => Promise<void>;
|
||||
snoozeExpired: () => void;
|
||||
recheckOllama: () => Promise<void>;
|
||||
}
|
||||
|
||||
const emptyContinuity: WeeklyContinuity = {
|
||||
@@ -167,5 +168,9 @@ export const useInbox = create<InboxState>((set, get) => ({
|
||||
const kstMidnightFloor = Math.floor(kstNow / 86_400_000) * 86_400_000;
|
||||
const nextKstMidnight = kstMidnightFloor + 86_400_000;
|
||||
set({ expiredSnoozeUntilMs: nextKstMidnight - KST_OFFSET_MS });
|
||||
},
|
||||
async recheckOllama() {
|
||||
const status = await inboxApi.ollamaRecheck();
|
||||
set({ ollamaStatus: status });
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -79,7 +79,9 @@ export interface InboxApi {
|
||||
getTrashCount(): Promise<number>;
|
||||
listExpired(): Promise<Note[]>;
|
||||
trashExpiredBatch(ids: string[]): Promise<{ trashedCount: number; confirmed: boolean }>;
|
||||
ollamaRecheck(): Promise<{ ok: boolean; reason?: string }>;
|
||||
onNoteUpdated(cb: (note: Note) => void): () => void;
|
||||
onOllamaStatus(cb: (status: { ok: boolean; reason?: string }) => void): () => void;
|
||||
}
|
||||
|
||||
export interface InklingApi {
|
||||
|
||||
55
tests/unit/store.ollama.test.ts
Normal file
55
tests/unit/store.ollama.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const mockApi = {
|
||||
listNotes: vi.fn(async () => []),
|
||||
listTrash: vi.fn(async () => []),
|
||||
getTrashCount: vi.fn(async () => 0),
|
||||
getContinuity: vi.fn(async () => ({ weekStart: '', weekCount: 0, weekTarget: 7, consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null })),
|
||||
getPendingCount: vi.fn(async () => 0),
|
||||
getOllamaStatus: vi.fn(async () => ({ ok: true })),
|
||||
getTodayCount: vi.fn(async () => 0),
|
||||
restoreNote: vi.fn(async () => {}),
|
||||
permanentDeleteNote: vi.fn(async () => ({ confirmed: true })),
|
||||
emptyTrash: vi.fn(async () => ({ confirmed: true, count: 0 })),
|
||||
deleteNote: vi.fn(async () => {}),
|
||||
onNoteUpdated: vi.fn(() => () => {}),
|
||||
updateAiFields: vi.fn(async () => {}),
|
||||
setDueDate: vi.fn(async () => {}),
|
||||
setIntent: vi.fn(async () => {}),
|
||||
dismissIntent: vi.fn(async () => {}),
|
||||
listExpired: vi.fn(async () => []),
|
||||
trashExpiredBatch: vi.fn(async () => ({ trashedCount: 0, confirmed: false })),
|
||||
ollamaRecheck: vi.fn(async (): Promise<{ ok: boolean; reason?: string }> => ({ ok: true })),
|
||||
onOllamaStatus: vi.fn(() => () => {})
|
||||
};
|
||||
|
||||
vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: mockApi }));
|
||||
|
||||
describe('useInbox — ollama (v0.2.3 #1)', () => {
|
||||
beforeEach(async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.setState({
|
||||
notes: [], trashNotes: [], trashCount: 0, showTrash: false,
|
||||
loading: false, tagFilter: null, pendingCount: 0, todayCount: 0,
|
||||
ollamaStatus: { ok: false, reason: 'refused' },
|
||||
continuity: { weekStart: '', weekCount: 0, weekTarget: 7, consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null },
|
||||
expiredCandidates: [], expiredSnoozeUntilMs: null
|
||||
});
|
||||
Object.values(mockApi).forEach((fn) => 'mockClear' in fn && (fn as any).mockClear());
|
||||
});
|
||||
|
||||
it('recheckOllama calls inboxApi.ollamaRecheck and updates ollamaStatus', async () => {
|
||||
mockApi.ollamaRecheck.mockResolvedValueOnce({ ok: true });
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
await useInbox.getState().recheckOllama();
|
||||
expect(mockApi.ollamaRecheck).toHaveBeenCalledTimes(1);
|
||||
expect(useInbox.getState().ollamaStatus).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('recheckOllama propagates failure status', async () => {
|
||||
mockApi.ollamaRecheck.mockResolvedValueOnce({ ok: false, reason: 'timeout' });
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
await useInbox.getState().recheckOllama();
|
||||
expect(useInbox.getState().ollamaStatus).toEqual({ ok: false, reason: 'timeout' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user