feat: add pin/unpin functionality to chat history

Adds pin/unpin functionality to the chat history sidebar, allowing users to keep important conversations readily accessible. This improves user experience and helps organize past interactions.

This feature includes:
- Pin/unpin buttons in the chat history sidebar.
- Updated database schema to include `is_pinned` field for chat history items.
- Localized translations for pin/unpin actions.
- Updated UI to display pinned items at the top of the list.
This commit is contained in:
n4ze3m 2024-10-26 21:10:28 +05:30
parent f01f9d0482
commit d5df8b5c5f
29 changed files with 326 additions and 113 deletions

View File

@ -41,6 +41,7 @@
"webSearch": "Søger på internettet", "webSearch": "Søger på internettet",
"regenerate": "Regenerer", "regenerate": "Regenerer",
"edit": "Ændre", "edit": "Ændre",
"delete": "Slet",
"saveAndSubmit": "Gem & Indsend", "saveAndSubmit": "Gem & Indsend",
"editMessage": { "editMessage": {
"placeholder": "Skriv en besked..." "placeholder": "Skriv en besked..."
@ -102,5 +103,14 @@
"custom": "Brugerdefineret" "custom": "Brugerdefineret"
}, },
"citations": "Citater", "citations": "Citater",
"downloadCode": "Download Kode" "downloadCode": "Download Kode",
"date": {
"pinned": "Fastgjort",
"today": "I dag",
"yesterday": "I går",
"last7Days": "Sidste 7 dage",
"older": "Ældre"
},
"pin": "Fastgør",
"unpin": "Frigør"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Navn" "name": "Navn"
}, },
"tooltip": {
"delete": "Slet"
},
"confirm": { "confirm": {
"delete": "Er du sikker på du vil slette denne viden?" "delete": "Er du sikker på du vil slette denne viden?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Web durchsuchen", "webSearch": "Web durchsuchen",
"regenerate": "Neu generieren", "regenerate": "Neu generieren",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"delete": "Löschen",
"saveAndSubmit": "Speichern & Absenden", "saveAndSubmit": "Speichern & Absenden",
"editMessage": { "editMessage": {
"placeholder": "Nachricht eingeben..." "placeholder": "Nachricht eingeben..."
@ -102,5 +103,14 @@
"custom": "Benutzerdefiniert" "custom": "Benutzerdefiniert"
}, },
"citations": "Zitate", "citations": "Zitate",
"downloadCode": "Code herunterladen" "downloadCode": "Code herunterladen",
"date": {
"pinned": "Angepinnt",
"today": "Heute",
"yesterday": "Gestern",
"last7Days": "Letzte 7 Tage",
"older": "Älter"
},
"pin": "Anheften",
"unpin": "Losheften"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Name" "name": "Name"
}, },
"tooltip": {
"delete": "Löschen"
},
"confirm": { "confirm": {
"delete": "Sind Sie sicher, dass Sie dieses Wissen löschen möchten?" "delete": "Sind Sie sicher, dass Sie dieses Wissen löschen möchten?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Searching the web", "webSearch": "Searching the web",
"regenerate": "Regenerate", "regenerate": "Regenerate",
"edit": "Edit", "edit": "Edit",
"delete": "Delete",
"saveAndSubmit": "Save & Submit", "saveAndSubmit": "Save & Submit",
"editMessage": { "editMessage": {
"placeholder": "Type a message..." "placeholder": "Type a message..."
@ -106,5 +107,14 @@
"ollama": "Ollama Models", "ollama": "Ollama Models",
"custom": "Custom Models" "custom": "Custom Models"
}, },
"downloadCode": "Download Code" "downloadCode": "Download Code",
"date": {
"pinned": "Pinned",
"today": "Today",
"yesterday": "Yesterday",
"last7Days": "Last 7 Days",
"older": "Older"
},
"pin": "Pin",
"unpin": "Unpin"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Name" "name": "Name"
}, },
"tooltip": {
"delete": "Delete"
},
"confirm": { "confirm": {
"delete": "Are you sure you want to delete this knowledge?" "delete": "Are you sure you want to delete this knowledge?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Buscando en la web", "webSearch": "Buscando en la web",
"regenerate": "Regenerar", "regenerate": "Regenerar",
"edit": "Editar", "edit": "Editar",
"delete": "Borrar",
"saveAndSubmit": "Guardar y Enviar", "saveAndSubmit": "Guardar y Enviar",
"editMessage": { "editMessage": {
"placeholder": "Ingresar un mensaje..." "placeholder": "Ingresar un mensaje..."
@ -101,5 +102,14 @@
"translate": "Traducir" "translate": "Traducir"
}, },
"citations": "Citas", "citations": "Citas",
"downloadCode": "Descargar Código" "downloadCode": "Descargar Código",
"date": {
"pinned": "Fijado",
"today": "Hoy",
"yesterday": "Ayer",
"last7Days": "Últimos 7 días",
"older": "Más antiguo"
},
"pin": "Fijar",
"unpin": "Desfijar"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Nombre" "name": "Nombre"
}, },
"tooltip": {
"delete": "Borrar"
},
"confirm": { "confirm": {
"delete": "¿Esta seguro que desea borrar este conocimiento?" "delete": "¿Esta seguro que desea borrar este conocimiento?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "جستجوی وب", "webSearch": "جستجوی وب",
"regenerate": "ایجاد مجدد", "regenerate": "ایجاد مجدد",
"edit": "ویرایش", "edit": "ویرایش",
"delete": "حذف",
"saveAndSubmit": "ذخیره و ارسال", "saveAndSubmit": "ذخیره و ارسال",
"editMessage": { "editMessage": {
"placeholder": "یک پیام وارد کنید..." "placeholder": "یک پیام وارد کنید..."
@ -95,5 +96,14 @@
"advanced": "تنظیمات بیشتر مدل" "advanced": "تنظیمات بیشتر مدل"
}, },
"citations": "منابع", "citations": "منابع",
"downloadCode": "دانلود کد" "downloadCode": "دانلود کد",
"date": {
"pinned": "پین شده",
"today": "امروز",
"yesterday": "دیروز",
"last7Days": "۷ روز گذشته",
"older": "قدیمی‌تر"
},
"pin": "پین کردن",
"unpin": "حذف پین"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "نام" "name": "نام"
}, },
"tooltip": {
"delete": "حذف"
},
"confirm": { "confirm": {
"delete": "آیا مطمئن هستید که می خواهید این دانش را حذف کنید؟" "delete": "آیا مطمئن هستید که می خواهید این دانش را حذف کنید؟"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Recherche sur le Web", "webSearch": "Recherche sur le Web",
"regenerate": "Régénérer", "regenerate": "Régénérer",
"edit": "Modifier", "edit": "Modifier",
"delete": "Supprimer",
"saveAndSubmit": "Enregistrer et soumettre", "saveAndSubmit": "Enregistrer et soumettre",
"editMessage": { "editMessage": {
"placeholder": "Tapez un message..." "placeholder": "Tapez un message..."
@ -101,5 +102,14 @@
"translate": "Traduire" "translate": "Traduire"
}, },
"citations": "Citations", "citations": "Citations",
"downloadCode": "Télécharger le code" "downloadCode": "Télécharger le code",
"date": {
"pinned": "Épinglé",
"today": "Aujourd'hui",
"yesterday": "Hier",
"last7Days": "7 derniers jours",
"older": "Plus ancien"
},
"pin": "Épingler",
"unpin": "Désépingler"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Nom" "name": "Nom"
}, },
"tooltip": {
"delete": "Supprimer"
},
"confirm": { "confirm": {
"delete": "Êtes-vous sûr de vouloir supprimer ces connaissances ?" "delete": "Êtes-vous sûr de vouloir supprimer ces connaissances ?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Ricerca nel Web", "webSearch": "Ricerca nel Web",
"regenerate": "Rigenera", "regenerate": "Rigenera",
"edit": "Modifica", "edit": "Modifica",
"delete": "Elimina",
"saveAndSubmit": "Salva e Invia", "saveAndSubmit": "Salva e Invia",
"editMessage": { "editMessage": {
"placeholder": "Scrivi un messaggio..." "placeholder": "Scrivi un messaggio..."
@ -101,5 +102,14 @@
"translate": "Tradurre" "translate": "Tradurre"
}, },
"citations": "Citazioni", "citations": "Citazioni",
"downloadCode": "Scarica Codice" "downloadCode": "Scarica Codice",
"date": {
"pinned": "Fissato",
"today": "Oggi",
"yesterday": "Ieri",
"last7Days": "Ultimi 7 Giorni",
"older": "Più Vecchi"
},
"pin": "Fissa",
"unpin": "Rimuovi"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Nome" "name": "Nome"
}, },
"tooltip": {
"delete": "Elimina"
},
"confirm": { "confirm": {
"delete": "Sei sicuro di voler eliminare questa Knowledge Base?" "delete": "Sei sicuro di voler eliminare questa Knowledge Base?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "ウェブを検索中", "webSearch": "ウェブを検索中",
"regenerate": "再生成", "regenerate": "再生成",
"edit": "編集", "edit": "編集",
"delete": "削除",
"saveAndSubmit": "保存して送信", "saveAndSubmit": "保存して送信",
"editMessage": { "editMessage": {
"placeholder": "メッセージを入力..." "placeholder": "メッセージを入力..."
@ -101,5 +102,14 @@
"translate": "翻訳" "translate": "翻訳"
}, },
"citations": "引用", "citations": "引用",
"downloadCode": "コードをダウンロード" "downloadCode": "コードをダウンロード",
"date": {
"pinned": "固定",
"today": "今日",
"yesterday": "昨日",
"last7Days": "過去7日間",
"older": "それ以前"
},
"pin": "固定",
"unpin": "固定解除"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "名前" "name": "名前"
}, },
"tooltip": {
"delete": "削除"
},
"confirm": { "confirm": {
"delete": "この知識を削除してもよろしいですか?" "delete": "この知識を削除してもよろしいですか?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "വെബ് തിരയുന്നു", "webSearch": "വെബ് തിരയുന്നു",
"regenerate": "വീണ്ടും ജനറേറ്റ് ചെയ്യുക", "regenerate": "വീണ്ടും ജനറേറ്റ് ചെയ്യുക",
"edit": "എഡിറ്റ് ചെയ്യുക", "edit": "എഡിറ്റ് ചെയ്യുക",
"delete": "ഇല്ലാതാക്കുക",
"saveAndSubmit": "സേവ് ചെയ്ത് സമര്‍പ്പിക്കുക", "saveAndSubmit": "സേവ് ചെയ്ത് സമര്‍പ്പിക്കുക",
"editMessage": { "editMessage": {
"placeholder": "ഒരു സന്ദേശം ടൈപ്പ് ചെയ്യുക..." "placeholder": "ഒരു സന്ദേശം ടൈപ്പ് ചെയ്യുക..."
@ -100,5 +101,14 @@
"translate": "വിവർത്തനം ചെയ്യുക" "translate": "വിവർത്തനം ചെയ്യുക"
}, },
"citations": "ഉദ്ധരണികൾ", "citations": "ഉദ്ധരണികൾ",
"downloadCode": "കോഡ് ഡൗൺലോഡ് ചെയ്യുക" "downloadCode": "കോഡ് ഡൗൺലോഡ് ചെയ്യുക",
"date": {
"pinned": "പിൻ ചെയ്തത്",
"today": "ഇന്ന്",
"yesterday": "ഇന്നലെ",
"last7Days": "കഴിഞ്ഞ 7 ദിവസം",
"older": "പഴയത്"
},
"pin": "പിൻ ചെയ്യുക",
"unpin": "അൺപിൻ ചെയ്യുക"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "നാമം" "name": "നാമം"
}, },
"tooltip": {
"delete": "ഇല്ലാതാക്കുക"
},
"confirm": { "confirm": {
"delete": "നിങ്ങൾക്ക് ഈ വിജ്ഞാനം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?" "delete": "നിങ്ങൾക്ക് ഈ വിജ്ഞാനം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Søker på internett", "webSearch": "Søker på internett",
"regenerate": "Regenerer", "regenerate": "Regenerer",
"edit": "Endre", "edit": "Endre",
"delete": "Slett",
"saveAndSubmit": "Lagre & Send inn", "saveAndSubmit": "Lagre & Send inn",
"editMessage": { "editMessage": {
"placeholder": "Skriv en melding..." "placeholder": "Skriv en melding..."
@ -102,5 +103,14 @@
"custom": "Egendefinert" "custom": "Egendefinert"
}, },
"citations": "Sitater", "citations": "Sitater",
"downloadCode": "Last ned kode" "downloadCode": "Last ned kode",
"date": {
"pinned": "Festet",
"today": "I dag",
"yesterday": "I går",
"last7Days": "Siste 7 dager",
"older": "Eldre"
},
"pin": "Fest",
"unpin": "Løsne"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Navn" "name": "Navn"
}, },
"tooltip": {
"delete": "Slett"
},
"confirm": { "confirm": {
"delete": "Er du sikker på at du vil slette denne kunnskapen?" "delete": "Er du sikker på at du vil slette denne kunnskapen?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Pesquisando na web", "webSearch": "Pesquisando na web",
"regenerate": "Gerar Novamente", "regenerate": "Gerar Novamente",
"edit": "Editar", "edit": "Editar",
"delete": "Excluir",
"saveAndSubmit": "Salvar & Enviar", "saveAndSubmit": "Salvar & Enviar",
"editMessage": { "editMessage": {
"placeholder": "Digite uma mensagem..." "placeholder": "Digite uma mensagem..."
@ -101,5 +102,14 @@
"translate": "Traduzir" "translate": "Traduzir"
}, },
"citations": "Citações", "citations": "Citações",
"downloadCode": "Baixar Código" "downloadCode": "Baixar Código",
"date": {
"pinned": "Fixado",
"today": "Hoje",
"yesterday": "Ontem",
"last7Days": "Últimos 7 Dias",
"older": "Mais Antigos"
},
"pin": "Fixar",
"unpin": "Desafixar"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Nome" "name": "Nome"
}, },
"tooltip": {
"delete": "Excluir"
},
"confirm": { "confirm": {
"delete": "Tem certeza de que deseja excluir este conhecimento?" "delete": "Tem certeza de que deseja excluir este conhecimento?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "Поиск в интернете", "webSearch": "Поиск в интернете",
"regenerate": "Пересоздать", "regenerate": "Пересоздать",
"edit": "Редактировать", "edit": "Редактировать",
"delete": "Удалить",
"saveAndSubmit": "Сохранить и отправить", "saveAndSubmit": "Сохранить и отправить",
"editMessage": { "editMessage": {
"placeholder": "Введите сообщение..." "placeholder": "Введите сообщение..."
@ -101,5 +102,14 @@
"translate": "Перевести" "translate": "Перевести"
}, },
"citations": "Цитаты", "citations": "Цитаты",
"downloadCode": "Скачать код" "downloadCode": "Скачать код",
"date": {
"pinned": "Закреплено",
"today": "Сегодня",
"yesterday": "Вчера",
"last7Days": "Последние 7 дней",
"older": "Ранее"
},
"pin": "Закрепить",
"unpin": "Открепить"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "Имя" "name": "Имя"
}, },
"tooltip": {
"delete": "Удалить"
},
"confirm": { "confirm": {
"delete": "Вы уверены, что хотите удалить это знание?" "delete": "Вы уверены, что хотите удалить это знание?"
}, },

