diff --git a/entrypoints/background.js b/entrypoints/background.js index 76e2ef6..df7e07c 100644 --- a/entrypoints/background.js +++ b/entrypoints/background.js @@ -1,6 +1,32 @@ import { browser } from "wxt/browser"; export default defineBackground(() => { + // browser.contextMenus.create({ + // id: "explain", + // title: "问问AI", + // contexts: ["selection"] + // }); + + // browser.contextMenus.create({ + // id: "summarize", + // title: "总结全文", + // contexts: ["page"] + // }); + + // browser.contextMenus.onClicked.addListener(async (info, tab) => { + // browser.scripting.executeScript({ + // target: { tabId: tab.id }, + // files: ['inject.js'], + // // args: [info] + // }); + // if (info.menuItemId === "explain") { + // // 处理 AI 讲解逻辑 + + // } else if (info.menuItemId === "summarize") { + // // 处理 总结全文 逻辑 + // console.log("总结全文:", tab.url); + // } + // }); browser.action.onClicked.addListener(function () { browser.tabs.create({ url: browser.runtime.getURL("ext.html") }); }); diff --git a/entrypoints/content/App.jsx b/entrypoints/content/App.jsx new file mode 100644 index 0000000..d7117cb --- /dev/null +++ b/entrypoints/content/App.jsx @@ -0,0 +1,74 @@ +import { useState, useEffect } from 'react'; +import Modal from './components/Modal'; +import Logo from '@/public/logo.svg'; + +const getPageContext = () => { + return { + content: document.body.innerText, + title: document.title, + keywords: document.querySelector('meta[name="keywords"]')?.content, + description: document.querySelector('meta[name="description"]')?.content, + }; +}; + +export default ({ ctx }) => { + const [showPage, setShowPage] = useState(false); + const [showButton, setShowButton] = useState(false); + const [buttonPosition, setButtonPosition] = useState({ x: 0, y: 0 }); + const [selection, setSelection] = useState(''); + const [payload, setPayload] = useState({}); + + useEffect(() => { + const handleSelection = e => { + const selection = window.getSelection(); + if (selection.toString().length > 0) { + setSelection(selection.toString()); + setShowButton(true); + setButtonPosition({ x: e.clientX + 6, y: e.clientY + 8 }); + } else { + setShowButton(false); + } + }; + + document.addEventListener('mouseup', handleSelection); + return () => document.removeEventListener('mouseup', handleSelection); + }, []); + + useEffect(() => { + if (showPage) { + document.querySelector('html').style.overflow = 'hidden'; + } else { + document.querySelector('html').style.overflow = 'auto'; + } + }, [showPage]); + + const handleButtonClick = () => { + setPayload({ selection, type: 'query', context: getPageContext() }); + setShowPage(true); + setShowButton(false); + }; + + return ( +
+ setShowPage(false)} + payload={payload} + /> +
+ +
+
+ ); +}; diff --git a/entrypoints/content/components/Modal.jsx b/entrypoints/content/components/Modal.jsx new file mode 100644 index 0000000..676e04a --- /dev/null +++ b/entrypoints/content/components/Modal.jsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; +import { useState } from 'react'; + +export default function ({ close, payload, visible }) { + const url = browser.runtime.getURL('ext.html') + '#web-copilot'; + const [loaded, setLoaded] = useState(false); + const iframeRef = useRef(null); + + useEffect(() => { + if (!visible) { + iframeRef.current.contentWindow.postMessage( + JSON.stringify({ type: 'clear' }), + '*' + ); + return; + } + if (payload.type) { + iframeRef.current.contentWindow.postMessage(JSON.stringify(payload), '*'); + } + }, [payload, loaded, visible]); + return ( +
+ +
+ ); +} diff --git a/entrypoints/content/index.jsx b/entrypoints/content/index.jsx new file mode 100644 index 0000000..291a4b5 --- /dev/null +++ b/entrypoints/content/index.jsx @@ -0,0 +1,36 @@ +import './style.css'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +export default defineContentScript({ + matches: ['*://*/*'], + cssInjectionMode: 'ui', + + async main(ctx) { + console.log('content script injecting', ctx); + const ui = await createShadowRootUi(ctx, { + name: 'silo-ui', + position: 'overlay', + alignment: 'bottom-right', + zIndex: 99999, + anchor: 'body', + append: 'first', + onMount: container => { + // Don't mount react app directly on + const wrapper = document.createElement('div'); + wrapper.className = ''; + container.append(wrapper); + + const root = ReactDOM.createRoot(wrapper); + root.render(); + return { root, wrapper }; + }, + onRemove: elements => { + elements?.root.unmount(); + elements?.wrapper.remove(); + }, + }); + + ui.mount(); + }, +}); diff --git a/entrypoints/content/style.css b/entrypoints/content/style.css new file mode 100644 index 0000000..acd26e8 --- /dev/null +++ b/entrypoints/content/style.css @@ -0,0 +1,23 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +* { + padding: 0; + margin: 0; +} + +body { + padding: 16px; +} + +.selected-search-box { + z-index: 300; + position: absolute; + cursor: pointer; + border: 0; + background: #fff; + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.1); + border-radius: 6px; + padding: 10px 15px 9px 16px; +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..611a41c --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@src/*": [ + "./src/*" + ] + }, + "jsx": "react" + } +} \ No newline at end of file diff --git a/package.json b/package.json index b5bd4cd..71515a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "silo", "private": true, - "version": "1.3.1", + "version": "1.4.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.jsx b/src/App.jsx index 85658d9..a817e4d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,23 +5,29 @@ import { Routes, Route, Navigate, - BrowserRouter as Router, + HashRouter as Router, } from 'react-router-dom'; import ImageGenerate from './pages/image-generate'; import { useIsImageMode } from './store/storage'; +import WebCopilot from './pages/web-copilot'; function App() { const [isImageMode] = useIsImageMode(); return (
- - {isImageMode ? : } - {/* - } /> - } /> - } /> - */} + + + + {isImageMode ? : } + + } + /> + } /> +
); diff --git a/src/components/CustomModelDrawer/CustomModelForm.jsx b/src/components/CustomModelDrawer/CustomModelForm.jsx index 9990bba..50ddeba 100644 --- a/src/components/CustomModelDrawer/CustomModelForm.jsx +++ b/src/components/CustomModelDrawer/CustomModelForm.jsx @@ -10,6 +10,7 @@ import { useState } from 'react'; import { Button } from 'tdesign-react'; import { CUSTOM_PRESET_PREFIX } from '../../utils/types'; import { useMemo } from 'react'; +import { isBrowserExtension } from '@src/utils/utils'; const { FormItem } = Form; const ID_REGEX = /^[a-zA-Z0-9_\-@\.]+\/[a-zA-Z0-9_\-@\.\/]+$/; @@ -92,14 +93,14 @@ export default forwardRef((props, ref) => { const isValid = ids.every(item => ID_REGEX.test(item)); return isValid ? Promise.resolve({ - result: true, - }) + result: true, + }) : Promise.resolve({ - result: false, - message: - 'ID格式错误,请检查是否符合: {manufacturer}/{model-name},多个需用英文逗号隔开', - type: 'warning', - }); + result: false, + message: + 'ID格式错误,请检查是否符合: {manufacturer}/{model-name},多个需用英文逗号隔开', + type: 'warning', + }); }, }, ], @@ -208,8 +209,15 @@ export default forwardRef((props, ref) => { ) : ( - +