improving context

This commit is contained in:
Dario Ghunney Ware
2025-11-14 17:58:12 +00:00
parent c37707d9ad
commit 9ffbede49a
8 changed files with 209 additions and 88 deletions

View File

@@ -5145,8 +5145,7 @@
"title": "Stirling PDF Bot",
"alphaBadge": "Alpha",
"alphaTitle": "Experimental feature",
"alphaDescription": "This Chatbot feature is in currently in alpha and is subject to change. Image-based content is not supported yet. Responses may be imperfect, so double-check important answers.",
"acceptAlphaLabel": "I understand this feature is experimental and image content is not supported yet.",
"alphaDescription": "Chatbot is in currently in alpha and is subject to change. Responses may be imperfect, please check responses.",
"fileLabel": "Document to query",
"filePlaceholder": "Select an uploaded PDF",
"noFiles": "Upload a PDF from File Manager to start chatting.",
@@ -5193,6 +5192,14 @@
"noTextTitle": "No text detected in this PDF",
"noTextBody": "We could not find selectable text in this document. Would you like to run OCR to convert scanned pages into text?",
"noTextDismiss": "Maybe later",
"noTextRunOcr": "Run OCR and retry"
"noTextRunOcr": "Run OCR and retry",
"usage": {
"limitReachedTitle": "Chatbot limit reached",
"limitReachedBody": "You have exceeded the current monthly allocation for the chatbot. Further responses may be throttled.",
"nearingLimitTitle": "Approaching usage limit",
"nearingLimitBody": "You are nearing your monthly chatbot allocation. Consider limiting very large requests."
},
"autoSyncInfo": "Selected documents are synced automatically when the chatbot opens.",
"autoSyncPrompt": "Acknowledge the alpha notice to start syncing automatically."
}
}

View File