View File

@ -41,6 +41,7 @@
"webSearch": "搜索网络", "webSearch": "搜索网络",
"regenerate": "重新生成", "regenerate": "重新生成",
"edit": "编辑", "edit": "编辑",
"delete": "删除",
"saveAndSubmit": "保存 & 提交", "saveAndSubmit": "保存 & 提交",
"editMessage": { "editMessage": {
"placeholder": "输入一条消息..." "placeholder": "输入一条消息..."
@ -101,5 +102,14 @@
"translate": "翻译" "translate": "翻译"
}, },
"citations": "引用", "citations": "引用",
"downloadCode": "下载代码" "downloadCode": "下载代码",
"date": {
"pinned": "已置顶",
"today": "今天",
"yesterday": "昨天",
"last7Days": "最近7天",
"older": "更早"
},
"pin": "置顶",
"unpin": "取消置顶"
} }

View File

@ -10,9 +10,6 @@
"expandedColumns": { "expandedColumns": {
"name": "名称" "name": "名称"
}, },
"tooltip": {
"delete": "删除"
},
"confirm": { "confirm": {
"delete": "您确定要删除此知识吗?" "delete": "您确定要删除此知识吗?"
}, },

View File

@ -93,7 +93,7 @@ export const KnowledgeSettings = () => {
key: "action", key: "action",
render: (text: string, record: any) => ( render: (text: string, record: any) => (
<div className="flex gap-4"> <div className="flex gap-4">
<Tooltip title={t("tooltip.delete")}> <Tooltip title={t("common:delete")}>
<button <button
disabled={isDeleting} disabled={isDeleting}
onClick={() => { onClick={() => {

View File

@ -4,11 +4,18 @@ import {
formatToChatHistory, formatToChatHistory,
formatToMessage, formatToMessage,
deleteByHistoryId, deleteByHistoryId,
updateHistory updateHistory,
pinHistory
} from "@/db" } from "@/db"
import { Empty, Skeleton } from "antd" import { Empty, Skeleton, Dropdown, Menu } from "antd"
import { useMessageOption } from "~/hooks/useMessageOption" import { useMessageOption } from "~/hooks/useMessageOption"
import { PencilIcon, Trash2 } from "lucide-react" import {
PencilIcon,
Trash2,
MoreVertical,
PinIcon,
PinOffIcon
} from "lucide-react"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { import {
@ -38,7 +45,46 @@ export const Sidebar = ({ onClose }: Props) => {
queryFn: async () => { queryFn: async () => {
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
const history = await db.getChatHistories() const history = await db.getChatHistories()
return history
const now = new Date()
const today = new Date(now.setHours(0, 0, 0, 0))
const yesterday = new Date(today)
yesterday.setDate(yesterday.getDate() - 1)
const lastWeek = new Date(today)
lastWeek.setDate(lastWeek.getDate() - 7)
const pinnedItems = history.filter((item) => item.is_pinned)
const todayItems = history.filter(
(item) => !item.is_pinned && new Date(item?.createdAt) >= today
)
const yesterdayItems = history.filter(
(item) =>
!item.is_pinned &&
new Date(item?.createdAt) >= yesterday &&
new Date(item?.createdAt) < today
)
const lastWeekItems = history.filter(
(item) =>
!item.is_pinned &&
new Date(item?.createdAt) >= lastWeek &&
new Date(item?.createdAt) < yesterday
)
const olderItems = history.filter(
(item) => !item.is_pinned && new Date(item?.createdAt) < lastWeek
)
const groups = []
if (pinnedItems.length)
groups.push({ label: "pinned", items: pinnedItems })
if (todayItems.length) groups.push({ label: "today", items: todayItems })
if (yesterdayItems.length)
groups.push({ label: "yesterday", items: yesterdayItems })
if (lastWeekItems.length)
groups.push({ label: "last7days", items: lastWeekItems })
if (olderItems.length) groups.push({ label: "older", items: olderItems })
return groups
} }
}) })
@ -67,6 +113,18 @@ export const Sidebar = ({ onClose }: Props) => {
} }
}) })
const { mutate: pinChatHistory, isPending: pinLoading } = useMutation({
mutationKey: ["pinHistory"],
mutationFn: async (data: { id: string; is_pinned: boolean }) => {
return await pinHistory(data.id, data.is_pinned)
},
onSuccess: () => {
client.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
}
})
return ( return (
<div className="overflow-y-auto z-99"> <div className="overflow-y-auto z-99">
{status === "success" && chatHistories.length === 0 && ( {status === "success" && chatHistories.length === 0 && (
@ -86,7 +144,13 @@ export const Sidebar = ({ onClose }: Props) => {
)} )}
{status === "success" && chatHistories.length > 0 && ( {status === "success" && chatHistories.length > 0 && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{chatHistories.map((chat, index) => ( {chatHistories.map((group, groupIndex) => (
<div key={groupIndex}>
<h3 className="px-2 text-sm font-medium text-gray-500">
{t(`common:date:${group.label}`)}
</h3>
<div className="flex flex-col gap-2 mt-2">
{group.items.map((chat, index) => (
<div <div
key={index} key={index}
className="flex py-2 px-2 items-start gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out bg-gray-100 dark:bg-[#232222] dark:text-gray-100 text-gray-800 border hover:bg-gray-200 dark:hover:bg-[#2d2d2d] dark:border-gray-800"> className="flex py-2 px-2 items-start gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out bg-gray-100 dark:bg-[#232222] dark:text-gray-100 text-gray-800 border hover:bg-gray-200 dark:hover:bg-[#2d2d2d] dark:border-gray-800">
@ -98,9 +162,12 @@ export const Sidebar = ({ onClose }: Props) => {
setHistoryId(chat.id) setHistoryId(chat.id)
setHistory(formatToChatHistory(history)) setHistory(formatToChatHistory(history))
setMessages(formatToMessage(history)) setMessages(formatToMessage(history))
const isLastUsedChatModel = await lastUsedChatModelEnabled() const isLastUsedChatModel =
await lastUsedChatModelEnabled()
if (isLastUsedChatModel) { if (isLastUsedChatModel) {
const currentChatModel = await getLastUsedChatModel(chat.id) const currentChatModel = await getLastUsedChatModel(
chat.id
)
if (currentChatModel) { if (currentChatModel) {
setSelectedModel(currentChatModel) setSelectedModel(currentChatModel)
} }
@ -110,27 +177,66 @@ export const Sidebar = ({ onClose }: Props) => {
}}> }}>
<span className="flex-grow truncate">{chat.title}</span> <span className="flex-grow truncate">{chat.title}</span>
</button> </button>
<div className="flex flex-row gap-3"> <div className="flex items-center gap-2">
<button <Dropdown
overlay={
<Menu>
<Menu.Item
key="pin"
icon={
chat.is_pinned ? (
<PinOffIcon className="w-4 h-4" />
) : (
<PinIcon className="w-4 h-4" />
)
}
onClick={() =>
pinChatHistory({
id: chat.id,
is_pinned: !chat.is_pinned
})
}
disabled={pinLoading}>
{chat.is_pinned
? t("common:unpin")
: t("common:pin")}
</Menu.Item>
<Menu.Item
key="edit"
icon={<PencilIcon className="w-4 h-4" />}
onClick={() => { onClick={() => {
const newTitle = prompt(t("editHistoryTitle"), chat.title) const newTitle = prompt(
t("editHistoryTitle"),
chat.title
)
if (newTitle) { if (newTitle) {
editHistory({ id: chat.id, title: newTitle }) editHistory({ id: chat.id, title: newTitle })
} }
}} }}>
className="text-gray-500 dark:text-gray-400 opacity-80"> {t("common:edit")}
<PencilIcon className="w-4 h-4" /> </Menu.Item>
</button> <Menu.Item
key="delete"
<button icon={<Trash2 className="w-4 h-4" />}
danger
onClick={() => { onClick={() => {
if (!confirm(t("deleteHistoryConfirmation"))) return if (!confirm(t("deleteHistoryConfirmation")))
return
deleteHistory(chat.id) deleteHistory(chat.id)
}} }}>
className="text-red-500 dark:text-red-400 opacity-80"> {t("common:delete")}
<Trash2 className=" w-4 h-4 " /> </Menu.Item>
</Menu>
}
trigger={["click"]}
placement="bottomRight">
<button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100">
<MoreVertical className="w-4 h-4" />
</button> </button>
</Dropdown>
</div>
</div>
))}
</div> </div>
</div> </div>
))} ))}

View File

@ -8,6 +8,7 @@ type HistoryInfo = {
title: string title: string
is_rag: boolean is_rag: boolean
message_source?: "copilot" | "web-ui" message_source?: "copilot" | "web-ui"
is_pinned?: boolean
createdAt: number createdAt: number
} }
@ -232,7 +233,11 @@ export const generateID = () => {
}) })
} }
export const saveHistory = async (title: string, is_rag?: boolean, message_source?: "copilot" | "web-ui") => { export const saveHistory = async (
title: string,
is_rag?: boolean,
message_source?: "copilot" | "web-ui"
) => {
const id = generateID() const id = generateID()
const createdAt = Date.now() const createdAt = Date.now()
const history = { id, title, createdAt, is_rag, message_source } const history = { id, title, createdAt, is_rag, message_source }
@ -317,6 +322,18 @@ export const updateHistory = async (id: string, title: string) => {
db.db.set({ chatHistories: newChatHistories }) db.db.set({ chatHistories: newChatHistories })
} }
export const pinHistory = async (id: string, is_pinned: boolean) => {
const db = new PageAssitDatabase()
const chatHistories = await db.getChatHistories()
const newChatHistories = chatHistories.map((history) => {
if (history.id === id) {
history.is_pinned = is_pinned
}
return history
})
db.db.set({ chatHistories: newChatHistories })
}
export const removeMessageUsingHistoryId = async (history_id: string) => { export const removeMessageUsingHistoryId = async (history_id: string) => {
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
const chatHistory = await db.getChatHistory(history_id) const chatHistory = await db.getChatHistory(history_id)
@ -490,7 +507,6 @@ export const getRecentChatFromCopilot = async () => {
return { history, messages } return { history, messages }
} }
export const getTitleById = async (id: string) => { export const getTitleById = async (id: string) => {
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
const title = await db.getChatHistoryTitleById(id) const title = await db.getChatHistoryTitleById(id)