Commit dd609683 authored by Domi's avatar Domi

feat: remote config

parent 62715bb0
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"name": "picture-in-picture", "name": "picture-in-picture",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@firebase/remote-config": "^0.4.5",
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
"@vueuse/core": "^10.7.1", "@vueuse/core": "^10.7.1",
"@vueuse/gesture": "^2.0.0-beta.1", "@vueuse/gesture": "^2.0.0-beta.1",
...@@ -1254,14 +1255,14 @@ ...@@ -1254,14 +1255,14 @@
"integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA=="
}, },
"node_modules/@firebase/remote-config": { "node_modules/@firebase/remote-config": {
"version": "0.4.4", "version": "0.4.5",
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.5.tgz",
"integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", "integrity": "sha512-rGLqc/4OmxrS39RA9kgwa6JmgWytQuMo+B8pFhmGp3d++x2Hf9j+MLQfhOLyyUo64fNw20J19mLXhrXvKHsjZQ==",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.4", "@firebase/component": "0.6.5",
"@firebase/installations": "0.6.4", "@firebase/installations": "0.6.5",
"@firebase/logger": "0.4.0", "@firebase/logger": "0.4.0",
"@firebase/util": "1.9.3", "@firebase/util": "1.9.4",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
"peerDependencies": { "peerDependencies": {
...@@ -1284,11 +1285,57 @@ ...@@ -1284,11 +1285,57 @@
"@firebase/app-compat": "0.x" "@firebase/app-compat": "0.x"
} }
}, },
"node_modules/@firebase/remote-config-compat/node_modules/@firebase/remote-config": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz",
"integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==",
"dependencies": {
"@firebase/component": "0.6.4",
"@firebase/installations": "0.6.4",
"@firebase/logger": "0.4.0",
"@firebase/util": "1.9.3",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@firebase/app": "0.x"
}
},
"node_modules/@firebase/remote-config-types": { "node_modules/@firebase/remote-config-types": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz",
"integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA=="
}, },
"node_modules/@firebase/remote-config/node_modules/@firebase/component": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.5.tgz",
"integrity": "sha512-2tVDk1ixi12sbDmmfITK8lxSjmcb73BMF6Qwc3U44hN/J1Fi1QY/Hnnb6klFlbB9/G16a3J3d4nXykye2EADTw==",
"dependencies": {
"@firebase/util": "1.9.4",
"tslib": "^2.1.0"
}
},
"node_modules/@firebase/remote-config/node_modules/@firebase/installations": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.5.tgz",
"integrity": "sha512-0xxnQWw8rSRzu0ZOCkZaO+MJ0LkDAfwwTB2Z1SxRK6FAz5xkxD1ZUwM0WbCRni49PKubCrZYOJ6yg7tSjU7AKA==",
"dependencies": {
"@firebase/component": "0.6.5",
"@firebase/util": "1.9.4",
"idb": "7.1.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@firebase/app": "0.x"
}
},
"node_modules/@firebase/remote-config/node_modules/@firebase/util": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.4.tgz",
"integrity": "sha512-WLonYmS1FGHT97TsUmRN3qnTh5TeeoJp1Gg5fithzuAgdZOUtsYECfy7/noQ3llaguios8r5BuXSEiK82+UrxQ==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@firebase/storage": { "node_modules/@firebase/storage": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.0.tgz",
...@@ -3591,6 +3638,21 @@ ...@@ -3591,6 +3638,21 @@
"@firebase/util": "1.9.3" "@firebase/util": "1.9.3"
} }
}, },
"node_modules/firebase/node_modules/@firebase/remote-config": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz",
"integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==",
"dependencies": {
"@firebase/component": "0.6.4",
"@firebase/installations": "0.6.4",
"@firebase/logger": "0.4.0",
"@firebase/util": "1.9.3",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@firebase/app": "0.x"
}
},
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "run-p dev:page dev:content dev:js",
"dev:page": "cross-env NODE_ENV=development vite build --watch", "dev:page": "cross-env NODE_ENV=development vite build --watch",
"dev:content": "vite build --watch -c vite.content.config.ts", "dev:content": "vite build --watch -c vite.content.config.ts",
"dev:js": "node esbuild.mjs --watch", "dev:js": "node esbuild.mjs --watch",
"dev": "run-p dev:page dev:content dev:js", "start": "vite -c vite.config.ts --port 3000",
"build": "run-p type-check build:js build:content \"build-only {@}\" --", "build": "run-p type-check build:js build:content \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"build:content": "vite build -c vite.content.config.ts", "build:content": "vite build -c vite.content.config.ts",
"build:js": "node esbuild.mjs", "build:js": "node esbuild.mjs",
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@firebase/remote-config": "^0.4.5",
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
"@vueuse/core": "^10.7.1", "@vueuse/core": "^10.7.1",
"@vueuse/gesture": "^2.0.0-beta.1", "@vueuse/gesture": "^2.0.0-beta.1",
......
{
"data": {
"configVersion": 20240213,
"chatDocSites": [
{
"host": "huggingface.co",
"path": "^/chat",
"maxInputLength": 8000,
"selector": {
"input": "form[tabindex] textarea",
"send": "form[tabindex] button[type=\"submit\"]",
"wait": "form[tabindex] button[type=\"submit\"]:not([disabled=true])"
}
},
{
"host": "chat.openai.com",
"path": ".",
"maxInputLength": 8000,
"maxInputToken": 4096,
"selector": {
"input": "form textarea#prompt-textarea",
"send": "form textarea ~ button",
"wait": "form textarea ~ button"
}
},
{
"host": "bard.google.com",
"path": "chat",
"maxInputLength": 4096,
"selector": {
"input": "input-area rich-textarea div",
"send": "input-area div[class*=send] button[class*=send]",
"wait": "input-area div[class*=send] button[class*=send]"
}
},
{
"host": "gemini.google.com",
"path": "app",
"maxInputLength": 32000,
"selector": {
"input": "input-area-v2 rich-textarea div",
"send": "input-area-v2 div[class*=send] button[class*=send]",
"wait": [
{
"target": ".conversation-container:last-of-type bard-avatar .avatar_primary_model img[src*='processing']",
"match": ""
},
{
"target": "input-area-v2 div[class*=send] button[class*=send] mat-icon",
"match": "*"
}
]
}
},
{
"host": "copilot.microsoft.com",
"path": ".",
"maxInputLength": 2048,
"selector": {
"input": "cib-serp /deep/ cib-action-bar /deep/ cib-text-input /deep/ textarea",
"send": "cib-serp /deep/ cib-action-bar /deep/ .bottom-right-controls button",
"wait": "cib-serp /deep/ cib-action-bar /deep/ cib-typing-indicator /deep/ button[disabled]"
}
},
{
"host": "yiyan.baidu.com",
"path": ".",
"maxInputLength": 2000,
"selector": {
"input": "textarea:not(h1 ~ textarea)",
"send": "div > span:has(svg[width=\"240\"])",
"wait": "div > span:has(svg[width=\"240\"]):not([style*=\"display: none\"])"
}
}
]
}
}
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types" import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import { waitMessage, tabUpdated } from "@/utils/ext" import { waitMessage, tabUpdated, getLocal } from "@/utils/ext"
import { offscreen } from "./offscreen" import { offscreen } from "./offscreen"
import config from '@/assets/config.json'
async function openPipBackground(url: string) { async function openPipBackground(url: string) {
const tab = await chrome.tabs.create({ const tab = await chrome.tabs.create({
...@@ -153,3 +154,40 @@ function handleCommand(command: string) { ...@@ -153,3 +154,40 @@ function handleCommand(command: string) {
} }
chrome.commands.onCommand.addListener(handleCommand) chrome.commands.onCommand.addListener(handleCommand)
async function updateConfig() {
const url = 'https://config.ziziyi.com/anything-copilot'
const now = Date.now()
let { timestamp, configVersion, chatDocSites } = await getLocal({
timestamp: {} as Record<string, number>,
configVersion: config.data.configVersion,
chatDocSites: config.data.chatDocSites,
})
if (timestamp.configUpdatedAt > 0 && now - timestamp.configUpdatedAt < 1000 * 60 * 60 * 24) {
return
}
const res = await fetch(url)
const data = await res.json()
if (data.configVersion < configVersion) {
return
}
if (Array.isArray(data.chatDocSites) && data.chatDocSites.every((i: any) => i.host && i.selector)) {
chatDocSites = data.chatDocSites
}
timestamp.configUpdatedAt = now
await chrome.storage.local.set({
chatDocSites,
timestamp,
})
}
chrome.runtime.onStartup.addListener(() => {
updateConfig()
})
\ No newline at end of file
...@@ -6,7 +6,10 @@ import { chatDocsPanel, docsAddon } from "@/store" ...@@ -6,7 +6,10 @@ import { chatDocsPanel, docsAddon } from "@/store"
import ChatDocsPanel from "@/components/chatdocs/ChatDocsPanel.vue" import ChatDocsPanel from "@/components/chatdocs/ChatDocsPanel.vue"
import { watchEffect } from "vue" import { watchEffect } from "vue"
import { useI18n } from "@/utils/i18n" import { useI18n } from "@/utils/i18n"
import { getDocItem, sitesConfig } from "./helper" import { getDocItem, devConfig, type SiteConfig } from "./helper"
import config from "@/assets/config.json"
import { getLocal } from "@/utils/ext"
import { chatDocPrompt } from "@/utils/prompt"
const { t } = useI18n() const { t } = useI18n()
const logoUrl = chrome.runtime.getURL("/logo.svg") const logoUrl = chrome.runtime.getURL("/logo.svg")
...@@ -19,10 +22,12 @@ const position = reactive({ ...@@ -19,10 +22,12 @@ const position = reactive({
ty: 0, ty: 0,
}) })
const supported = computed(() => { const siteConfig: SiteConfig = reactive({
return sitesConfig.some( prompt: chatDocPrompt,
(s) => s.host == location.host && s.path.test(location.pathname) maxInput: 3000,
) maxInputType: "token",
maxRuns: 10,
selector: null,
}) })
let timer = 0 let timer = 0
...@@ -38,7 +43,7 @@ watchEffect(() => { ...@@ -38,7 +43,7 @@ watchEffect(() => {
}) })
function onDragOver(e: DragEvent) { function onDragOver(e: DragEvent) {
if (!supported.value) return if (!siteConfig.selector) return
docsAddon.visible = true docsAddon.visible = true
clearTimeout(timer) clearTimeout(timer)
timer = window.setTimeout(() => (docsAddon.visible = false), 180) timer = window.setTimeout(() => (docsAddon.visible = false), 180)
...@@ -93,14 +98,14 @@ function adjustPosition() { ...@@ -93,14 +98,14 @@ function adjustPosition() {
rect.left < 0 rect.left < 0
? -rect.left ? -rect.left
: rect.right > innerWidth : rect.right > innerWidth
? innerWidth - rect.right ? innerWidth - rect.right
: 0 : 0
const dy = const dy =
rect.top < 0 rect.top < 0
? -rect.top ? -rect.top
: rect.bottom > innerHeight : rect.bottom > innerHeight
? innerHeight - rect.bottom ? innerHeight - rect.bottom
: 0 : 0
position.tx += dx position.tx += dx
position.ty += dy position.ty += dy
// div.style.transform = `translate(${position.tx}px, ${position.ty}px)` // div.style.transform = `translate(${position.tx}px, ${position.ty}px)`
...@@ -111,6 +116,25 @@ onMounted(() => { ...@@ -111,6 +116,25 @@ onMounted(() => {
const doc = div.value?.ownerDocument || document const doc = div.value?.ownerDocument || document
doc.addEventListener("dragover", onDragOver, true) doc.addEventListener("dragover", onDragOver, true)
doc.defaultView?.addEventListener("resize", adjustPosition) doc.defaultView?.addEventListener("resize", adjustPosition)
const defaultChatDocSites = config.data.chatDocSites
getLocal({
chatDocSites: defaultChatDocSites,
}).then(({ chatDocSites }) => {
chatDocSites.push(devConfig)
const { host, pathname } = doc.location
const matchConfig = chatDocSites.find(
(s) => s.host == host && (new RegExp(s.path)).test(pathname)
)
if (matchConfig) {
siteConfig.maxInput = matchConfig.maxInputToken || matchConfig.maxInputLength
siteConfig.maxInputType = matchConfig.maxInputToken ? "token" : "char"
siteConfig.selector = matchConfig.selector
}
console.log("chatdoc config: ", matchConfig, chatDocSites)
})
}) })
onUnmounted(() => { onUnmounted(() => {
...@@ -123,21 +147,15 @@ onUnmounted(() => { ...@@ -123,21 +147,15 @@ onUnmounted(() => {
<template> <template>
<div ref="div" class="hidden"></div> <div ref="div" class="hidden"></div>
<div <div v-if="docsAddon.visible" :class="[
v-if="docsAddon.visible" 'fixed mt-10 top-0 p-6 border-2 rounded-lg bg-background z-[9999999]',
:class="[ 'shadow-lg left-1/2 -translate-x-1/2 w-max transition-all',
'fixed mt-10 top-0 p-6 border-2 rounded-lg bg-background z-[9999999]', {
'shadow-lg left-1/2 -translate-x-1/2 w-max transition-all', 'border-primary/50': !docsAddon.active && docsAddon.visible,
{ 'border-primary scale-105': docsAddon.active,
'border-primary/50': !docsAddon.active && docsAddon.visible, },
'border-primary scale-105': docsAddon.active, ]" @dragenter="docsAddon.active = true" @dragleave="docsAddon.active = false" @dragover="(e) => e.preventDefault()"
}, @drop="onDrop">
]"
@dragenter="docsAddon.active = true"
@dragleave="docsAddon.active = false"
@dragover="(e) => e.preventDefault()"
@drop="onDrop"
>
<div class="pointer-events-none"> <div class="pointer-events-none">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<IconNoteStackAdd class="w-8 h-8" /> <IconNoteStackAdd class="w-8 h-8" />
...@@ -150,37 +168,23 @@ onUnmounted(() => { ...@@ -150,37 +168,23 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<div <div v-if="chatDocsPanel.visible" ref="chatDocsDiv" :class="[
v-if="chatDocsPanel.visible" 'fixed flex flex-col w-96 max-w-full h-[600px] max-h-full border rounded-lg',
ref="chatDocsDiv" 'z-[9999] border-foreground/10 bg-background shadow-lg dark:border-2',
:class="[ {
'fixed flex flex-col w-96 max-w-full h-[600px] max-h-full border rounded-lg', 'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2': !position.valid,
'z-[9999] border-foreground/10 bg-background shadow-lg dark:border-2', },
{ ]">
'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2': !position.valid, <div class="flex items-center px-4 pt-4 pb-1 select-none" @pointerdown="(e) => e.buttons == 1 && (e.target as Element)?.setPointerCapture(e.pointerId)
}, " @pointermove="handlePointerMove" @pointerup="adjustPosition" @pointercancel="adjustPosition">
]"
>
<div
class="flex items-center px-4 pt-4 pb-1 select-none"
@pointerdown="
(e) => e.buttons == 1 && (e.target as Element)?.setPointerCapture(e.pointerId)
"
@pointermove="handlePointerMove"
@pointerup="adjustPosition"
@pointercancel="adjustPosition"
>
<img :src="logoUrl" class="w-6 h-6" /> <img :src="logoUrl" class="w-6 h-6" />
<span class="mx-2 text-xl font-bold">{{ t("chatDocsAddon") }}</span> <span class="mx-2 text-xl font-bold">{{ t("chatDocsAddon") }}</span>
<button <button aria-label="close" class="ml-auto p-1 top-0 right-0 rounded-full hover:bg-rose-400/10"
aria-label="close" @click="chatDocsPanel.visible = false">
class="ml-auto p-1 top-0 right-0 rounded-full hover:bg-rose-400/10"
@click="chatDocsPanel.visible = false"
>
<IconClose class="w-5 h-5" /> <IconClose class="w-5 h-5" />
</button> </button>
</div> </div>
<ChatDocsPanel @close="chatDocsPanel.visible = false" /> <ChatDocsPanel @close="chatDocsPanel.visible = false" :siteConfig="siteConfig" />
</div> </div>
</template> </template>
......
...@@ -11,7 +11,8 @@ import DocInput from "./DocInput.vue" ...@@ -11,7 +11,8 @@ import DocInput from "./DocInput.vue"
import IconPlayCircle from "../icons/IconPlayCircle.vue" import IconPlayCircle from "../icons/IconPlayCircle.vue"
import IconPause from "../icons/IconPause.vue" import IconPause from "../icons/IconPause.vue"
import IconProgressActivity from "../icons/IconProgressActivity.vue" import IconProgressActivity from "../icons/IconProgressActivity.vue"
import { sitesConfig } from "./helper" import { devConfig, type SiteConfig } from "./helper"
import config from "@/assets/config.json"
import { import {
query, query,
dispatchInput, dispatchInput,
...@@ -19,6 +20,7 @@ import { ...@@ -19,6 +20,7 @@ import {
waitFor, waitFor,
getInputValue, getInputValue,
} from "@/utils/dom" } from "@/utils/dom"
import { getLocal } from "@/utils/ext"
import { chatDocPrompt } from "@/utils/prompt" import { chatDocPrompt } from "@/utils/prompt"
import { useI18n } from "@/utils/i18n" import { useI18n } from "@/utils/i18n"
...@@ -33,13 +35,8 @@ type SnippetItem = { ...@@ -33,13 +35,8 @@ type SnippetItem = {
length: number length: number
} }
type Selector = {
input: string
send: string
wait: string
}
defineEmits(["close"]) defineEmits(["close"])
const { siteConfig } = defineProps<{ siteConfig: SiteConfig }>()
const { t } = useI18n() const { t } = useI18n()
const logoUrl = chrome.runtime.getURL("/logo.svg") const logoUrl = chrome.runtime.getURL("/logo.svg")
...@@ -52,14 +49,6 @@ const sendTask = reactive({ ...@@ -52,14 +49,6 @@ const sendTask = reactive({
error: "", error: "",
}) })
const config = reactive({
prompt: chatDocPrompt,
maxInput: 3000,
maxInputType: "token" as "char" | "token",
maxRuns: 10,
selector: null as Selector | null,
})
const lenRate = reactive({ const lenRate = reactive({
tokenRate: 4, tokenRate: 4,
}) })
...@@ -72,7 +61,7 @@ const docs = computed(() => { ...@@ -72,7 +61,7 @@ const docs = computed(() => {
}) })
const currentMessage = computed(() => { const currentMessage = computed(() => {
const { prompt, maxInput, maxInputType } = config const { prompt, maxInput, maxInputType } = siteConfig
const snippets: SnippetItem[] = [] const snippets: SnippetItem[] = []
const rate = maxInputType == "token" ? lenRate.tokenRate : 1 const rate = maxInputType == "token" ? lenRate.tokenRate : 1
...@@ -129,7 +118,7 @@ const currentMessage = computed(() => { ...@@ -129,7 +118,7 @@ const currentMessage = computed(() => {
}) })
watch( watch(
[config, () => currentMessage.value], [siteConfig, () => currentMessage.value],
async ([config, currentMessage]) => { async ([config, currentMessage]) => {
const { message } = currentMessage const { message } = currentMessage
const { maxInput, maxInputType } = config const { maxInput, maxInputType } = config
...@@ -243,22 +232,6 @@ const progress = computed(() => { ...@@ -243,22 +232,6 @@ const progress = computed(() => {
} }
}) })
onMounted(() => {
const doc = div.value?.ownerDocument || document
const { host, pathname } = doc.location
const matchConfig = sitesConfig.find(
(c) => c.host == host && c.path.test(pathname)
)
if (matchConfig) {
config.maxInput = matchConfig.maxInputToken || matchConfig.maxInputLength
config.maxInputType = matchConfig.maxInputToken ? "token" : "char"
config.selector = matchConfig.selector
}
console.log("site config: ", matchConfig)
})
const openDocSelect = (key: string) => { const openDocSelect = (key: string) => {
sheet.value = "docSelect" sheet.value = "docSelect"
currentDoc.value = key currentDoc.value = key
...@@ -293,7 +266,7 @@ const autoSend = async () => { ...@@ -293,7 +266,7 @@ const autoSend = async () => {
return return
} }
if (!config.selector) { if (!siteConfig.selector) {
return return
} }
...@@ -301,7 +274,7 @@ const autoSend = async () => { ...@@ -301,7 +274,7 @@ const autoSend = async () => {
sendTask.key = key sendTask.key = key
sendTask.status = "running" sendTask.status = "running"
const selector = config.selector const selector = siteConfig.selector
const isWorking = () => const isWorking = () =>
!currentMessage.value.done && !currentMessage.value.done &&
...@@ -331,11 +304,26 @@ const autoSend = async () => { ...@@ -331,11 +304,26 @@ const autoSend = async () => {
} }
return sented return sented
}, },
{ interval: 1000 * 2 } { interval: 1000 * 1 }
) )
await new Promise((r) => setTimeout(r, 200)) await new Promise((r) => setTimeout(r, 200))
await waitFor(() => query(selector.wait) != null, {})
const waitList =
typeof selector.wait == "string"
? [{ target: selector.wait, match: "*" }]
: selector.wait
for (let item of waitList) {
await waitFor(() => {
const t = query(item.target)
if (item.match == "") {
return t == null
}
return t != null && t.matches(item.match)
}, {})
}
await new Promise((r) => setTimeout(r, 200)) await new Promise((r) => setTimeout(r, 200))
await nextMessage() await nextMessage()
...@@ -411,11 +399,11 @@ const resetSent = () => { ...@@ -411,11 +399,11 @@ const resetSent = () => {
<div class="flex items-center justify-between my-1"> <div class="flex items-center justify-between my-1">
<span <span
>{{ t("chatDocs.maxLength") }} ({{ >{{ t("chatDocs.maxLength") }} ({{
config.maxInputType == "token" ? "token" : "char" siteConfig.maxInputType == "token" ? "token" : "char"
}})</span }})</span
> >
<input <input
v-model="config.maxInput" v-model="siteConfig.maxInput"
class="border rounded px-2 py-1 w-20" class="border rounded px-2 py-1 w-20"
type="number" type="number"
min="1000" min="1000"
...@@ -425,7 +413,7 @@ const resetSent = () => { ...@@ -425,7 +413,7 @@ const resetSent = () => {
<div class="flex items-center justify-between my-1"> <div class="flex items-center justify-between my-1">
<span>{{ t("chatDocs.maxSendings") }}</span> <span>{{ t("chatDocs.maxSendings") }}</span>
<input <input
v-model="config.maxRuns" v-model="siteConfig.maxRuns"
class="border rounded px-2 py-1 w-20" class="border rounded px-2 py-1 w-20"
type="number" type="number"
min="1" min="1"
...@@ -518,7 +506,7 @@ const resetSent = () => { ...@@ -518,7 +506,7 @@ const resetSent = () => {
{{ sendTask.error }} {{ sendTask.error }}
</p> </p>
<p <p
v-if="!config.selector" v-if="!siteConfig.selector"
class="px-3 py-1 border rounded border-amber-400/60 mb-4" class="px-3 py-1 border rounded border-amber-400/60 mb-4"
> >
{{ t("chatDocs.notSupported") }} {{ t("chatDocs.notSupported") }}
...@@ -610,7 +598,7 @@ const resetSent = () => { ...@@ -610,7 +598,7 @@ const resetSent = () => {
'scrollbar border border-foreground/20 w-full h-36 p-2 bg-background-soft', 'scrollbar border border-foreground/20 w-full h-36 p-2 bg-background-soft',
'outline-none rounded', 'outline-none rounded',
]" ]"
v-model="config.prompt" v-model="siteConfig.prompt"
></textarea> ></textarea>
<div class="flex gap-2 justify-end my-2"> <div class="flex gap-2 justify-end my-2">
<button class="px-2 py-1 bg-foreground/10" @click="sheet = ''"> <button class="px-2 py-1 bg-foreground/10" @click="sheet = ''">
...@@ -647,6 +635,7 @@ input:hover { ...@@ -647,6 +635,7 @@ input:hover {
from { from {
transform: translate(-24px, 0); transform: translate(-24px, 0);
} }
to { to {
transform: translate(100%, 0); transform: translate(100%, 0);
} }
......
import type { chatDocsPanel } from "@/store" import type { chatDocsPanel } from "@/store"
export const sitesConfig = [ type Selector = {
{ input: string
host: "huggingface.co", send: string
path: /^\/chat/, wait: string | { target: string; match: string }[]
maxInputLength: 8000, // 2048 token }
selector: {
input: "form[tabindex] textarea", export interface SiteConfig {
send: 'form[tabindex] button[type="submit"]', prompt: string
wait: 'form[tabindex] button[type="submit"]:not([disabled=true])', maxInput: number
}, maxInputType: "char" | "token"
}, maxRuns: number
{ selector: Selector | null
host: "chat.openai.com", }
path: /./,
maxInputLength: 8000, export const devConfig = {
maxInputToken: 4096, host: chrome.runtime.id + "-",
selector: { path: "^/dev.html",
input: "form textarea#prompt-textarea", maxInputLength: 8000,
send: "form textarea ~ button", maxInputToken: 4096,
wait: "form textarea ~ button", selector: {
}, input: "form textarea#prompt-textarea",
}, send: "form textarea ~ button",
{ wait: "form textarea ~ button",
host: "bard.google.com",
path: /chat/,
maxInputLength: 4096,
selector: {
input: "input-area rich-textarea div",
send: "input-area div[class*=send] button[class*=send]",
wait: "input-area div[class*=send] button[class*=send]",
},
},
{
host: "copilot.microsoft.com",
path: /./,
maxInputLength: 2048,
selector: {
input:
"cib-serp /deep/ cib-action-bar /deep/ cib-text-input /deep/ textarea",
send: "cib-serp /deep/ cib-action-bar /deep/ .bottom-right-controls button",
wait: "cib-serp /deep/ cib-action-bar /deep/ cib-typing-indicator /deep/ button[disabled]",
},
},
{
host: "yiyan.baidu.com",
path: /./,
maxInputLength: 2000,
selector: {
input: "textarea:not(h1 ~ textarea)",
send: 'div > span:has(svg[width="240"])',
wait: 'div > span:has(svg[width="240"]):not([style*="display: none"])',
},
},
{
host: chrome.runtime.id + "-",
path: /^\/dev.html/,
maxInputLength: 8000,
maxInputToken: 4096,
selector: {
input: "form textarea#prompt-textarea",
send: "form textarea ~ button",
wait: "form textarea ~ button",
},
}, },
] }
export async function getDocItem(itemList: DataTransferItemList | FileList) { export async function getDocItem(itemList: DataTransferItemList | FileList) {
const items: typeof chatDocsPanel.inputs = [] const items: typeof chatDocsPanel.inputs = []
......
import "@/content/index" import "@/content/index"
// import "@/pages/popup" import "@/pages/popup"
import { testFirebase } from "@/utils/firebase" import { testFirebase } from "@/utils/firebase"
......
...@@ -9,12 +9,12 @@ import cl100k_base from "tiktoken/encoders/cl100k_base.json" ...@@ -9,12 +9,12 @@ import cl100k_base from "tiktoken/encoders/cl100k_base.json"
pdfjs.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.js" pdfjs.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.js"
const service = new TurndownService({ headingStyle: "atx" })
async function parsePdf(file: File) { async function parsePdf(file: File) {
const buffer = await file.arrayBuffer() const buffer = await file.arrayBuffer()
const task = pdfjs.getDocument(buffer) const task = pdfjs.getDocument(buffer)
const pdf = await task.promise const pdf = await task.promise
const service = new TurndownService({ headingStyle: "atx" })
const contents = [] const contents = []
...@@ -38,6 +38,7 @@ async function parseDocx(file: File) { ...@@ -38,6 +38,7 @@ async function parseDocx(file: File) {
) )
console.log("result: ", result) console.log("result: ", result)
const service = new TurndownService({ headingStyle: "atx" })
const markdown = service.turndown(result.value) const markdown = service.turndown(result.value)
return [markdown] return [markdown]
} }
......
...@@ -98,7 +98,8 @@ export async function checkContent(tabId: number) { ...@@ -98,7 +98,8 @@ export async function checkContent(tabId: number) {
await resMsgPromise; await resMsgPromise;
} }
} catch (err) { } catch (err) {
console.error(err); console.warn(err);
console.log("content is not available")
} }
console.log("checkContent alive: ", alive); console.log("checkContent alive: ", alive);
...@@ -172,3 +173,11 @@ export function getStoreUrl(options: StoreUrlOptions) { ...@@ -172,3 +173,11 @@ export function getStoreUrl(options: StoreUrlOptions) {
id: "lbeehbkcmjaopnlccpjcdgamcabhnanl", id: "lbeehbkcmjaopnlccpjcdgamcabhnanl",
}) })
} }
export function getLocal<T extends Record<string, any>>(key: string | T,) {
return chrome.storage.local.get(key) as Promise<T>
}
export function getSession<T extends Record<string, any>>(key: string | T) {
return chrome.storage.session.get(key) as Promise<T>
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"baseUrl": ".", "baseUrl": ".",
"resolveJsonModule": true,
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment