Skip to content

Commit 6598a0e

Browse files
committed
Move most AI logic to new chunk #951
1 parent 7a8cb91 commit 6598a0e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+413
-384
lines changed

browser/data-browser/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"ollama-ai-provider": "^1.2.0",
4545
"polished": "^4.3.1",
4646
"prismjs": "^1.29.0",
47-
"query-string": "^7.1.3",
4847
"quick-score": "^0.2.0",
4948
"react": "^19.0.0",
5049
"react-colorful": "^5.6.1",

browser/data-browser/src/components/AI/AIChatMessage.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import styled from 'styled-components';
22
import { FaRetweet, FaTrash } from 'react-icons/fa6';
33
import { type AtomicUIMessage } from './types';
44
import { AssistantMessage } from './AIChatMessageParts/AssistantMessage';
5-
import { IconButton } from '../IconButton/IconButton';
5+
import { IconButton } from '@components/IconButton/IconButton';
66
import { UserMessage } from './AIChatMessageParts/UserMessage';
77

88
interface MessageProps {

browser/data-browser/src/components/AI/AIChatMessageParts/BasicMessage.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessageParts/BasicMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Markdown from '../../datatypes/Markdown';
1+
import Markdown from '@components/datatypes/Markdown';
22
import styled from 'styled-components';
33

44
const MessageWrapper = styled.div`

browser/data-browser/src/components/AI/AIChatMessageParts/FileContent.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessageParts/FileContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FileUIPart, ImagePart } from 'ai';
22
import { FaFile } from 'react-icons/fa6';
33
import { styled } from 'styled-components';
4-
import { ImageViewer } from '../../ImageViewer';
4+
import { ImageViewer } from '@components/ImageViewer';
55
import { useId } from 'react';
66

77
export const FileContent = ({ part }: { part: FileUIPart }) => {

browser/data-browser/src/components/AI/AIChatMessageParts/MessageToolPart.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessageParts/MessageToolPart.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { getToolName, type ToolUIPart } from 'ai';
22
import { styled } from 'styled-components';
3-
import { Row } from '../../Row';
3+
import { Row } from '@components/Row';
44
import {
55
FaAtom,
66
FaBook,
77
FaMagnifyingGlass,
88
FaPencil,
99
FaWrench,
1010
} from 'react-icons/fa6';
11-
import { Details } from '../../Details';
11+
import { Details } from '@components/Details';
1212
import { TOOL_NAMES } from '../useAtomicTools';
13-
import { InlineFormattedResourceList } from '../../InlineFormattedResourceList';
13+
import { InlineFormattedResourceList } from '@components/InlineFormattedResourceList';
1414
import { useResource } from '@tomic/react';
1515

1616
interface ToolMessageProps {

browser/data-browser/src/components/AI/AIChatMessageParts/ReasoningMessage.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessageParts/ReasoningMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import Markdown from '../../datatypes/Markdown';
1+
import Markdown from '@components/datatypes/Markdown';
22
import styled from 'styled-components';
3-
import { Details } from '../../Details';
3+
import { Details } from '@components/Details';
44

55
const ReasoningMessageWrapper = styled.div`
66
padding: ${p => p.theme.size()};

browser/data-browser/src/components/AI/AIChatMessageParts/UserMessage.tsx renamed to browser/data-browser/src/chunks/AI/AIChatMessageParts/UserMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type AtomicUIMessage } from '../types';
22
import { styled } from 'styled-components';
3-
import Markdown from '../../datatypes/Markdown';
4-
import { Row } from '../../Row';
3+
import Markdown from '@components/datatypes/Markdown';
4+
import { Row } from '@components/Row';
55
import { MessageContextItem } from '../MessageContextItem';
66
import { FileContent } from './FileContent';
77

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { useEffect, useState } from 'react';
2+
import {
3+
Ai,
4+
ai,
5+
useArray,
6+
useCanWrite,
7+
useStore,
8+
useTitle,
9+
type Resource,
10+
} from '@tomic/react';
11+
import type { ResourcePageProps } from '@views/ResourcePage';
12+
import toast from 'react-hot-toast';
13+
import { type AIMessageContext, type AtomicUIMessage } from './types';
14+
import { Column, Row } from '@components/Row';
15+
import { EditableTitle } from '@components/EditableTitle';
16+
import { DEFAULT_AICHAT_NAME } from '@components/AI/aiContstants';
17+
import { useGenerativeData } from './useGenerativeData';
18+
import {
19+
uiMessageToResource,
20+
messageResourcesToDisplayMessages,
21+
} from './chatConversionUtils';
22+
import { TagBar } from '@components/Tag/TagBar';
23+
import { RealAIChat } from './RealAIChat';
24+
25+
const AIChatPage: React.FC<ResourcePageProps<Ai.AiChat>> = ({ resource }) => {
26+
const store = useStore();
27+
const [loading, setLoading] = useState(true);
28+
const [messages, setMessages] = useState<AtomicUIMessage[]>([]);
29+
const [contextItems, setContextItems] = useState<AIMessageContext[]>([]);
30+
const [messageSubjects, setMessageSubjects] = useArray(
31+
resource,
32+
ai.properties.messages,
33+
{
34+
commit: true,
35+
},
36+
);
37+
const [messageToResourceMap, setMessageToResourceMap] = useState(
38+
new Map<AtomicUIMessage, Resource>(),
39+
);
40+
const [title, setTitle] = useTitle(resource);
41+
42+
const canWrite = useCanWrite(resource);
43+
const { generateTitleFromConversation } = useGenerativeData();
44+
45+
const addNewMessage = async (message: AtomicUIMessage) => {
46+
setMessages(prev => [...prev, message]);
47+
48+
try {
49+
const messageResource = await uiMessageToResource(
50+
message,
51+
resource,
52+
store,
53+
);
54+
55+
resource.push(ai.properties.messages, [messageResource.subject]);
56+
57+
await resource.save();
58+
59+
setMessageToResourceMap(prev => {
60+
prev.set(message, messageResource);
61+
62+
return prev;
63+
});
64+
} catch (error) {
65+
console.error(error);
66+
toast.error('Failed to create message resource');
67+
}
68+
};
69+
70+
const handleDeleteMessage = (message: AtomicUIMessage) => {
71+
const messageResource = messageToResourceMap.get(message);
72+
73+
if (messageResource) {
74+
setMessageSubjects(
75+
messageSubjects.filter(s => s !== messageResource.subject),
76+
);
77+
messageResource.destroy();
78+
}
79+
80+
setMessages(prev => prev.filter(m => m !== message));
81+
82+
setMessageToResourceMap(prev => {
83+
prev.delete(message);
84+
85+
return prev;
86+
});
87+
};
88+
89+
const removeFollowingMessages = async (message: AtomicUIMessage) => {
90+
const nextMessages = messages.slice(
91+
messages.findIndex(x => x.id === message.id) + 1,
92+
);
93+
94+
// We need to destroy the resources server side as well as in the internal state.
95+
// We also need to update the `messages` prop in the chat resource.
96+
const destroySubjects: string[] = [];
97+
98+
for (const m of nextMessages) {
99+
const r = messageToResourceMap.get(m);
100+
101+
if (r) {
102+
destroySubjects.push(r.subject);
103+
104+
try {
105+
await r.destroy();
106+
} catch (error) {
107+
console.error('Error removing message:', error);
108+
}
109+
} else {
110+
throw new Error(`Resource not found for message: ${m.id}`);
111+
}
112+
}
113+
114+
try {
115+
// Set chat resource on server with new message array
116+
await resource.set(
117+
ai.properties.messages,
118+
resource.props.messages?.filter(x => !destroySubjects.includes(x)),
119+
);
120+
await resource.save();
121+
// Set internal message state
122+
setMessages(prev => {
123+
const newMessages = prev.slice(
124+
0,
125+
prev.findIndex(x => x.id === message.id) + 1,
126+
);
127+
128+
return newMessages;
129+
});
130+
} catch (error) {
131+
console.error('Error removing messages:', error);
132+
}
133+
};
134+
135+
// On load create AIChatDisplayMessages from the resource's messages.
136+
useEffect(() => {
137+
messageResourcesToDisplayMessages(messageSubjects, store).then(map => {
138+
setMessages(Array.from(map.keys()));
139+
setMessageToResourceMap(map);
140+
setLoading(false);
141+
});
142+
}, []);
143+
144+
// When there are only two messages and the title is still the default name, generate a title from the conversation.
145+
useEffect(() => {
146+
if (messages.length === 2 && title === DEFAULT_AICHAT_NAME) {
147+
generateTitleFromConversation(messages).then(setTitle);
148+
}
149+
}, [messages, title]);
150+
151+
if (loading) {
152+
return <div>Loading...</div>;
153+
}
154+
155+
return (
156+
<RealAIChat
157+
fullView
158+
initialMessages={messages}
159+
readonly={!canWrite}
160+
externalContextItems={contextItems}
161+
setExternalContextItems={setContextItems}
162+
chatSubject={resource.subject}
163+
onNewMessage={addNewMessage}
164+
onDeleteMessage={handleDeleteMessage}
165+
onRegenerateMessage={removeFollowingMessages}
166+
>
167+
<Column gap='0.5rem'>
168+
<Row>
169+
<EditableTitle resource={resource} />
170+
</Row>
171+
<TagBar resource={resource} />
172+
</Column>
173+
</RealAIChat>
174+
);
175+
};
176+
177+
export default AIChatPage;

0 commit comments

Comments
 (0)