Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 5 additions & 4 deletions client/src/components/digital-twin/tabs/EnrichTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function EnrichTab({ onRefresh }) {

const loadData = useCallback(async () => {
setLoading(true);
const progressData = await api.getSoulEnrichProgress().catch(() => null);
const progressData = await api.getDigitalTwinEnrichProgress().catch(() => null);
setProgress(progressData);
setLoading(false);
}, []);
Expand Down Expand Up @@ -121,7 +121,7 @@ export default function EnrichTab({ onRefresh }) {
content: writingAnalysis.suggestedContent
}).catch(async () => {
// Document might exist, try to update by fetching ID
const docs = await api.getSoulDocuments();
const docs = await api.getDigitalTwinDocuments();
Comment thread
atomantic marked this conversation as resolved.
Outdated
const existing = docs.find(d => d.filename === 'WRITING_STYLE.md');
if (existing) {
return api.updateSoulDocument(existing.id, {
Expand All @@ -137,11 +137,12 @@ export default function EnrichTab({ onRefresh }) {
const loadQuestion = useCallback(async (categoryId, skipList = []) => {
setLoadingQuestion(true);
try {
const question = await api.getSoulEnrichQuestion(categoryId, undefined, undefined, skipList.length ? skipList : undefined);
const question = await api.getDigitalTwinEnrichQuestion(categoryId, undefined, undefined, skipList.length ? skipList : undefined);
setCurrentQuestion(question);
setAnswer('');
setScaleValue(null);
} catch {
} catch (err) {
console.warn(`⚠️ Failed to load enrichment question: ${err.message}`);
setCurrentQuestion(null);
} finally {
setLoadingQuestion(false);
Expand Down
3 changes: 2 additions & 1 deletion client/src/hooks/useAutoRefetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export function useAutoRefetch(fetchFn, intervalMs) {
if (cancelled) return;
setData(result);
setLoading(false);
} catch {
} catch (err) {
// Keep prior data on failure, just clear loading state
console.warn(`⚠️ Auto-refetch failed: ${err.message}`);
if (!cancelled) setLoading(false);
}
};
Expand Down
42 changes: 13 additions & 29 deletions client/src/pages/CharacterSheet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,12 @@ import {
} from 'lucide-react';
import toast from '../components/ui/Toast';
import { timeAgo } from '../utils/formatters';
import { generateAvatar } from '../services/api';
import api, { generateAvatar } from '../services/api';
import socket from '../services/socket';

const request = async (endpoint, options = {}) => {
const response = await fetch(`/api/character${endpoint}`, {
headers: { 'Content-Type': 'application/json', ...options.headers },
...options
});
let data = null;
try { data = await response.json(); } catch { /* non-JSON body */ }
if (!response.ok) {
const message = data?.message || `Request failed with status ${response.status}`;
const error = new Error(message);
error.status = response.status;
throw error;
}
return data;
};

const get = () => request('');
const post = (path, body) => request(path, { method: 'POST', body: JSON.stringify(body) });
const put = (body) => request('', { method: 'PUT', body: JSON.stringify(body) });
const charGet = () => api.get('/character');
const charPost = (path, body) => api.post(`/character${path}`, body);
const charPut = (body) => api.put('/character', body);

// D&D 5e XP thresholds (must match server)
const XP_THRESHOLDS = [
Expand Down Expand Up @@ -91,7 +75,7 @@ export default function CharacterSheet() {
const load = useCallback(async () => {
setLoadError(null);
try {
const data = await get();
const data = await charGet();
if (!data || data.error) {
setLoadError('Failed to load character data');
return;
Expand Down Expand Up @@ -143,7 +127,7 @@ export default function CharacterSheet() {

const handleDamage = async () => {
try {
const result = await post('/damage', { diceNotation: dmgDice, description: dmgDesc || undefined });
const result = await charPost('/damage', { diceNotation: dmgDice, description: dmgDesc || undefined });
setChar(result.character);
setActiveAction(null);
setDmgDice('1d6');
Expand All @@ -153,22 +137,22 @@ export default function CharacterSheet() {

const handleShortRest = async () => {
try {
const result = await post('/rest', { type: 'short' });
const result = await charPost('/rest', { type: 'short' });
setChar(result.character);
} catch (err) { toast.error(err.message || 'Failed to take short rest'); }
};

const handleLongRest = async () => {
try {
const result = await post('/rest', { type: 'long' });
const result = await charPost('/rest', { type: 'long' });
setChar(result.character);
} catch (err) { toast.error(err.message || 'Failed to take long rest'); }
};

const handleAddXp = async () => {
if (!xpAmount) return;
try {
const result = await post('/xp', { amount: Number(xpAmount), source: 'manual', description: xpDesc || undefined });
const result = await charPost('/xp', { amount: Number(xpAmount), source: 'manual', description: xpDesc || undefined });
setChar(result.character);
setActiveAction(null);
setXpAmount('');
Expand All @@ -182,7 +166,7 @@ export default function CharacterSheet() {
const body = { description: evtDesc };
if (evtXp) body.xp = Number(evtXp);
if (evtDice) body.diceNotation = evtDice;
const result = await post('/event', body);
const result = await charPost('/event', body);
setChar(result.character);
setActiveAction(null);
setEvtDesc('');
Expand All @@ -194,7 +178,7 @@ export default function CharacterSheet() {
const handleSync = async (type) => {
setSyncing(type);
try {
const result = await post(`/sync/${type}`, {});
const result = await charPost(`/sync/${type}`, {});
setChar(result.character);
} catch (err) {
toast.error(err.message || `Failed to sync ${type}`);
Expand All @@ -206,7 +190,7 @@ export default function CharacterSheet() {
const handleNameSave = async () => {
try {
if (nameVal.trim() && nameVal !== char.name) {
const data = await put({ name: nameVal.trim() });
const data = await charPut({ name: nameVal.trim() });
setChar(data);
}
} catch (err) { toast.error(err.message || 'Failed to save name'); }
Expand All @@ -216,7 +200,7 @@ export default function CharacterSheet() {
const handleClassSave = async () => {
try {
if (classVal.trim() && classVal !== char.class) {
const data = await put({ class: classVal.trim() });
const data = await charPut({ class: classVal.trim() });
setChar(data);
}
} catch (err) { toast.error(err.message || 'Failed to save class'); }
Expand Down
10 changes: 7 additions & 3 deletions client/src/pages/DataDog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ export default function DataDog() {
...(formData.appKey && { appKey: formData.appKey })
};

await api.post('/datadog/instances', payload);
const saved = await api.post('/datadog/instances', payload);

toast.success(`DataDog instance "${payload.name}" saved successfully`);
await loadInstances();
setInstances(prev => ({ ...prev, [saved.id]: saved }));
handleCancel();
} catch (error) {
console.error(`Failed to save DataDog instance: ${error.message}`);
Expand All @@ -125,7 +125,11 @@ export default function DataDog() {
try {
await api.delete(`/datadog/instances/${instanceId}`);
toast.success(`DataDog instance "${instanceId}" deleted`);
await loadInstances();
setInstances(prev => {
const next = { ...prev };
delete next[instanceId];
return next;
});
} catch (error) {
console.error(`Failed to delete DataDog instance: ${error.message}`);
toast.error(`Failed to delete: ${error.message}`);
Expand Down
10 changes: 5 additions & 5 deletions client/src/pages/FeatureAgentDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ export default function FeatureAgentDetail() {
}, [fetchAgent, id]);

const handleStart = useCallback((agentId) => {
api.startFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Activated'); }).catch(() => {});
api.startFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Activated'); }).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);
const handlePause = useCallback((agentId) => {
api.pauseFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Paused'); }).catch(() => {});
api.pauseFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Paused'); }).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);
const handleResume = useCallback((agentId) => {
api.resumeFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Resumed'); }).catch(() => {});
api.resumeFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Resumed'); }).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);
const handleStop = useCallback((agentId) => {
api.stopFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Stopped'); }).catch(() => {});
api.stopFeatureAgent(agentId).then(data => { setAgent(prev => ({ ...prev, ...data })); toast.success('Stopped'); }).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);
const handleTrigger = useCallback((agentId) => {
api.triggerFeatureAgent(agentId).then(() => toast.success('Run triggered')).catch(() => {});
api.triggerFeatureAgent(agentId).then(() => toast.success('Run triggered')).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handleSave = useCallback((saved) => {
Expand Down
12 changes: 6 additions & 6 deletions client/src/pages/FeatureAgents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,41 +33,41 @@ export default function FeatureAgents() {
api.startFeatureAgent(id).then(agent => {
setAgents(prev => prev.map(a => a.id === id ? { ...a, ...agent } : a));
toast.success('Feature agent activated');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handlePause = useCallback((id) => {
api.pauseFeatureAgent(id).then(agent => {
setAgents(prev => prev.map(a => a.id === id ? { ...a, ...agent } : a));
toast.success('Feature agent paused');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handleResume = useCallback((id) => {
api.resumeFeatureAgent(id).then(agent => {
setAgents(prev => prev.map(a => a.id === id ? { ...a, ...agent } : a));
toast.success('Feature agent resumed');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handleStop = useCallback((id) => {
api.stopFeatureAgent(id).then(agent => {
setAgents(prev => prev.map(a => a.id === id ? { ...a, ...agent } : a));
toast.success('Feature agent stopped');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handleTrigger = useCallback((id) => {
api.triggerFeatureAgent(id).then(() => {
toast.success('Run triggered');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const handleDelete = useCallback((id) => {
api.deleteFeatureAgent(id).then(() => {
setAgents(prev => prev.filter(a => a.id !== id));
toast.success('Feature agent deleted');
}).catch(() => {});
}).catch(err => toast.error(err.message || 'Action failed'));
Comment thread
atomantic marked this conversation as resolved.
}, []);

const activeCount = agents.filter(a => a.status === 'active').length;
Expand Down
16 changes: 10 additions & 6 deletions client/src/pages/Jira.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function Jira() {
const response = await api.get('/jira/instances');
setInstances(response.instances || {});
} catch (error) {
console.error('Failed to load JIRA instances:', error);
console.error(`Failed to load JIRA instances: ${error.message}`);
toast.error(`Failed to load JIRA instances: ${error.message}`);
} finally {
setLoading(false);
Expand Down Expand Up @@ -102,13 +102,13 @@ export default function Jira() {
apiToken: formData.apiToken
};

await api.post('/jira/instances', payload);
const saved = await api.post('/jira/instances', payload);

toast.success(`JIRA instance "${payload.name}" saved successfully`);
await loadInstances();
setInstances(prev => ({ ...prev, [saved.id]: saved }));
handleCancel();
} catch (error) {
console.error('Failed to save JIRA instance:', error);
console.error(`Failed to save JIRA instance: ${error.message}`);
setSaveError(error.message);
toast.error(`Failed to save: ${error.message}`);
} finally {
Expand All @@ -127,9 +127,13 @@ export default function Jira() {
try {
await api.delete(`/jira/instances/${instanceId}`);
toast.success(`JIRA instance "${instanceId}" deleted`);
await loadInstances();
setInstances(prev => {
const next = { ...prev };
delete next[instanceId];
return next;
});
} catch (error) {
console.error('Failed to delete JIRA instance:', error);
console.error(`Failed to delete JIRA instance: ${error.message}`);
toast.error(`Failed to delete: ${error.message}`);
}
};
Expand Down
6 changes: 4 additions & 2 deletions server/lib/telegramClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { EventEmitter } from 'events';
const BASE_URL = 'https://api.telegram.org/bot';
const POLL_TIMEOUT_SEC = 30;
const API_TIMEOUT_MS = 10_000; // regular calls: sendMessage, editMessageText, etc.
const RETRY_DELAY_API_ERROR_MS = 5_000;
const RETRY_DELAY_NETWORK_ERROR_MS = 2_000;

function buildUrl(token, method) {
return `${BASE_URL}${token}/${method}`;
Expand Down Expand Up @@ -57,14 +59,14 @@ export function createTelegramBot(token, opts = {}) {
const json = await res.json();
if (!json.ok) {
// Retry after a delay on API errors
await new Promise(r => setTimeout(r, 5000));
await new Promise(r => setTimeout(r, RETRY_DELAY_API_ERROR_MS));
continue;
}
updates = json.result;
} catch {
// Aborted (stopPolling called) or network error
if (!polling) break;
await new Promise(r => setTimeout(r, 2000));
await new Promise(r => setTimeout(r, RETRY_DELAY_NETWORK_ERROR_MS));
continue;
}

Expand Down
Loading