Skip to content

Commit

Permalink
Merge pull request #61 from Ray-D-Song/aitag
Browse files Browse the repository at this point in the history
Aitag
  • Loading branch information
banzhe authored Dec 4, 2024
2 parents 0fee736 + 905d497 commit 56f67b8
Show file tree
Hide file tree
Showing 21 changed files with 982 additions and 119 deletions.
29 changes: 29 additions & 0 deletions packages/plugin/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,32 @@ onMessage('scrape-available', async ({ data: { tabId } }) => {
return { available: false }
}
})

onMessage('get-ai-tag-config', async () => {
const aiTagConfig = await request('/config/ai_tag', {
method: 'GET',
})
console.log(aiTagConfig)
return {
aiTagConfig,
}
})

onMessage('generate-tag', async ({ data: { title, pageDesc, tagLanguage, preferredTags, model } }) => {
const tags = await request('/tags/generate_tag', {
method: 'POST',
body: JSON.stringify({
title,
pageDesc,
tagLanguage,
preferredTags,
model,
}),
headers: {
'Content-Type': 'application/json',
},
})
return {
tags,
}
})
96 changes: 96 additions & 0 deletions packages/plugin/popup/components/FolderSelectWithCache.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { PlusIcon } from 'lucide-react'
import { Button } from '@web-archive/shared/components/button'
import { isNotNil } from '@web-archive/shared/utils'
import { sendMessage } from 'webext-bridge/popup'
import { useRequest } from 'ahooks'
import { useState } from 'react'
import FolderCombobox from './FolderCombobox'
import NewFolderDialog from './NewFolderDialog'

interface FolderSelectWithCacheProps {
value: string | undefined
onValueChange: (value: string | undefined) => void
}

async function getAllFolders() {
const { folders } = await sendMessage('get-all-folders', {})
await new Promise(resolve => setTimeout(resolve, 2000))
return folders
}

export function getLastChooseFolderId() {
return localStorage.getItem('lastChooseFolderId') || undefined
}

function FolderSelectWithCache({ value, onValueChange }: FolderSelectWithCacheProps) {
const lastChooseFolderId = getLastChooseFolderId()
const { data: folderList, refresh: refreshFolderList, mutate: setFolderList } = useRequest(getAllFolders, {
cacheKey: 'folderList',
setCache: (data) => {
localStorage.setItem('folderList', JSON.stringify(data))
},
getCache: () => {
const cache = localStorage.getItem('folderList')
return cache ? JSON.parse(cache) : []
},
onSuccess: (data) => {
if (isNotNil(value) && !data.some(folder => folder.id.toString() === value)) {
onValueChange(undefined)
lastChooseFolderId && localStorage.removeItem('lastChooseFolderId')
}
},
})

const [newFolderDialogVisible, setNewFolderDialogVisible] = useState(false)
function handleNewFolderAdded(folder: { id: number, name: string }) {
setFolderList(prevList => [
...(prevList ?? []),
folder,
])
onValueChange(folder.id.toString())
localStorage.setItem('lastChooseFolderId', folder.id.toString())
refreshFolderList()
}

function handleFolderSelect(newFolder: string) {
localStorage.setItem('lastChooseFolderId', newFolder)
onValueChange(newFolder)
}

return (
<div className="flex space-x-2">
<NewFolderDialog
open={newFolderDialogVisible}
afterSubmit={handleNewFolderAdded}
setOpen={setNewFolderDialogVisible}
>
</NewFolderDialog>
<div className="flex-1">
{/*
use combobox instead of select due to Select(v2.1.2) will auto close when resize event is fired
and popup in firefox will fire resize event after DOM mutations
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Popups#:~:text=In%20Firefox%2C%20the%20size%20is%20calculated%20just%20before%20the%20popup%20is%20shown%2C%20and%20at%20most%2010%20times%20per%20second%20after%20DOM%20mutations.
*/}
<FolderCombobox
value={value}
onValueChange={handleFolderSelect}
options={(folderList ?? []).map(folder => ({
value: folder.id.toString(),
label: folder.name,
}))}
>
</FolderCombobox>
</div>
<Button
variant="secondary"
className="h-10"
size="icon"
onClick={() => setNewFolderDialogVisible(true)}
>
<PlusIcon size={18}></PlusIcon>
</Button>
</div>
)
}