@@ -1,6 +1,5 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react';
import {
ActionIcon,
Badge,
Box,
Button,
@@ -13,14 +12,12 @@ import {
Switch,
Text,
Textarea,
Tooltip,
} from '@mantine/core';
import { useMediaQuery, useViewportSize } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded';
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded';
import SendRoundedIcon from '@mui/icons-material/SendRounded';
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import { useChatbot } from '@app/contexts/ChatbotContext';
@@ -47,8 +44,6 @@ interface ChatMessage {
createdAt: Date;
}
const ALPHA_ACK_KEY = 'stirling.chatbot.alphaAck';
function createMessageId() {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
@@ -68,7 +63,6 @@ const ChatbotDrawer = () => {
const { show } = useToast();
const files = selectors.getFiles();
const [selectedFileId, setSelectedFileId] = useState<string | undefined>();
const [alphaAccepted, setAlphaAccepted] = useState(false);
const [runOcr, setRunOcr] = useState(false);
const [isStartingSession, setIsStartingSession] = useState(false);
const [isSendingMessage, setIsSendingMessage] = useState(false);
@@ -94,11 +88,6 @@ const ChatbotDrawer = () => {
return;
}
const storedAck = typeof window !== 'undefined'
? window.localStorage.getItem(ALPHA_ACK_KEY) === 'true'
: false;
setAlphaAccepted(storedAck);
if (preferredFileId) {
setSelectedFileId(preferredFileId);
setPreferredFileId(undefined);
@@ -161,6 +150,8 @@ const ChatbotDrawer = () => {
setContextStats(null);
setMessages([]);
setWarnings([]);
setPendingOcrRetry(false);
setNoTextModalOpen(false);
}
}, [sessionInfo, selectedFileId]);
@@ -192,17 +183,6 @@ const ChatbotDrawer = () => {
};
}, [isMobile, isOpen, sidebarRefs.toolPanelRef]);
const handleAlphaAccept = (checked: boolean) => {
setAlphaAccepted(checked);
if (typeof window !== 'undefined') {
if (checked) {
window.localStorage.setItem(ALPHA_ACK_KEY, 'true');
} else {
window.localStorage.removeItem(ALPHA_ACK_KEY);
}
}
};
const withStatus = async <T,>(label: string, fn: () => Promise<T>): Promise<T> => {
setStatusMessage(label);
try {
@@ -228,14 +208,6 @@ const ChatbotDrawer = () => {
if (!ensureFileSelected() || !selectedFile) {
return;
}
if (!alphaAccepted) {
show({
alertType: 'neutral',
title: t('chatbot.toasts.ackTitle', 'Accept alpha notice'),
body: t('chatbot.toasts.ackBody', 'Please acknowledge the alpha warning before starting.'),
});
return;
}
setIsStartingSession(true);
try {
let workingFile: File = selectedFile;
@@ -271,7 +243,7 @@ const ChatbotDrawer = () => {
text: extractionResult.text,
metadata,
ocrRequested: shouldRunOcr,
warningsAccepted: alphaAccepted,
warningsAccepted: true,
};
const response = await withStatus(
@@ -302,6 +274,36 @@ const ChatbotDrawer = () => {
}
};
useEffect(() => {
if (
!isOpen ||
!selectedFile ||
sessionInfo ||
isStartingSession ||
pendingOcrRetry ||
noTextModalOpen
) {
return;
}
let cancelled = false;
handleSessionStart().catch((error) => {
if (!cancelled) {
console.error('[Chatbot] Auto-sync failed', error);
}
});
return () => {
cancelled = true;
};
}, [isOpen, selectedFile, sessionInfo, isStartingSession, pendingOcrRetry, noTextModalOpen, runOcr]);
useEffect(() => {
if (!sessionInfo) {
return;
}
setSessionInfo(null);
setContextStats(null);
}, [runOcr]);
const handleSendMessage = async () => {
if (!sessionInfo) {
show({
@@ -495,25 +497,6 @@ const ChatbotDrawer = () => {
transitionProps={{ transition: 'slide-left', duration: 200 }}
>
<Stack gap="sm" h="100%" style={{ minHeight: 0 }}>
<Box
p="sm"
style={{
border: '1px solid var(--border-subtle)',
borderRadius: 8,
backgroundColor: 'var(--bg-subtle)',
display: 'flex',
gap: '0.5rem',
alignItems: 'flex-start',
}}
>
<WarningAmberRoundedIcon fontSize="small" style={{ color: 'var(--text-warning)' }} />
<Box>
<Text fw={600}>{t('chatbot.alphaTitle', 'Experimental feature')}</Text>
<Text size="sm">
{t('chatbot.alphaDescription', 'This chatbot is in alpha. It currently ignores images and may produce inaccurate answers. Your PDF text stays local until you confirm you want to chat.')}
</Text>
</Box>
</Box>
<Group align="flex-end" justify="space-between" gap="md" wrap="wrap">
<Select
@@ -526,11 +509,6 @@ const ChatbotDrawer = () => {
style={{ flex: '1 1 200px' }}
/>
<Stack gap={4} style={{ minWidth: 160 }}>
<Switch
checked={alphaAccepted}
label={t('chatbot.acceptAlphaLabel', 'Alpha notice acknowledged')}
onChange={(event) => handleAlphaAccept(event.currentTarget.checked)}
/>
<Switch
checked={runOcr}
onChange={(event) => setRunOcr(event.currentTarget.checked)}
@@ -539,19 +517,6 @@ const ChatbotDrawer = () => {
</Stack>
</Group>
<Button
fullWidth
variant="filled"
leftSection={<RefreshRoundedIcon fontSize="small" />}
loading={isStartingSession}
onClick={() => handleSessionStart()}
disabled={!selectedFile || !alphaAccepted}
>
{sessionInfo
? t('chatbot.refreshButton', 'Re-sync document')
: t('chatbot.startButton', 'Send document to chat')}
</Button>
{statusMessage && (
<Box
p="sm"
@@ -596,6 +561,28 @@ const ChatbotDrawer = () => {
</Group>
</Box>
))}
{isOpen && (
<Box
p="sm"
bg="var(--bg-muted)"
style={{ borderRadius: 12, border: '1px solid var(--border-subtle)' }}
>
<Group gap="xs" align="flex-start">
<WarningAmberRoundedIcon fontSize="small" style={{ color: 'var(--text-warning)' }} />
<Box>
<Text size="sm" fw={600}>
{t('chatbot.alphaTitle', 'Experimental feature')}
</Text>
<Text size="sm">
{t(
'chatbot.alphaDescription',
'This chatbot is in alpha. It currently ignores images and may produce inaccurate answers.'
)}
</Text>
</Box>
</Group>
</Box>
)}
{messages.length === 0 && (
<Text size="sm" c="dimmed">
{t('chatbot.emptyState', 'Ask a question about your PDF to start the conversation.')}

View File

@@ -7,14 +7,10 @@ import LocalIcon from '@app/components/shared/LocalIcon';
import { Tooltip } from '@app/components/shared/Tooltip';
import { SearchInterface } from '@app/components/viewer/SearchInterface';
import ViewerAnnotationControls from '@app/components/shared/rightRail/ViewerAnnotationControls';
import { useFileState } from '@app/contexts/FileContext';
export function useViewerRightRailButtons() {
const { t } = useTranslation();
const viewer = useViewer();
const { selectors } = useFileState();
const filesSignature = selectors.getFilesSignature();
const files = useMemo(() => selectors.getFiles(), [selectors, filesSignature]);
const [isPanning, setIsPanning] = useState<boolean>(() => viewer.getPanState()?.isPanning ?? false);
// Lift i18n labels out of memo for clarity