feat: change the AI summary to pure server-side rendering
This commit is contained in:
parent
7490395fef
commit
24b4b284b3
|
|
@ -61,6 +61,7 @@
|
|||
"@urql/core": "4.0.7",
|
||||
"ahooks": "^3.7.7",
|
||||
"aplayer-react": "^1.1.0",
|
||||
"async-lock": "^1.4.0",
|
||||
"canvas": "^2.11.2",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"chroma-js": "^2.4.2",
|
||||
|
|
@ -148,6 +149,7 @@
|
|||
"@next/bundle-analyzer": "13.4.1",
|
||||
"@playwright/test": "1.33.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "4.1.1",
|
||||
"@types/async-lock": "^1.4.0",
|
||||
"@types/canvas-confetti": "^1.6.0",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/cookie": "0.5.1",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,9 @@ dependencies:
|
|||
aplayer-react:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(react@18.2.0)
|
||||
async-lock:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
canvas:
|
||||
specifier: ^2.11.2
|
||||
version: 2.11.2
|
||||
|
|
@ -357,6 +360,9 @@ devDependencies:
|
|||
'@trivago/prettier-plugin-sort-imports':
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1(prettier@2.8.8)
|
||||
'@types/async-lock':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
'@types/canvas-confetti':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
|
|
@ -4430,6 +4436,10 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@types/async-lock@1.4.0:
|
||||
resolution: {integrity: sha512-2+rYSaWrpdbQG3SA0LmMT6YxWLrI81AqpMlSkw3QtFc2HGDufkweQSn30Eiev7x9LL0oyFrBqk1PXOnB9IEgKg==}
|
||||
dev: true
|
||||
|
||||
/@types/bn.js@5.1.1:
|
||||
resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==}
|
||||
dependencies:
|
||||
|
|
@ -5641,6 +5651,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/async-lock@1.4.0:
|
||||
resolution: {integrity: sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==}
|
||||
dev: false
|
||||
|
||||
/async-mutex@0.2.6:
|
||||
resolution: {integrity: sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==}
|
||||
dependencies:
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains"
|
||||
import { OpenAI } from "langchain/llms/openai"
|
||||
import { PromptTemplate } from "langchain/prompts"
|
||||
import removeMarkdown from "remove-markdown"
|
||||
|
||||
import { Metadata } from "@prisma/client"
|
||||
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import prisma from "~/lib/prisma.server"
|
||||
import { cacheGet } from "~/lib/redis.server"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
|
||||
let model: OpenAI | undefined
|
||||
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
model = new OpenAI({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
modelName: "gpt-3.5-turbo",
|
||||
temperature: 0.3,
|
||||
maxTokens: 400,
|
||||
})
|
||||
}
|
||||
|
||||
const chains = new Map<string, AnalyzeDocumentChain>()
|
||||
|
||||
const getOriginalSummary = async (cid: string, lang: string) => {
|
||||
if (!model) return
|
||||
try {
|
||||
let { content } = await (await fetch(toGateway(`ipfs://${cid}`))).json()
|
||||
|
||||
if (content?.length > 5000) {
|
||||
content = content.slice(0, 5000)
|
||||
}
|
||||
if (content?.length < 200) {
|
||||
return ""
|
||||
} else if (content) {
|
||||
console.time(`fetching summary ${cid}, ${lang}`)
|
||||
|
||||
let chain = chains.get(lang)
|
||||
if (!chain) {
|
||||
const prompt = new PromptTemplate({
|
||||
template: `Summarize this in "${lang}" language:
|
||||
"{text}"
|
||||
CONCISE SUMMARY:`,
|
||||
inputVariables: ["text"],
|
||||
})
|
||||
|
||||
const combineDocsChain = loadSummarizationChain(model, {
|
||||
type: "map_reduce",
|
||||
combineMapPrompt: prompt,
|
||||
combinePrompt: prompt,
|
||||
})
|
||||
|
||||
chain = new AnalyzeDocumentChain({
|
||||
combineDocumentsChain: combineDocsChain,
|
||||
})
|
||||
|
||||
chains.set(lang, chain)
|
||||
}
|
||||
|
||||
const res = await chain.call({
|
||||
input_document: removeMarkdown(content, {
|
||||
useImgAltText: true,
|
||||
gfm: true,
|
||||
}),
|
||||
})
|
||||
|
||||
console.timeEnd(`fetching summary ${cid}, ${lang}`)
|
||||
|
||||
return res?.text
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.timeEnd(`fetching summary ${cid}, ${lang}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getSummary(cid: string, lang: string = "en") {
|
||||
const summary = await cacheGet({
|
||||
key: ["summary", cid, lang],
|
||||
getValueFun: async () => {
|
||||
if (["en", "zh", "zh-TW", "ja"].includes(lang)) {
|
||||
const key = `ai_summary_${lang.replace("-", "").toLowerCase()}`
|
||||
const meta = await prisma.metadata.findFirst({
|
||||
where: {
|
||||
uri: `ipfs://${cid}`,
|
||||
},
|
||||
})
|
||||
if (meta) {
|
||||
if (meta?.[key as keyof Metadata]) {
|
||||
return meta?.[key as keyof Metadata]
|
||||
} else {
|
||||
const summary = await getOriginalSummary(cid, lang)
|
||||
if (summary) {
|
||||
await prisma.metadata.update({
|
||||
where: {
|
||||
uri: `ipfs://${cid}`,
|
||||
},
|
||||
data: {
|
||||
[key as keyof Metadata]: summary,
|
||||
},
|
||||
})
|
||||
|
||||
return summary
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const summary = await getOriginalSummary(cid, lang)
|
||||
if (summary) {
|
||||
await prisma.metadata.create({
|
||||
data: {
|
||||
uri: `ipfs://${cid}`,
|
||||
[key as keyof Metadata]: summary,
|
||||
},
|
||||
})
|
||||
|
||||
return summary
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
noUpdate: true,
|
||||
})
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
let { cid, lang } = getQuery(req)
|
||||
const res = new NextServerResponse()
|
||||
|
||||
if (!cid) {
|
||||
return res.status(400).send("Bad Request")
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
data: await getSummary(cid as string, lang as string),
|
||||
})
|
||||
}
|
||||
|
|
@ -598,18 +598,6 @@ export async function updateComment(
|
|||
})
|
||||
}
|
||||
|
||||
export async function getSummary({
|
||||
cid,
|
||||
lang,
|
||||
}: {
|
||||
cid: string
|
||||
lang?: string
|
||||
}) {
|
||||
return (
|
||||
await (await fetch(`/api/summary?cid=${cid}&lang=${lang || "en"}`)).json()
|
||||
).data
|
||||
}
|
||||
|
||||
export async function checkMirror(characterId: number) {
|
||||
const notes = await indexer.getNotes({
|
||||
characterId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
import AsyncLock from "async-lock"
|
||||
import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains"
|
||||
import { OpenAI } from "langchain/llms/openai"
|
||||
import { PromptTemplate } from "langchain/prompts"
|
||||
import removeMarkdown from "remove-markdown"
|
||||
|
||||
import { Metadata } from "@prisma/client"
|
||||
import { QueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { getNoteSlug } from "~/lib/helpers"
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import prisma from "~/lib/prisma.server"
|
||||
import { cacheDelete, cacheGet } from "~/lib/redis.server"
|
||||
import * as pageModel from "~/models/page.model"
|
||||
|
||||
|
|
@ -118,3 +127,129 @@ export const fetchGetPagesBySite = async (
|
|||
}) as Promise<ReturnType<typeof pageModel.getPagesBySite>>
|
||||
})
|
||||
}
|
||||
|
||||
// Post summary
|
||||
|
||||
let model: OpenAI | undefined
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
model = new OpenAI({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
modelName: "gpt-3.5-turbo",
|
||||
temperature: 0.3,
|
||||
maxTokens: 400,
|
||||
})
|
||||
}
|
||||
const chains = new Map<string, AnalyzeDocumentChain>()
|
||||
|
||||
const getOriginalSummary = async (cid: string, lang: string) => {
|
||||
if (!model) return
|
||||
try {
|
||||
let { content } = await (await fetch(toGateway(`ipfs://${cid}`))).json()
|
||||
|
||||
if (content?.length > 5000) {
|
||||
content = content.slice(0, 5000)
|
||||
}
|
||||
if (content?.length < 200) {
|
||||
return
|
||||
} else if (content) {
|
||||
console.time(`fetching summary ${cid}, ${lang}`)
|
||||
|
||||
let chain = chains.get(lang)
|
||||
if (!chain) {
|
||||
const prompt = new PromptTemplate({
|
||||
template: `Summarize this in "${lang}" language:
|
||||
"{text}"
|
||||
CONCISE SUMMARY:`,
|
||||
inputVariables: ["text"],
|
||||
})
|
||||
|
||||
const combineDocsChain = loadSummarizationChain(model, {
|
||||
type: "map_reduce",
|
||||
combineMapPrompt: prompt,
|
||||
combinePrompt: prompt,
|
||||
})
|
||||
|
||||
chain = new AnalyzeDocumentChain({
|
||||
combineDocumentsChain: combineDocsChain,
|
||||
})
|
||||
|
||||
chains.set(lang, chain)
|
||||
}
|
||||
|
||||
const res = await chain.call({
|
||||
input_document: removeMarkdown(content, {
|
||||
useImgAltText: true,
|
||||
gfm: true,
|
||||
}),
|
||||
})
|
||||
|
||||
console.timeEnd(`fetching summary ${cid}, ${lang}`)
|
||||
|
||||
return res?.text as string
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.timeEnd(`fetching summary ${cid}, ${lang}`)
|
||||
}
|
||||
}
|
||||
|
||||
const lock = new AsyncLock()
|
||||
|
||||
export async function getSummary({
|
||||
cid,
|
||||
lang = "en",
|
||||
}: {
|
||||
cid: string
|
||||
lang?: string
|
||||
}) {
|
||||
const key = `ai_summary_${lang.replace("-", "").toLowerCase()}`
|
||||
|
||||
const summary = (await cacheGet({
|
||||
key: ["summary", cid, lang],
|
||||
getValueFun: async () => {
|
||||
if (["en", "zh", "zh-TW", "ja"].includes(lang)) {
|
||||
const meta = await prisma.metadata.findFirst({
|
||||
where: {
|
||||
uri: `ipfs://${cid}`,
|
||||
},
|
||||
})
|
||||
if (meta) {
|
||||
if (meta?.[key as keyof Metadata]) {
|
||||
return meta?.[key as keyof Metadata]
|
||||
} else {
|
||||
lock.acquire(cid, async () => {
|
||||
const summary = await getOriginalSummary(cid, lang)
|
||||
if (summary) {
|
||||
await prisma.metadata.update({
|
||||
where: {
|
||||
uri: `ipfs://${cid}`,
|
||||
},
|
||||
data: {
|
||||
[key as keyof Metadata]: summary,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lock.acquire(cid, async () => {
|
||||
const summary = await getOriginalSummary(cid, lang)
|
||||
if (summary) {
|
||||
await prisma.metadata.create({
|
||||
data: {
|
||||
uri: `ipfs://${cid}`,
|
||||
[key as keyof Metadata]: summary,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
noUpdate: true,
|
||||
})) as string | undefined
|
||||
|
||||
return summary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,18 +359,6 @@ export function useGetComments(
|
|||
})
|
||||
}
|
||||
|
||||
export function useGetSummary(input: { cid?: string; lang?: string }) {
|
||||
return useQuery(["getSummary", input.cid, input.lang], async () => {
|
||||
if (!input.cid || !input.lang) {
|
||||
return
|
||||
}
|
||||
return pageModel.getSummary({
|
||||
cid: input.cid,
|
||||
lang: input.lang,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function useGetMirrorXyz(input: { address?: string }) {
|
||||
return useQuery(["getMirror", input.address], async () => {
|
||||
if (!input.address) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue