Skip to content

Commit 657e943

Browse files
committed
fix: repair @ mention context, dead state, and misc bugs
1 parent 873849d commit 657e943

File tree

6 files changed

+30
-52
lines changed

6 files changed

+30
-52
lines changed

apps/saru/app/api/blog-chat/route.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ import { myProvider } from '@/lib/ai/providers';
55
export async function POST(request: Request) {
66
const { messages, context, title, author, date } = await request.json();
77

8-
const prompt = [
9-
'You are a helpful AI assistant answering reader questions about the following article. Provide concise, accurate answers.',
8+
const systemPrompt = [
9+
'You are a helpful AI assistant answering reader questions about the following article. Provide concise, accurate answers based on the article content.',
1010
'',
1111
`Title: ${title}`,
1212
`Author: ${author}`,
1313
`Date: ${date}`,
1414
'',
15+
'Article content:',
1516
context,
16-
...messages.map((m: any) => `${m.role}: ${m.content}`),
17-
'assistant:'
1817
].join('\n');
1918

2019
const { fullStream } = streamText({
2120
model: myProvider.languageModel(DEFAULT_CHAT_MODEL),
22-
prompt,
21+
system: systemPrompt,
22+
messages: messages.map((m: any) => ({
23+
role: m.role,
24+
content: m.content,
25+
})),
2326
experimental_transform: smoothStream({ chunking: 'word' }),
2427
});
2528

@@ -28,7 +31,7 @@ export async function POST(request: Request) {
2831
async start(controller) {
2932
for await (const delta of fullStream) {
3033
if (delta.type === 'text-delta') {
31-
controller.enqueue(new TextEncoder().encode(delta.text));
34+
controller.enqueue(new TextEncoder().encode(delta.textDelta));
3235
}
3336
}
3437
controller.close();

apps/saru/app/api/chat/route.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async function createEnhancedSystemPrompt({
4646
customInstructions,
4747
writingStyleSummary,
4848
applyStyle,
49+
userId,
4950
availableTools = ['streamingDocument','updateDocument','webSearch'] as Array<'streamingDocument'|'updateDocument'|'webSearch'>,
5051
}: {
5152
selectedChatModel: string;
@@ -54,11 +55,11 @@ async function createEnhancedSystemPrompt({
5455
customInstructions?: string | null;
5556
writingStyleSummary?: string | null;
5657
applyStyle?: boolean;
58+
userId: string;
5759
availableTools?: Array<'streamingDocument'|'updateDocument'|'webSearch'>;
5860
}) {
5961

6062
let basePrompt = systemPrompt({ selectedChatModel, availableTools });
61-
let contextAdded = false;
6263

6364
if (customInstructions) {
6465
basePrompt = customInstructions + "\n\n" + basePrompt;
@@ -72,17 +73,17 @@ async function createEnhancedSystemPrompt({
7273
if (activeDocumentId) {
7374
try {
7475
const document = await getDocumentById({ id: activeDocumentId });
75-
if (document) {
76+
if (document && document.userId === userId) {
7677
const documentContext = `
7778
CURRENT DOCUMENT:
7879
Title: ${document.title}
7980
Content:
8081
${document.content || '(Empty document)'}
8182
`;
8283
basePrompt += `\n\n${documentContext}`;
83-
contextAdded = true;
8484
}
8585
} catch (error) {
86+
console.error(`[Chat] Failed to load active document ${activeDocumentId}:`, error);
8687
}
8788
}
8889

@@ -93,17 +94,17 @@ ${document.content || '(Empty document)'}
9394

9495
try {
9596
const document = await getDocumentById({ id: mentionedId });
96-
if (document) {
97+
if (document && document.userId === userId) {
9798
const mentionedContext = `
9899
MENTIONED DOCUMENT:
99100
Title: ${document.title}
100101
Content:
101102
${document.content || '(Empty document)'}
102103
`;
103104
basePrompt += `\n${mentionedContext}`;
104-
contextAdded = true;
105105
}
106106
} catch (error) {
107+
console.error(`[Chat] Failed to load mentioned document ${mentionedId}:`, error);
107108
}
108109
}
109110
basePrompt += `\n--- END MENTIONED DOCUMENTS ---`;
@@ -335,6 +336,7 @@ export async function POST(request: Request) {
335336
customInstructions,
336337
writingStyleSummary,
337338
applyStyle,
339+
userId,
338340
availableTools: activeToolsList,
339341
});
340342

@@ -386,7 +388,7 @@ export async function POST(request: Request) {
386388
chatId,
387389
userId,
388390
context: {
389-
active: activeDocumentId || undefined,
391+
active: validatedActiveDocumentId,
390392
mentioned: mentionedDocumentIds,
391393
},
392394
});

apps/saru/components/chat/chat.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -243,20 +243,6 @@ export function Chat({
243243
};
244244
}, [chatId]);
245245

246-
useEffect(() => {
247-
const handleChatIdChanged = (event: CustomEvent<{ oldChatId: string, newChatId: string }>) => {
248-
const { oldChatId, newChatId } = event.detail;
249-
250-
if (oldChatId === chatId) {
251-
}
252-
};
253-
254-
window.addEventListener('chat-id-changed', handleChatIdChanged as unknown as EventListener);
255-
256-
return () => {
257-
window.removeEventListener('chat-id-changed', handleChatIdChanged as unknown as EventListener);
258-
};
259-
}, [chatId]);
260246

261247
useEffect(() => {
262248
const handleReset = () => {

apps/saru/components/chat/message.tsx

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,10 @@ import { useDocument } from "@/hooks/use-document";
2222
function formatMessageWithMentions(content: string) {
2323
if (!content) return content;
2424

25-
const mentionRegex = /@([a-zA-Z0-9\s_-]+)/g;
26-
27-
const parts = content.split(mentionRegex);
28-
29-
if (parts.length <= 1) return content;
30-
3125
const formattedContent = [];
32-
let i = 0;
33-
3426
let match;
3527
let lastIndex = 0;
36-
const regex = new RegExp(mentionRegex);
28+
const regex = /@([a-zA-Z0-9\s_-]+)/g;
3729

3830
while ((match = regex.exec(content)) !== null) {
3931
if (match.index > lastIndex) {
@@ -84,7 +76,6 @@ const PurePreviewMessage = ({
8476
const { document } = useDocument();
8577
const dispatchedKeysRef = useRef<Set<string>>(new Set());
8678
const [mode, setMode] = useState<'view' | 'edit'>('view');
87-
console.log("[PreviewMessage] Rendering message:", message);
8879

8980
const reasoningPart = message.parts?.find(
9081
(part) => part.type === "reasoning"
@@ -97,8 +88,6 @@ const PurePreviewMessage = ({
9788
const toolParts =
9889
message.parts?.filter((part) => part.type?.startsWith("tool-")) || [];
9990

100-
console.log("[PreviewMessage] Tool parts found:", toolParts);
101-
10291
return (
10392
<AnimatePresence>
10493
<motion.div

apps/saru/components/chat/multimodal-input.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,10 @@ function PureMultimodalInput({
147147
const { width } = useWindowSize();
148148
const { document: currentDoc } = useDocument();
149149

150-
const [fileSuggestions, setFileSuggestions] = useState<DocumentSuggestion[]>([]);
151-
const [isSuggestionLoading, setIsSuggestionLoading] = useState(false);
152-
const [currentMentionValue, setCurrentMentionValue] = useState('');
153150

154151
const [inputValue, setInputValue] = useState(input);
155152
const [markupValue, setMarkupValue] = useState('');
153+
const [plainTextValue, setPlainTextValue] = useState('');
156154
const { writingStyleSummary, applyStyle } = useAiOptionsValue();
157155

158156
const [localStorageInput, setLocalStorageInput] = useLocalStorage('input', '');
@@ -172,7 +170,7 @@ function PureMultimodalInput({
172170
}, [inputValue, markupValue, setInput, setLocalStorageInput, onMentionsChange]);
173171

174172
const parseMentionsFromMarkup = (markup: string): MentionedDocument[] => {
175-
const mentionRegex = /@\[([^)]+)\]\\((\\S+)\\)/g;
173+
const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
176174
const mentions: MentionedDocument[] = [];
177175
let match;
178176
while ((match = mentionRegex.exec(markup)) !== null) {
@@ -188,7 +186,8 @@ function PureMultimodalInput({
188186
mentions: Array<{ id: string; display: string }>
189187
) => {
190188
setInputValue(newValue);
191-
setMarkupValue(newPlainTextValue);
189+
setMarkupValue(newValue);
190+
setPlainTextValue(newPlainTextValue);
192191
};
193192

194193

@@ -205,7 +204,7 @@ function PureMultimodalInput({
205204
contextData.mentionedDocumentIds = confirmedMentions.map(doc => doc.id);
206205
}
207206

208-
const parts: any[] = [{ type: 'text', text: inputValue }];
207+
const parts: any[] = [{ type: 'text', text: plainTextValue || inputValue }];
209208

210209
const requestBody = {
211210
chatId: chatId,
@@ -225,6 +224,7 @@ function PureMultimodalInput({
225224

226225
setInputValue('');
227226
setMarkupValue('');
227+
setPlainTextValue('');
228228
setInput('');
229229
onMentionsChange([]);
230230

@@ -233,6 +233,7 @@ function PureMultimodalInput({
233233
}
234234
}, [
235235
inputValue,
236+
plainTextValue,
236237
currentDoc.documentId,
237238
confirmedMentions,
238239
sendMessage,
@@ -246,27 +247,24 @@ function PureMultimodalInput({
246247
callback: (data: SuggestionDataItem[]) => void
247248
) => {
248249
if (!query) {
249-
setFileSuggestions([]);
250250
callback([]);
251251
return;
252252
}
253-
setIsSuggestionLoading(true);
254253
fetch(`/api/search?query=${encodeURIComponent(query)}`)
255-
.then(response => response.json())
254+
.then(response => {
255+
if (!response.ok) throw new Error(response.statusText);
256+
return response.json();
257+
})
256258
.then(data => {
257259
const suggestions = (data.results || []).map((doc: any) => ({
258260
id: doc.id,
259261
display: doc.title,
260262
}));
261-
setFileSuggestions(suggestions);
262263
callback(suggestions);
263264
})
264265
.catch(error => {
265266
console.error('Error searching documents:', error);
266267
callback([]);
267-
})
268-
.finally(() => {
269-
setIsSuggestionLoading(false);
270268
});
271269
};
272270

apps/saru/components/suggestion-overlay-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export function SuggestionOverlayProvider({ children }: { children: ReactNode })
144144
const { from, to, empty } = state.selection;
145145

146146
if (!empty) {
147-
const text = state.doc.textBetween(from, to, ' \\n\\n ');
147+
const text = state.doc.textBetween(from, to, ' \n\n ');
148148

149149
let pos = { x: 100, y: 100 }; // Default position
150150
const domSelection = window.getSelection();

0 commit comments

Comments
 (0)