Skip to content

Commit a88eb81

Browse files
committed
feature: Additional AI context blcoks
1 parent c85bd07 commit a88eb81

File tree

5 files changed

+277
-179
lines changed

5 files changed

+277
-179
lines changed

clients/trieve-shopify-extension/extensions/admin-block-pdp-questions/src/BlockExtension.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
TextField,
1515
} from "@shopify/ui-extensions-react/admin";
1616
import { SuggestedQueriesResponse, TrieveSDK } from "trieve-ts-sdk";
17+
import React from "react";
1718

1819
export type TrieveKey = {
1920
id?: string;

clients/trieve-shopify-extension/extensions/enrich-content-block/src/BlockExtension.tsx

Lines changed: 268 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import React, { useEffect, useState } from "react";
22
import {
33
reactExtension,
44
BlockStack,
@@ -10,9 +10,10 @@ import {
1010
Banner,
1111
TextArea,
1212
Icon,
13+
Box,
1314
} from "@shopify/ui-extensions-react/admin";
14-
import { TrieveProvider } from "./TrieveProvider";
15-
import { useChunkExtraContent } from "./useChunkExtraContent";
15+
import { TrieveProvider, useTrieve } from "./TrieveProvider";
16+
import { ChunkMetadata } from "trieve-ts-sdk";
1617

1718
const TARGET = "admin.product-details.block.render";
1819

@@ -32,69 +33,297 @@ export default reactExtension(TARGET, () => (
3233

3334
function App() {
3435
const { data } = useApi(TARGET);
35-
const productId = data.selected[0].id;
36-
const simplifiedProductId = extractShopifyProductId(productId);
37-
const [content, setContent] = useState("");
38-
const [isSaving, setIsSaving] = useState(false);
36+
const productId = extractShopifyProductId(data.selected[0].id);
37+
const [content, setContent] = useState<ChunkMetadata[]>([]);
38+
const [currentPage, setCurrentPage] = useState(1);
3939
const [showSuccess, setShowSuccess] = useState(false);
40+
const trieve = useTrieve();
41+
const [extraContent, setExtraContent] = useState<ChunkMetadata[]>([]);
42+
const [aiLoading, setAILoading] = useState(false);
4043

41-
const {
42-
extraContent,
43-
updateContent,
44-
generateAIDescription,
45-
loading,
46-
aiLoading,
47-
} = useChunkExtraContent(simplifiedProductId);
44+
// Fetch current product extra content chunk
45+
const getData = async () => {
46+
if (!productId) {
47+
console.info("Tried to fetch product content without id");
48+
return;
49+
}
50+
try {
51+
const result = await trieve.scroll({
52+
filters: {
53+
"should": [
54+
{
55+
"field": "tag_set",
56+
"match_all": [
57+
`${productId}-pdp-content`
58+
],
59+
},
60+
{
61+
"tracking_ids": [
62+
`${productId}-pdp-content`
63+
]
64+
}
65+
]
66+
},
67+
page_size: 20
68+
});
69+
if (!result) {
70+
setExtraContent([]);
71+
return;
72+
}
73+
setExtraContent(result.chunks);
74+
} catch {
75+
setExtraContent([]);
76+
}
77+
};
78+
79+
const generateAIDescription = async () => {
80+
if (!productId) {
81+
console.info("Tried to generate AI description without id");
82+
return;
83+
}
84+
setAILoading(true);
85+
const topic = await trieve.createTopic({
86+
owner_id: "shopify-enrich-content-block",
87+
first_user_message: "Describe this product",
88+
name: "Shopify Enrich Content Block",
89+
});
90+
91+
const message = await trieve.createMessage({
92+
topic_id: topic.id,
93+
new_message_content:
94+
"Describe this product to add extra context to an LLM. Generate a description for an online shop. Keep it to 3 sentences maximum. Do not include an introduction or welcome message",
95+
use_group_search: true,
96+
filters: {
97+
must: [
98+
{
99+
field: "group_tracking_ids",
100+
match_all: [productId],
101+
},
102+
],
103+
},
104+
llm_options: {
105+
stream_response: false,
106+
},
107+
});
108+
109+
const response = message.split("||").at(1);
110+
if (!response) {
111+
console.error("No response from AI");
112+
return;
113+
}
114+
115+
const chunk = await trieve.createChunk({
116+
chunk_html: response,
117+
tag_set: [`${productId}-pdp-content`],
118+
group_tracking_ids: [productId],
119+
});
120+
// Check if chunk.chunk_metadata is a list
121+
if (!Array.isArray(chunk.chunk_metadata)) {
122+
setExtraContent((prev) => {
123+
return [
124+
chunk.chunk_metadata,
125+
...prev,
126+
]
127+
});
128+
setShowSuccess(true);
129+
}
130+
setAILoading(false);
131+
};
132+
133+
useEffect(() => {
134+
if (!productId) {
135+
return;
136+
}
137+
getData();
138+
}, [productId]);
139+
140+
const [indexBeingEdited, setIndexBeingEdited] = useState<number | null>(null);
48141

49142
useEffect(() => {
50143
if (extraContent) {
51144
setContent(extraContent);
52145
}
53146
}, [extraContent]);
54147

55-
const handleSave = async () => {
56-
setIsSaving(true);
57-
try {
58-
await updateContent(content);
59-
setShowSuccess(true);
60-
} finally {
61-
setIsSaving(false);
148+
149+
const upsertContent = (chunk: ChunkMetadata) => {
150+
setIndexBeingEdited(null);
151+
if (chunk.id != "") {
152+
trieve.updateChunk({
153+
chunk_id: chunk.id,
154+
chunk_html: chunk.chunk_html
155+
})
156+
} else if (productId) {
157+
trieve.createChunk({
158+
chunk_html: chunk.chunk_html,
159+
tag_set: [`${productId}-pdp-content`],
160+
group_tracking_ids: [productId],
161+
})
62162
}
63163
};
64164

65165
return (
66-
<AdminBlock title="Enrich Content">
166+
<AdminBlock title="AI Context">
67167
<BlockStack gap="base">
68168
{showSuccess && (
69169
<Banner tone="success" onDismiss={() => setShowSuccess(false)}>
70170
Content saved successfully
71171
</Banner>
72172
)}
73-
<BlockStack>
74-
<InlineStack inlineAlignment="space-between" blockAlignment="end">
75-
<Text>Extra Content</Text>
173+
<InlineStack inlineAlignment="space-between" blockAlignment="center">
174+
<Box
175+
inlineSize="80%">
176+
<Text>Product context for the AI</Text>
177+
</Box>
178+
<InlineStack
179+
blockAlignment="center"
180+
inlineAlignment="end"
181+
gap="base base"
182+
>
76183
<Button
77184
disabled={aiLoading}
78-
variant="tertiary"
79185
onPress={generateAIDescription}
80186
>
81-
<InlineStack blockAlignment="center" gap="small small">
187+
<InlineStack blockAlignment="center">
82188
<Icon name="WandMinor" />
83-
{aiLoading ? "Generating..." : "Generate AI Description"}
189+
{aiLoading ? "Generating..." : "Generate AI Context"}
190+
</InlineStack>
191+
</Button>
192+
193+
<Button
194+
onPress={() => {
195+
setExtraContent((prev) => [
196+
{
197+
id: "",
198+
chunk_html: "",
199+
tag_set: [`${productId}-pdp-content`],
200+
group_tracking_ids: [productId],
201+
created_at: "",
202+
updated_at: "",
203+
dataset_id: "",
204+
weight: 1 // Just to make the lsp stop
205+
},
206+
...prev,
207+
]);
208+
setIndexBeingEdited(0);
209+
setCurrentPage(1);
210+
}}
211+
>
212+
<InlineStack blockAlignment="center">
213+
<Icon name="PlusMinor" />
214+
Add Context
84215
</InlineStack>
85216
</Button>
86217
</InlineStack>
87-
<TextArea
88-
rows={4}
89-
disabled={loading}
90-
label=""
91-
value={content}
92-
onChange={setContent}
93-
/>
94-
</BlockStack>
95-
<InlineStack gap="base" inlineAlignment="end">
96-
<Button onPress={handleSave} disabled={isSaving}>
97-
{isSaving ? "Saving..." : "Save Content"}
218+
</InlineStack>
219+
<Box>
220+
{content.map((chunk, index) => {
221+
if (index != (currentPage - 1)) {
222+
return null;
223+
}
224+
225+
return (
226+
<Box key={index} padding="base small">
227+
<InlineStack
228+
blockAlignment="center"
229+
inlineAlignment="space-between"
230+
inlineSize="100%"
231+
gap="large"
232+
>
233+
<Box
234+
inlineSize={`${index === indexBeingEdited ? "100%" : "75%"}`}
235+
>
236+
{index === indexBeingEdited ? (
237+
<TextArea
238+
rows={4}
239+
label=""
240+
value={chunk.chunk_html ?? ""}
241+
onChange={(value) => {
242+
// updateContent(index, value);
243+
setContent((prevContent) => prevContent.map((prevChunk) => prevChunk.id == chunk.id
244+
? { ...prevChunk, chunk_html: value }
245+
: prevChunk
246+
))
247+
}}
248+
/>
249+
) : (
250+
<Text>{chunk.chunk_html}</Text>
251+
)}
252+
</Box>
253+
<Box inlineSize="25%">
254+
<InlineStack
255+
inlineSize="100%"
256+
inlineAlignment="end"
257+
blockAlignment="center"
258+
>
259+
{index === indexBeingEdited ? (
260+
<>
261+
<Button
262+
onClick={() => {
263+
upsertContent(chunk);
264+
}}
265+
variant="primary"
266+
>
267+
<Text>Finish</Text>
268+
</Button>
269+
</>
270+
) : (
271+
<>
272+
<Button
273+
onClick={() => {
274+
setIndexBeingEdited(index);
275+
}}
276+
variant="tertiary"
277+
>
278+
<Icon name="EditMinor" />
279+
</Button>
280+
<Button
281+
onClick={() => {
282+
trieve.deleteChunkById({
283+
chunkId: chunk.id
284+
});
285+
setContent((prevContent) => prevContent.filter((prevChunk) => prevChunk.id != chunk.id
286+
))
287+
if (index === content.length - 1) {
288+
setCurrentPage((prev) => prev - 1);
289+
}
290+
}}
291+
variant="tertiary"
292+
>
293+
<Icon name="DeleteMinor" />
294+
</Button>
295+
</>
296+
)}
297+
</InlineStack>
298+
</Box>
299+
</InlineStack>
300+
</Box>
301+
);
302+
})}
303+
</Box>
304+
<InlineStack
305+
paddingBlockStart="large"
306+
blockAlignment="center"
307+
inlineAlignment="center"
308+
>
309+
<Button
310+
onPress={() => setCurrentPage((prev) => prev - 1)}
311+
disabled={currentPage === 1}
312+
>
313+
<Icon name="ChevronLeftMinor" />
314+
</Button>
315+
<InlineStack
316+
inlineSize={50}
317+
blockAlignment="center"
318+
inlineAlignment="center"
319+
>
320+
<Text>{currentPage} / {content.length}</Text>
321+
</InlineStack>
322+
<Button
323+
onPress={() => setCurrentPage((prev) => prev + 1)}
324+
disabled={currentPage >= content.length}
325+
>
326+
<Icon name="ChevronRightMinor" />
98327
</Button>
99328
</InlineStack>
100329
</BlockStack>

0 commit comments

Comments
 (0)