Commit 62715bb0 authored by Domi's avatar Domi

fix: chatdocs addon (better support for Bard)

parent 66213a6e
...@@ -12,7 +12,13 @@ import IconPlayCircle from "../icons/IconPlayCircle.vue" ...@@ -12,7 +12,13 @@ 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 { sitesConfig } from "./helper"
import { query, dispatchInput, click, waitFor } from "@/utils/dom" import {
query,
dispatchInput,
click,
waitFor,
getInputValue,
} from "@/utils/dom"
import { chatDocPrompt } from "@/utils/prompt" import { chatDocPrompt } from "@/utils/prompt"
import { useI18n } from "@/utils/i18n" import { useI18n } from "@/utils/i18n"
...@@ -295,6 +301,8 @@ const autoSend = async () => { ...@@ -295,6 +301,8 @@ const autoSend = async () => {
sendTask.key = key sendTask.key = key
sendTask.status = "running" sendTask.status = "running"
const selector = config.selector
const isWorking = () => const isWorking = () =>
!currentMessage.value.done && !currentMessage.value.done &&
sendTask.status == "running" && sendTask.status == "running" &&
...@@ -306,15 +314,28 @@ const autoSend = async () => { ...@@ -306,15 +314,28 @@ const autoSend = async () => {
console.log(">>", message) console.log(">>", message)
await new Promise((r) => setTimeout(r, 100)) await new Promise((r) => setTimeout(r, 100))
const input = query(config.selector.input) as HTMLInputElement const input = query(selector.input) as HTMLElement
if (!input) { if (!input) {
throw Error("couldn't find input element for " + config.selector.input) throw Error("couldn't find input element for " + selector.input)
} }
await dispatchInput(input, message) await dispatchInput(input, message)
await new Promise((r) => setTimeout(r, 200))
await click(config.selector.send)
await new Promise((r) => setTimeout(r, 600)) await new Promise((r) => setTimeout(r, 600))
await waitFor(config.selector.wait, 1000 * 30) await click(selector.send)
await waitFor(
async (i) => {
const input = query(selector.input) as HTMLElement
const value = input ? getInputValue(input) : ""
const sented = value.length < 100
if (!sented && i % 3 == 0) {
await click(selector.send)
}
return sented
},
{ interval: 1000 * 2 }
)
await new Promise((r) => setTimeout(r, 200))
await waitFor(() => query(selector.wait) != null, {})
await new Promise((r) => setTimeout(r, 200)) await new Promise((r) => setTimeout(r, 200))
await nextMessage() await nextMessage()
...@@ -491,7 +512,7 @@ const resetSent = () => { ...@@ -491,7 +512,7 @@ const resetSent = () => {
v-if="sendTask.error" v-if="sendTask.error"
:class="[ :class="[
'text-rose-600 bg-rose-200/10 border border-rose-600 px-3', 'text-rose-600 bg-rose-200/10 border border-rose-600 px-3',
'mb-4 rounded py-1', 'mb-4 rounded py-1 break-words',
]" ]"
> >
{{ sendTask.error }} {{ sendTask.error }}
......
...@@ -27,9 +27,9 @@ export const sitesConfig = [ ...@@ -27,9 +27,9 @@ export const sitesConfig = [
path: /chat/, path: /chat/,
maxInputLength: 4096, maxInputLength: 4096,
selector: { selector: {
input: "input-area rich-textarea p", input: "input-area rich-textarea div",
send: "input-area div[class*=send] button[class*=send]", send: "input-area div[class*=send] button[class*=send]",
wait: "input-area div[class*=send] button[class*=send]:not([hidden])", wait: "input-area div[class*=send] button[class*=send]",
}, },
}, },
{ {
...@@ -54,7 +54,7 @@ export const sitesConfig = [ ...@@ -54,7 +54,7 @@ export const sitesConfig = [
}, },
}, },
{ {
host: chrome.runtime.id + '-', host: chrome.runtime.id + "-",
path: /^\/dev.html/, path: /^\/dev.html/,
maxInputLength: 8000, maxInputLength: 8000,
maxInputToken: 4096, maxInputToken: 4096,
......
...@@ -3,6 +3,7 @@ import { ...@@ -3,6 +3,7 @@ import {
querySome, querySome,
copyStyleSheets, copyStyleSheets,
getDomNonce, getDomNonce,
getTrustedHTML,
replaceHtmlNonce, replaceHtmlNonce,
removePrerenderRules, removePrerenderRules,
} from "@/utils/dom" } from "@/utils/dom"
...@@ -117,13 +118,7 @@ export async function copilotReopen({ url, width, height }: ReopenOptions) { ...@@ -117,13 +118,7 @@ export async function copilotReopen({ url, width, height }: ReopenOptions) {
function writeHtml(pipWindow: Window, html: string) { function writeHtml(pipWindow: Window, html: string) {
const nonce = getDomNonce(document) const nonce = getDomNonce(document)
let escaped = replaceHtmlNonce(html, nonce) let escaped = replaceHtmlNonce(html, nonce)
escaped = getTrustedHTML(escaped)
if (window.trustedTypes) {
const escapeHTMLPolicy = window.trustedTypes.createPolicy("escapePolicy", {
createHTML: (string: string) => string,
})
escaped = escapeHTMLPolicy.createHTML(escaped)
}
pipWindow.document.open() pipWindow.document.open()
pipWindow.document.write(escaped) pipWindow.document.write(escaped)
......
...@@ -10,7 +10,7 @@ const manifest = { ...@@ -10,7 +10,7 @@ const manifest = {
// short_name: "__MSG_short_name__", // short_name: "__MSG_short_name__",
// no more than 132 characters // no more than 132 characters
description: "__MSG_description__", description: "__MSG_description__",
version: "1.2.0", version: "1.2.1",
action: { action: {
default_icon: { default_icon: {
16: "logo.png", 16: "logo.png",
......
...@@ -157,38 +157,61 @@ export function removePrerenderRules(doc: Document) { ...@@ -157,38 +157,61 @@ export function removePrerenderRules(doc: Document) {
} }
} }
export async function dispatchInput( export function getTrustedHTML(html: string) {
input: HTMLInputElement | HTMLTextAreaElement, if ("window" in globalThis) {
value: string const policy = window.trustedTypes.createPolicy("trustedPolicy", {
) { createHTML: (str: string) => str,
})
return policy.createHTML(html)
}
return html
}
export async function dispatchInput(input: HTMLElement, value: string) {
if (["INPUT", "TEXTAREA"].includes(input.nodeName)) { if (["INPUT", "TEXTAREA"].includes(input.nodeName)) {
input.value = value ;(input as HTMLInputElement).value = value
} else if (input.contentEditable) { } else if (input.contentEditable) {
input.innerText = value const html = value.replace(/\n/g, "<br />")
input.innerHTML = html
} }
input.dispatchEvent(new Event("input", { bubbles: true })) input.dispatchEvent(new Event("input", { bubbles: true }))
input.dispatchEvent(new Event("change", { bubbles: true })) input.dispatchEvent(new Event("change", { bubbles: true }))
} }
export function getInputValue(input: HTMLElement) {
if (["INPUT", "TEXTAREA"].includes(input.nodeName)) {
return (input as HTMLInputElement).value
} else if (input.contentEditable) {
const value = input.innerText
return value
}
return ""
}
export async function click(target: string) { export async function click(target: string) {
const el = query(target) as HTMLElement const el = query(target) as HTMLElement
el.click() el.click()
} }
export async function waitFor(target: string, timeout?: number) { export async function waitFor(
test: (i: number) => PromiseLike<boolean> | boolean,
options: { timeout?: number; interval?: number }
) {
let success = false let success = false
let count = 0 let count = 0
const t0 = Date.now() const t0 = Date.now()
const timeout = options.timeout || 1000 * 30
const interval = options.interval || 600
while (!success) { while (!success) {
const view = query(target) const result = await test(count)
success = !!view success = !!result
count++ count++
await new Promise((r) => setTimeout(r, 200)) await new Promise((r) => setTimeout(r, interval))
if (timeout && Date.now() - t0 > timeout) { if (timeout && Date.now() - t0 > timeout) {
throw Error(`click() timeout, target: ${target} wait: ${waitFor}`) throw Error(`waitFor timeout, test: ${test}`)
} }
} }
} }
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