export default FolderSelectWithCache
130 changes: 130 additions & 0 deletions packages/plugin/popup/components/TagInputWithCache.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { useRef } from 'react'
import type { AutoCompleteTagInputRef } from '@web-archive/shared/components/auto-complete-tag-input'
import AutoCompleteTagInput from '@web-archive/shared/components/auto-complete-tag-input'
import { Button } from '@web-archive/shared/components/button'
import type { GenerateTagProps } from '@web-archive/shared/utils'
import { generateTagByOpenAI, isNil } from '@web-archive/shared/utils'
import { useRequest } from 'ahooks'
import { AlertCircleIcon, Loader2Icon, SparklesIcon } from 'lucide-react'
import { sendMessage } from 'webext-bridge/popup'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@web-archive/shared/components/tooltip'
import toast from 'react-hot-toast'

async function getAllTags() {
const { tags } = await sendMessage('get-all-tags', {})
return tags
}

async function getAITagConfig() {
const { aiTagConfig } = await sendMessage('get-ai-tag-config', {})
return aiTagConfig
}

async function doGenerateTag(props: GenerateTagProps) {
if (!props.model) {
toast.error('Please configure in website settings first')
throw new Error('Invalid AI tag config')
}
if (props.type === 'cloudflare') {
const { tags } = await sendMessage('generate-tag', props)
return tags
}
return await generateTagByOpenAI(props)
}

interface TagInputWithCacheProps {
title: string
description: string
onValueChange: (value: string[]) => void
}

function TagInputWithCache({ onValueChange, title, description }: TagInputWithCacheProps) {
const tagInputRef = useRef<AutoCompleteTagInputRef>(null)
const { data: tagList } = useRequest(getAllTags, {
cacheKey: 'tagList',
setCache: (data) => {
localStorage.setItem('tagList', JSON.stringify(data))
},
getCache: () => {
const cache = localStorage.getItem('tagList')
return cache ? JSON.parse(cache) : []
},
})

const { data: aiTagConfig } = useRequest(getAITagConfig, {
cacheKey: 'aiTagConfig',
setCache: (data) => {
localStorage.setItem('aiTagConfig', JSON.stringify(data))
},
getCache: () => {
const cache = localStorage.getItem('aiTagConfig')
return cache ? JSON.parse(cache) : []
},
})

const { run: generateTagRun, loading: generateTagRunning, error: generateTagError } = useRequest(
doGenerateTag,
{
manual: true,
onSuccess: (data) => {
tagInputRef.current?.addTags(data)
},
onError: (error) => {
console.error(error)
toast.error(error?.message)
},
},
)

return (
<div className="flex space-x-2">
<AutoCompleteTagInput
ref={tagInputRef}
tags={tagList ?? []}
shouldLimitHeight
onChange={({ bindTags }) => {
onValueChange(bindTags)
}}
>
</AutoCompleteTagInput>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
className="w-12 h-10 "
variant="secondary"
size="icon"
disabled={generateTagRunning}
onClick={() => {
generateTagRun({
...aiTagConfig,
title,
pageDesc: description,
})
}}
>
{
generateTagRunning
? <Loader2Icon size={18} className="animate-spin"></Loader2Icon>
: (
generateTagError
? <AlertCircleIcon size={18} className="text-destructive"></AlertCircleIcon>
: <SparklesIcon size={18} className="opacity-80"></SparklesIcon>
)
}
</Button>
</TooltipTrigger>
{
generateTagError && (
<TooltipContent>
{generateTagError.message}
</TooltipContent>
)
}
</Tooltip>
</TooltipProvider>
</div>
)
}

export default TagInputWithCache
Loading

0 comments on commit 56f67b8

Please sign in to comment.