Update OptionLayout component and add OptionPrompt route
This commit is contained in:
parent
4365580f5d
commit
88e7e7521b
@ -7,8 +7,14 @@ import { useQuery } from "@tanstack/react-query"
|
|||||||
import { getAllModels } from "~services/ollama"
|
import { getAllModels } from "~services/ollama"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~hooks/useMessageOption"
|
||||||
import { Settings } from "./Settings"
|
import { Settings } from "./Settings"
|
||||||
import { BrainCircuit, ChevronLeft, CogIcon, GithubIcon, PanelLeftIcon, SquarePen } from "lucide-react"
|
import {
|
||||||
|
Book,
|
||||||
|
BrainCircuit,
|
||||||
|
ChevronLeft,
|
||||||
|
CogIcon,
|
||||||
|
PanelLeftIcon,
|
||||||
|
SquarePen
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
export default function OptionLayout({
|
export default function OptionLayout({
|
||||||
children
|
children
|
||||||
@ -83,14 +89,21 @@ export default function OptionLayout({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<Tooltip title="Github Repository">
|
<Tooltip title="Manage Prompts">
|
||||||
|
<NavLink
|
||||||
|
to="/prompts"
|
||||||
|
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<Book className="w-6 h-6" />
|
||||||
|
</NavLink>
|
||||||
|
</Tooltip>
|
||||||
|
{/* <Tooltip title="Github Repository">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/n4ze3m/page-assist"
|
href="https://github.com/n4ze3m/page-assist"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
<GithubIcon className="w-6 h-6" />
|
<GithubIcon className="w-6 h-6" />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip> */}
|
||||||
<Tooltip title="Manage Ollama Models">
|
<Tooltip title="Manage Ollama Models">
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/models"
|
to="/models"
|
||||||
|
@ -75,7 +75,7 @@ export const ModelsBody = () => {
|
|||||||
<div className="ml-4 mt-2 flex-shrink-0">
|
<div className="ml-4 mt-2 flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="inline-flex items-center rounded-md border border-transparent bg-black px-3 py-3 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50">
|
className="inline-flex items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50">
|
||||||
Add New Model
|
Add New Model
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
273
src/components/Option/Prompt/index.tsx
Normal file
273
src/components/Option/Prompt/index.tsx
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
|
import {
|
||||||
|
Skeleton,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Tooltip,
|
||||||
|
notification,
|
||||||
|
Modal,
|
||||||
|
Input,
|
||||||
|
Form,
|
||||||
|
Switch
|
||||||
|
} from "antd"
|
||||||
|
import { Trash2, Pen } from "lucide-react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import {
|
||||||
|
deletePromptById,
|
||||||
|
getAllPrompts,
|
||||||
|
savePrompt,
|
||||||
|
updatePrompt
|
||||||
|
} from "~libs/db"
|
||||||
|
|
||||||
|
export const PromptBody = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [openEdit, setOpenEdit] = useState(false)
|
||||||
|
const [editId, setEditId] = useState("")
|
||||||
|
const [createForm] = Form.useForm()
|
||||||
|
const [editForm] = Form.useForm()
|
||||||
|
|
||||||
|
const { data, status } = useQuery({
|
||||||
|
queryKey: ["fetchAllPrompts"],
|
||||||
|
queryFn: getAllPrompts
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutate: deletePrompt } = useMutation({
|
||||||
|
mutationFn: deletePromptById,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["fetchAllPrompts"]
|
||||||
|
})
|
||||||
|
notification.success({
|
||||||
|
message: "Model Deleted",
|
||||||
|
description: "Model has been deleted successfully"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: error?.message || "Something went wrong"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutate: savePromptMutation, isPending: savePromptLoading } =
|
||||||
|
useMutation({
|
||||||
|
mutationFn: savePrompt,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["fetchAllPrompts"]
|
||||||
|
})
|
||||||
|
setOpen(false)
|
||||||
|
createForm.resetFields()
|
||||||
|
notification.success({
|
||||||
|
message: "Prompt Added",
|
||||||
|
description: "Prompt has been added successfully"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: error?.message || "Something went wrong"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutate: updatePromptMutation, isPending: isUpdatingPrompt } =
|
||||||
|
useMutation({
|
||||||
|
mutationFn: async (data: any) => {
|
||||||
|
return await updatePrompt({
|
||||||
|
...data,
|
||||||
|
id: editId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["fetchAllPrompts"]
|
||||||
|
})
|
||||||
|
setOpenEdit(false)
|
||||||
|
editForm.resetFields()
|
||||||
|
notification.success({
|
||||||
|
message: "Prompt Updated",
|
||||||
|
description: "Prompt has been updated successfully"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: error?.message || "Something went wrong"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="z-10 min-h-screen">
|
||||||
|
<div className="mt-16 mx-auto py-10 max-w-7xl px-3 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="-ml-4 -mt-2 flex flex-wrap items-center justify-end sm:flex-nowrap">
|
||||||
|
<div className="ml-4 mt-2 flex-shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="inline-flex items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50">
|
||||||
|
Add New Prompt
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status === "pending" && <Skeleton paragraph={{ rows: 8 }} />}
|
||||||
|
|
||||||
|
{status === "success" && (
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Title",
|
||||||
|
dataIndex: "title",
|
||||||
|
key: "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Prompt",
|
||||||
|
dataIndex: "content",
|
||||||
|
key: "content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Is System Prompt",
|
||||||
|
dataIndex: "is_system",
|
||||||
|
key: "is_system",
|
||||||
|
render: (is_system) => (
|
||||||
|
<Tag color={is_system ? "green" : "blue"}>
|
||||||
|
{is_system ? "Yes" : "No"}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Action",
|
||||||
|
render: (_, record) => (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Tooltip title="Delete Prompt">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
window.confirm(
|
||||||
|
"Are you sure you want to delete this prompt? This action cannot be undone."
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
deletePrompt(record.id)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-red-500 dark:text-red-400">
|
||||||
|
<Trash2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Edit Prompt">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setEditId(record.id)
|
||||||
|
editForm.setFieldsValue(record)
|
||||||
|
setOpenEdit(true)
|
||||||
|
}}
|
||||||
|
className="text-gray-500 dark:text-gray-400">
|
||||||
|
<Pen className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
bordered
|
||||||
|
dataSource={data}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="Add New Prompt"
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
footer={null}>
|
||||||
|
<Form
|
||||||
|
onFinish={(values) => savePromptMutation(values)}
|
||||||
|
layout="vertical"
|
||||||
|
form={createForm}>
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
label="Title"
|
||||||
|
rules={[{ required: true, message: "Title is required" }]}>
|
||||||
|
<Input placeholder="My Awesome Prompt" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="content"
|
||||||
|
label="Prompt"
|
||||||
|
rules={[{ required: true, message: "Prompt is required" }]}
|
||||||
|
help="You can use {key} as variable in your prompt.">
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="Your prompt goes here..."
|
||||||
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="is_system"
|
||||||
|
label="Is System Prompt"
|
||||||
|
valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<button
|
||||||
|
disabled={savePromptLoading}
|
||||||
|
className="inline-flex justify-center w-full text-center mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
|
||||||
|
{savePromptLoading ? "Adding Prompt..." : "Add Prompt"}
|
||||||
|
</button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="Update Prompt"
|
||||||
|
open={openEdit}
|
||||||
|
onCancel={() => setOpenEdit(false)}
|
||||||
|
footer={null}>
|
||||||
|
<Form
|
||||||
|
onFinish={(values) => updatePromptMutation(values)}
|
||||||
|
layout="vertical"
|
||||||
|
form={editForm}>
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
label="Title"
|
||||||
|
rules={[{ required: true, message: "Title is required" }]}>
|
||||||
|
<Input placeholder="My Awesome Prompt" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="content"
|
||||||
|
label="Prompt"
|
||||||
|
rules={[{ required: true, message: "Prompt is required" }]}
|
||||||
|
help="You can use {key} as variable in your prompt.">
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="Your prompt goes here..."
|
||||||
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="is_system"
|
||||||
|
label="Is System Prompt"
|
||||||
|
valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<button
|
||||||
|
disabled={isUpdatingPrompt}
|
||||||
|
className="inline-flex justify-center w-full text-center mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
|
||||||
|
{isUpdatingPrompt ? "Updating Prompt..." : "Update Prompt"}
|
||||||
|
</button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -31,10 +31,22 @@ type Message = {
|
|||||||
createdAt: number
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Prompt = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
is_system: boolean
|
||||||
|
createdBy?: string
|
||||||
|
createdAt: number
|
||||||
|
}
|
||||||
|
|
||||||
type MessageHistory = Message[]
|
type MessageHistory = Message[]
|
||||||
|
|
||||||
type ChatHistory = HistoryInfo[]
|
type ChatHistory = HistoryInfo[]
|
||||||
|
|
||||||
|
type Prompts = Prompt[]
|
||||||
|
|
||||||
export class PageAssitDatabase {
|
export class PageAssitDatabase {
|
||||||
db: chrome.storage.StorageArea
|
db: chrome.storage.StorageArea
|
||||||
|
|
||||||
@ -102,8 +114,49 @@ export class PageAssitDatabase {
|
|||||||
async deleteMessage(history_id: string) {
|
async deleteMessage(history_id: string) {
|
||||||
await this.db.remove(history_id)
|
await this.db.remove(history_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getAllPrompts(): Promise<Prompts> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.db.get("prompts", (result) => {
|
||||||
|
resolve(result.prompts || [])
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addPrompt(prompt: Prompt) {
|
||||||
|
const prompts = await this.getAllPrompts()
|
||||||
|
const newPrompts = [prompt, ...prompts]
|
||||||
|
this.db.set({ prompts: newPrompts })
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePrompt(id: string) {
|
||||||
|
const prompts = await this.getAllPrompts()
|
||||||
|
const newPrompts = prompts.filter((prompt) => prompt.id !== id)
|
||||||
|
this.db.set({ prompts: newPrompts })
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePrompt(id: string, title: string, content: string, is_system: boolean) {
|
||||||
|
const prompts = await this.getAllPrompts()
|
||||||
|
const newPrompts = prompts.map((prompt) => {
|
||||||
|
if (prompt.id === id) {
|
||||||
|
prompt.title = title
|
||||||
|
prompt.content = content
|
||||||
|
prompt.is_system = is_system
|
||||||
|
}
|
||||||
|
return prompt
|
||||||
|
})
|
||||||
|
this.db.set({ prompts: newPrompts })
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPromptById(id: string) {
|
||||||
|
const prompts = await this.getAllPrompts()
|
||||||
|
return prompts.find((prompt) => prompt.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const generateID = () => {
|
const generateID = () => {
|
||||||
return "pa_xxxx-xxxx-xxx-xxxx".replace(/[x]/g, () => {
|
return "pa_xxxx-xxxx-xxx-xxxx".replace(/[x]/g, () => {
|
||||||
const r = Math.floor(Math.random() * 16)
|
const r = Math.floor(Math.random() * 16)
|
||||||
@ -188,3 +241,39 @@ export const removeMessageUsingHistoryId = async (history_id: string) => {
|
|||||||
const newChatHistory = chatHistory.slice(0, -1)
|
const newChatHistory = chatHistory.slice(0, -1)
|
||||||
await db.db.set({ [history_id]: newChatHistory })
|
await db.db.set({ [history_id]: newChatHistory })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getAllPrompts = async () => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
return await db.getAllPrompts()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const savePrompt = async ({ content, title, is_system = false }: { title: string, content: string, is_system: boolean }) => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
const id = generateID()
|
||||||
|
const createdAt = Date.now()
|
||||||
|
const prompt = { id, title, content, is_system, createdAt }
|
||||||
|
await db.addPrompt(prompt)
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const deletePromptById = async (id: string) => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
await db.deletePrompt(id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const updatePrompt = async ({ content, id, title, is_system }: { id: string, title: string, content: string, is_system: boolean }) => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
await db.updatePrompt(id, title, content, is_system)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getPromptById = async (id: string) => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
return await db.getPromptById(id)
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { useDarkMode } from "~hooks/useDarkmode"
|
|||||||
import { SidepanelSettings } from "./sidepanel-settings"
|
import { SidepanelSettings } from "./sidepanel-settings"
|
||||||
import { OptionIndex } from "./option-index"
|
import { OptionIndex } from "./option-index"
|
||||||
import { OptionModal } from "./option-model"
|
import { OptionModal } from "./option-model"
|
||||||
|
import { OptionPrompt } from "./option-prompt"
|
||||||
|
|
||||||
export const OptionRouting = () => {
|
export const OptionRouting = () => {
|
||||||
const { mode } = useDarkMode()
|
const { mode } = useDarkMode()
|
||||||
@ -13,6 +14,7 @@ export const OptionRouting = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<OptionIndex />} />
|
<Route path="/" element={<OptionIndex />} />
|
||||||
<Route path="/models" element={<OptionModal />} />
|
<Route path="/models" element={<OptionModal />} />
|
||||||
|
<Route path="/prompts" element={<OptionPrompt />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
10
src/routes/option-prompt.tsx
Normal file
10
src/routes/option-prompt.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import OptionLayout from "~components/Option/Layout"
|
||||||
|
import { PromptBody } from "~components/Option/Prompt"
|
||||||
|
|
||||||
|
export const OptionPrompt = () => {
|
||||||
|
return (
|
||||||
|
<OptionLayout>
|
||||||
|
<PromptBody />
|
||||||
|
</OptionLayout>
|
||||||
|
)
|
||||||
|
}
|
@ -40,7 +40,14 @@ export const localGoogleSearch = async (query: string) => {
|
|||||||
(result) => {
|
(result) => {
|
||||||
const title = result.querySelector("h3")?.textContent
|
const title = result.querySelector("h3")?.textContent
|
||||||
const link = result.querySelector("a")?.getAttribute("href")
|
const link = result.querySelector("a")?.getAttribute("href")
|
||||||
return { title, link }
|
let content = result.querySelector("div[data-sncf='2']")?.textContent
|
||||||
|
if(content === "") {
|
||||||
|
content = result.querySelector("div[data-sncf='1']")?.textContent
|
||||||
|
if(content === "") {
|
||||||
|
content = result.querySelector("div[data-sncf='3']")?.textContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { title, link, content }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const filteredSearchResults = searchResults
|
const filteredSearchResults = searchResults
|
||||||
|
Loading…
x
Reference in New Issue
Block a user