Update localization messages for Chinese, English, and Japanese languages
This commit is contained in:
parent
36c1cae5fb
commit
9eaa0c9d66
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"embed": "It may take a few minutes to embed the page. Please wait..."
|
"embed": "It may take a few minutes to embed the page. Please wait...",
|
||||||
|
"clear": "Erase chat history",
|
||||||
|
"history": "Chat history"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
import { SaveButton } from "@/components/Common/SaveButton"
|
import { SaveButton } from "@/components/Common/SaveButton"
|
||||||
import { getSearchSettings, setSearchSettings } from "@/services/search"
|
|
||||||
import { getTTSSettings, setTTSSettings } from "@/services/tts"
|
import { getTTSSettings, setTTSSettings } from "@/services/tts"
|
||||||
import { useWebUI } from "@/store/webui"
|
import { useWebUI } from "@/store/webui"
|
||||||
import { SUPPORTED_SERACH_PROVIDERS } from "@/utils/search-provider"
|
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Select, Skeleton, Switch, InputNumber } from "antd"
|
import { Select, Skeleton, Switch } from "antd"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
export const TTSModeSettings = ({ hideTitle }: { hideTitle?: boolean }) => {
|
export const TTSModeSettings = ({ hideBorder }: { hideBorder?: boolean }) => {
|
||||||
const { t } = useTranslation("settings")
|
const { t } = useTranslation("settings")
|
||||||
const { setTTSEnabled } = useWebUI()
|
const { setTTSEnabled } = useWebUI()
|
||||||
|
|
||||||
@ -36,14 +34,14 @@ export const TTSModeSettings = ({ hideTitle }: { hideTitle?: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{!hideTitle && (
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
||||||
{t("generalSettings.tts.heading")}
|
{t("generalSettings.tts.heading")}
|
||||||
</h2>
|
</h2>
|
||||||
|
{!hideBorder && (
|
||||||
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit(async (values) => {
|
onSubmit={form.onSubmit(async (values) => {
|
||||||
await setTTSSettings(values)
|
await setTTSSettings(values)
|
||||||
|
@ -2,10 +2,12 @@ import React from "react"
|
|||||||
import { PlaygroundMessage } from "~/components/Common/Playground/Message"
|
import { PlaygroundMessage } from "~/components/Common/Playground/Message"
|
||||||
import { useMessage } from "~/hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { EmptySidePanel } from "../Chat/empty"
|
import { EmptySidePanel } from "../Chat/empty"
|
||||||
|
import { useWebUI } from "@/store/webui"
|
||||||
|
|
||||||
export const SidePanelBody = () => {
|
export const SidePanelBody = () => {
|
||||||
const { messages, streaming } = useMessage()
|
const { messages, streaming } = useMessage()
|
||||||
const divRef = React.useRef<HTMLDivElement>(null)
|
const divRef = React.useRef<HTMLDivElement>(null)
|
||||||
|
const {ttsEnabled} = useWebUI()
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (divRef.current) {
|
if (divRef.current) {
|
||||||
divRef.current.scrollIntoView({ behavior: "smooth" })
|
divRef.current.scrollIntoView({ behavior: "smooth" })
|
||||||
@ -27,6 +29,7 @@ export const SidePanelBody = () => {
|
|||||||
onRengerate={() => {}}
|
onRengerate={() => {}}
|
||||||
isProcessing={streaming}
|
isProcessing={streaming}
|
||||||
hideEditAndRegenerate
|
hideEditAndRegenerate
|
||||||
|
isTTSEnabled={ttsEnabled}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="w-full h-32 md:h-48 flex-shrink-0"></div>
|
<div className="w-full h-32 md:h-48 flex-shrink-0"></div>
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
getAllModels,
|
getAllModels,
|
||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
isOllamaRunning,
|
isOllamaRunning,
|
||||||
setOllamaURL as saveOllamaURL
|
setOllamaURL as saveOllamaURL,
|
||||||
|
fetchChatModels
|
||||||
} from "~/services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const EmptySidePanel = () => {
|
export const EmptySidePanel = () => {
|
||||||
@ -24,7 +25,7 @@ export const EmptySidePanel = () => {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const ollamaURL = await getOllamaURL()
|
const ollamaURL = await getOllamaURL()
|
||||||
const isOk = await isOllamaRunning()
|
const isOk = await isOllamaRunning()
|
||||||
const models = await getAllModels({ returnEmpty: false })
|
const models = await fetchChatModels({ returnEmpty: false })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOk,
|
isOk,
|
||||||
|
@ -2,10 +2,10 @@ import logoImage from "~/assets/icon.png"
|
|||||||
import { useMessage } from "~/hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import { Tooltip } from "antd"
|
import { Tooltip } from "antd"
|
||||||
import { BoxesIcon, CogIcon, RefreshCcw } from "lucide-react"
|
import { BoxesIcon, CogIcon, EraserIcon, HistoryIcon } from "lucide-react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
export const SidepanelHeader = () => {
|
export const SidepanelHeader = () => {
|
||||||
const { clearChat, isEmbedding } = useMessage()
|
const { clearChat, isEmbedding, messages } = useMessage()
|
||||||
const { t } = useTranslation(["sidepanel", "common"])
|
const { t } = useTranslation(["sidepanel", "common"])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,13 +25,22 @@ export const SidepanelHeader = () => {
|
|||||||
<BoxesIcon className="h-5 w-5 text-gray-500 dark:text-gray-400 animate-bounce animate-infinite" />
|
<BoxesIcon className="h-5 w-5 text-gray-500 dark:text-gray-400 animate-bounce animate-infinite" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
|
{messages.length > 0 && (
|
||||||
|
<Tooltip title={t("tooltip.clear")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clearChat()
|
clearChat()
|
||||||
}}
|
}}
|
||||||
className="flex items-center space-x-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-pink-700">
|
className="flex items-center space-x-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-pink-700">
|
||||||
<RefreshCcw className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
<EraserIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{/* <Tooltip title={t("tooltip.history")}>
|
||||||
|
<Link to="/history">
|
||||||
|
<HistoryIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
||||||
|
</Link>
|
||||||
|
</Tooltip> */}
|
||||||
<Link to="/settings">
|
<Link to="/settings">
|
||||||
<CogIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
<CogIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -22,6 +22,7 @@ import { useMessage } from "~/hooks/useMessage"
|
|||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useI18n } from "@/hooks/useI18n"
|
import { useI18n } from "@/hooks/useI18n"
|
||||||
|
import { TTSModeSettings } from "@/components/Option/Settings/tts-mode"
|
||||||
|
|
||||||
export const SettingsBody = () => {
|
export const SettingsBody = () => {
|
||||||
const { t } = useTranslation("settings")
|
const { t } = useTranslation("settings")
|
||||||
@ -285,6 +286,9 @@ export const SettingsBody = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
|
||||||
|
<TTSModeSettings hideBorder />
|
||||||
|
</div>
|
||||||
<div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
|
<div className="border border-gray-300 dark:border-gray-700 rounded p-4 bg-white dark:bg-[#171717]">
|
||||||
<h2 className="text-md mb-4 font-semibold dark:text-white">
|
<h2 className="text-md mb-4 font-semibold dark:text-white">
|
||||||
{t("generalSettings.settings.language.label")}{" "}
|
{t("generalSettings.settings.language.label")}{" "}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
|
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
|
||||||
|
import { Storage } from "@plasmohq/storage"
|
||||||
|
|
||||||
const progressHuman = (completed: number, total: number) => {
|
const progressHuman = (completed: number, total: number) => {
|
||||||
return ((completed / total) * 100).toFixed(0) + "%"
|
return ((completed / total) * 100).toFixed(0) + "%"
|
||||||
}
|
}
|
||||||
@ -75,6 +77,8 @@ const streamDownload = async (url: string, model: string) => {
|
|||||||
}
|
}
|
||||||
export default defineBackground({
|
export default defineBackground({
|
||||||
main() {
|
main() {
|
||||||
|
const storage = new Storage()
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(async (message) => {
|
chrome.runtime.onMessage.addListener(async (message) => {
|
||||||
if (message.type === "sidepanel") {
|
if (message.type === "sidepanel") {
|
||||||
chrome.tabs.query(
|
chrome.tabs.query(
|
||||||
@ -139,8 +143,8 @@ export default defineBackground({
|
|||||||
{ active: true, currentWindow: true },
|
{ active: true, currentWindow: true },
|
||||||
async (tabs) => {
|
async (tabs) => {
|
||||||
const tab = tabs[0]
|
const tab = tabs[0]
|
||||||
await chrome.sidePanel.open({
|
chrome.sidePanel.open({
|
||||||
windowId: tab.windowId!
|
tabId: tab.id!
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ import { Document } from "@langchain/core/documents"
|
|||||||
import { compile } from "html-to-text"
|
import { compile } from "html-to-text"
|
||||||
import { chromeRunTime } from "~/libs/runtime"
|
import { chromeRunTime } from "~/libs/runtime"
|
||||||
import { YtTranscript } from "yt-transcript"
|
import { YtTranscript } from "yt-transcript"
|
||||||
|
import { isWikipedia, parseWikipedia } from "@/parser/wiki"
|
||||||
|
|
||||||
const YT_REGEX =
|
const YT_REGEX =
|
||||||
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9_-]+)/
|
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9_-]+)/
|
||||||
@ -16,7 +17,6 @@ const getTranscript = async (url: string) => {
|
|||||||
return await ytTranscript.getTranscript()
|
return await ytTranscript.getTranscript()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface WebLoaderParams {
|
export interface WebLoaderParams {
|
||||||
html: string
|
html: string
|
||||||
url: string
|
url: string
|
||||||
@ -24,7 +24,8 @@ export interface WebLoaderParams {
|
|||||||
|
|
||||||
export class PageAssistHtmlLoader
|
export class PageAssistHtmlLoader
|
||||||
extends BaseDocumentLoader
|
extends BaseDocumentLoader
|
||||||
implements WebLoaderParams {
|
implements WebLoaderParams
|
||||||
|
{
|
||||||
html: string
|
html: string
|
||||||
url: string
|
url: string
|
||||||
|
|
||||||
@ -47,7 +48,6 @@ export class PageAssistHtmlLoader
|
|||||||
text += item.text + " "
|
text += item.text + " "
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -58,10 +58,23 @@ export class PageAssistHtmlLoader
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let html = this.html
|
||||||
|
|
||||||
|
if (isWikipedia(this.url)) {
|
||||||
|
console.log("Wikipedia URL detected")
|
||||||
|
html = parseWikipedia(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
// else if (isTwitter(this.url)) {
|
||||||
|
// console.log("Twitter URL detected")
|
||||||
|
// html = parseTweet(html, this.url)
|
||||||
|
// }
|
||||||
|
|
||||||
const htmlCompiler = compile({
|
const htmlCompiler = compile({
|
||||||
wordwrap: false
|
wordwrap: false
|
||||||
})
|
})
|
||||||
const text = htmlCompiler(this.html)
|
const text = htmlCompiler(html)
|
||||||
const metadata = { source: this.url }
|
const metadata = { source: this.url }
|
||||||
return [new Document({ pageContent: text, metadata })]
|
return [new Document({ pageContent: text, metadata })]
|
||||||
}
|
}
|
||||||
@ -79,7 +92,6 @@ export class PageAssistHtmlLoader
|
|||||||
text += item.text + " "
|
text += item.text + " "
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -92,7 +104,18 @@ export class PageAssistHtmlLoader
|
|||||||
}
|
}
|
||||||
await chromeRunTime(this.url)
|
await chromeRunTime(this.url)
|
||||||
const fetchHTML = await fetch(this.url)
|
const fetchHTML = await fetch(this.url)
|
||||||
const html = await fetchHTML.text()
|
let html = await fetchHTML.text()
|
||||||
|
|
||||||
|
if (isWikipedia(this.url)) {
|
||||||
|
console.log("Wikipedia URL detected")
|
||||||
|
html = parseWikipedia(await fetchHTML.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
// else if (isTwitter(this.url)) {
|
||||||
|
// console.log("Twitter URL detected")
|
||||||
|
// html = parseTweet(await fetchHTML.text(), this.url)
|
||||||
|
// }
|
||||||
|
|
||||||
const htmlCompiler = compile({
|
const htmlCompiler = compile({
|
||||||
wordwrap: false,
|
wordwrap: false,
|
||||||
selectors: [
|
selectors: [
|
||||||
|
90
src/parser/twitter.ts
Normal file
90
src/parser/twitter.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import * as cheerio from "cheerio"
|
||||||
|
|
||||||
|
export const isTweet = (url: string) => {
|
||||||
|
const TWEET_REGEX = /twitter\.com\/[a-zA-Z0-9_]+\/status\/[0-9]+/g
|
||||||
|
return TWEET_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTwitterProfile = (url: string) => {
|
||||||
|
const PROFILE_REGEX = /twitter\.com\/[a-zA-Z0-9_]+/g
|
||||||
|
return PROFILE_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTwitterTimeline = (url: string) => {
|
||||||
|
const TIMELINE_REGEX = /twitter\.com\/home/g
|
||||||
|
return TIMELINE_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTwitter = (url: string) => {
|
||||||
|
return isTweet(url) || isTwitterProfile(url) || isTwitterTimeline(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTwitterNotification = (url: string) => {
|
||||||
|
const NOTIFICATION_REGEX = /twitter\.com\/notifications/g
|
||||||
|
return NOTIFICATION_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseTweet = (html: string, url: string) => {
|
||||||
|
if (!html) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html)
|
||||||
|
|
||||||
|
if (isTweet(url)) {
|
||||||
|
console.log("tweet")
|
||||||
|
const tweet = $("div[data-testid='tweet']")
|
||||||
|
const tweetContent = tweet.find("div[lang]")
|
||||||
|
const tweetMedia = tweet.find("div[role='group']")
|
||||||
|
const author = tweet.find("a[role='link']").text()
|
||||||
|
const date = tweet.find("time").text()
|
||||||
|
return `<div>${author} ${tweetContent.text()} ${tweetMedia.html()} ${date}</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTwitterTimeline(url)) {
|
||||||
|
console.log("timeline")
|
||||||
|
const timeline = $("div[data-testid='primaryColumn']")
|
||||||
|
const timelineContent = timeline.find("div[data-testid='tweet']")
|
||||||
|
console.log(timelineContent.html())
|
||||||
|
const tweet = timelineContent
|
||||||
|
.map((i, el) => {
|
||||||
|
const author = $(el).find("a[role='link']").text()
|
||||||
|
const content = $(el).find("div[lang]").text()
|
||||||
|
const media = $(el).find("div[role='group']").html()
|
||||||
|
const date = $(el).find("time").text()
|
||||||
|
return `<div>${author} ${content} ${media} ${date}</div>`
|
||||||
|
})
|
||||||
|
.get()
|
||||||
|
.join("")
|
||||||
|
console.log(tweet)
|
||||||
|
return `<div>${tweet}</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTwitterNotification(url)) {
|
||||||
|
console.log("notification")
|
||||||
|
const notification = $("div[data-testid='primaryColumn']")
|
||||||
|
const notificationContent = notification.find("div[data-testid='tweet']")
|
||||||
|
return `<div>${notificationContent.html()}</div>`
|
||||||
|
}
|
||||||
|
if (isTwitterProfile(url)) {
|
||||||
|
console.log("profile")
|
||||||
|
const profile = $("div[data-testid='primaryColumn']")
|
||||||
|
const profileContent = profile.find(
|
||||||
|
"div[data-testid='UserProfileHeader_Items']"
|
||||||
|
)
|
||||||
|
const profileTweets = profile.find("div[data-testid='tweet']")
|
||||||
|
return `<div>${profileContent.html()}</div><div>${profileTweets.html()}</div>`
|
||||||
|
}
|
||||||
|
console.log("no match")
|
||||||
|
const timeline = $("div[data-testid='primaryColumn']")
|
||||||
|
const timelineContent = timeline.find("div[data-testid='tweet']")
|
||||||
|
const tweet = timelineContent.map((i, el) => {
|
||||||
|
const author = $(el).find("a[role='link']").text()
|
||||||
|
const content = $(el).find("div[lang]").text()
|
||||||
|
const media = $(el).find("div[role='group']").html()
|
||||||
|
const date = $(el).find("time").text()
|
||||||
|
return `<div>${author} ${content} ${media} ${date}</div>`
|
||||||
|
})
|
||||||
|
|
||||||
|
return `<div>${tweet}</div>`
|
||||||
|
}
|
28
src/parser/wiki.ts
Normal file
28
src/parser/wiki.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import * as cheerio from "cheerio"
|
||||||
|
|
||||||
|
export const isWikipedia = (url: string) => {
|
||||||
|
const WIKI_REGEX = /wikipedia\.org\/wiki\//g
|
||||||
|
return WIKI_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseWikipedia = (html: string) => {
|
||||||
|
if (!html) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const $ = cheerio.load(html)
|
||||||
|
const title = $("h1#firstHeading")
|
||||||
|
const content = $("#mw-content-text")
|
||||||
|
content?.find("sup.reference")?.remove()
|
||||||
|
content?.find("div.thumb")?.remove()
|
||||||
|
content?.find("div.reflist")?.remove()
|
||||||
|
content?.find("div.navbox")?.remove()
|
||||||
|
content?.find("table.infobox")?.remove()
|
||||||
|
content?.find("div.sister-wikipedia")?.remove()
|
||||||
|
content?.find("div.sister-projects")?.remove()
|
||||||
|
content?.find("div.metadata")?.remove()
|
||||||
|
content?.find("div.vertical-navbox")?.remove()
|
||||||
|
content?.find("div.toc")?.remove()
|
||||||
|
const newHtml = content?.html()
|
||||||
|
|
||||||
|
return `<div>TITLE: ${title?.text()}</div><div>${newHtml}</div>`
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user