mirror of
https://github.com/open-webui/extension
synced 2025-06-26 18:25:58 +00:00
refac: migrate to svelte
This commit is contained in:
parent
e26ea0f51b
commit
b1c2547f17
@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
settings: { react: { version: '18.2' } },
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
4
extension/.gitignore
vendored
4
extension/.gitignore
vendored
@ -8,10 +8,14 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
_old
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
|
47
extension/README.md
Normal file
47
extension/README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Svelte + TS + Vite
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
1
extension/dist/index.html
vendored
1
extension/dist/index.html
vendored
@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Open WebUI Extension</title>
|
||||
<script type="module" crossorigin src="/main.js"></script>
|
||||
|
47
extension/dist/main.js
vendored
47
extension/dist/main.js
vendored
File diff suppressed because one or more lines are too long
@ -2,11 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Open WebUI Extension</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="extension-app"></div>
|
||||
<script type="module" src="./src/main.jsx"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
3386
extension/package-lock.json
generated
3386
extension/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,24 +6,19 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"postcss": "^8.4.38",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.7",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
}
|
||||
|
1
extension/public/vite.svg
Normal file
1
extension/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,12 +0,0 @@
|
||||
import { SpotlightSearch } from "./components/SpotlightSearch";
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<div className="extension-container">
|
||||
<SpotlightSearch />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
7
extension/src/App.svelte
Normal file
7
extension/src/App.svelte
Normal file
@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SpotlightSearch from "./lib/components/SpotlightSearch.svelte";
|
||||
</script>
|
||||
|
||||
<div class="extension-container">
|
||||
<SpotlightSearch />
|
||||
</div>
|
@ -1,467 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { generateOpenAIChatCompletion, getModels } from "../apis";
|
||||
import { splitStream } from "../utils";
|
||||
|
||||
export const SpotlightSearch = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [storageCache, setStorageCache] = useState(null);
|
||||
|
||||
const [url, setUrl] = useState("");
|
||||
const [key, setKey] = useState("");
|
||||
const [model, setModel] = useState("");
|
||||
|
||||
const [showConfig, setShowConfig] = useState(true);
|
||||
const [models, setModels] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getStorageCache() {
|
||||
let _storageCache = null;
|
||||
|
||||
try {
|
||||
_storageCache = await chrome.storage.local.get();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setStorageCache(_storageCache);
|
||||
|
||||
if (_storageCache) {
|
||||
setUrl(_storageCache.url ?? "");
|
||||
setKey(_storageCache.key ?? "");
|
||||
setModel(_storageCache.model ?? "");
|
||||
if (_storageCache.url && _storageCache.key && _storageCache.model) {
|
||||
setModels(await getModels(_storageCache.key, _storageCache.url));
|
||||
setShowConfig(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
getStorageCache();
|
||||
}, []);
|
||||
|
||||
const resetConfig = () => {
|
||||
console.log("resetConfig");
|
||||
|
||||
try {
|
||||
chrome.storage.local.clear().then(() => {
|
||||
console.log("Value is cleared");
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
localStorage.setItem("url", "");
|
||||
localStorage.setItem("key", "");
|
||||
localStorage.setItem("model", "");
|
||||
}
|
||||
|
||||
setUrl("");
|
||||
setKey("");
|
||||
setModel("");
|
||||
setShowConfig(true);
|
||||
};
|
||||
|
||||
// Toggle the menu when ⌘Space+Shift is pressed
|
||||
useEffect(() => {
|
||||
const down = async (e) => {
|
||||
// Reset the configuration when ⌘Shift+Escape is pressed
|
||||
if (open && e.shiftKey && e.key === "Escape") {
|
||||
resetConfig();
|
||||
} else if (e.key === "Escape") {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
if (
|
||||
e.key === " " &&
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(e.shiftKey || e.altKey)
|
||||
) {
|
||||
e.preventDefault();
|
||||
setOpen((open) => !open);
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "getSelection",
|
||||
});
|
||||
|
||||
if (response?.data ?? false) {
|
||||
setSearchValue(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("catch", error);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const inputElement = document.getElementById(
|
||||
"open-webui-search-input"
|
||||
);
|
||||
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (key !== "" && url !== "") {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(e.shiftKey || e.altKey)
|
||||
) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "getSelection",
|
||||
});
|
||||
|
||||
if (response?.data ?? false) {
|
||||
await chrome.runtime.sendMessage({
|
||||
action: "writeText",
|
||||
text: "\n",
|
||||
});
|
||||
|
||||
const [res, controller] = await generateOpenAIChatCompletion(
|
||||
key,
|
||||
{
|
||||
model: model,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: response.data,
|
||||
},
|
||||
],
|
||||
stream: true,
|
||||
},
|
||||
models.find((m) => m.id === model)?.url
|
||||
);
|
||||
|
||||
if (res && res.ok) {
|
||||
const reader = res.body
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(splitStream("\n"))
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
let lines = value.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line !== "") {
|
||||
console.log(line);
|
||||
if (line === "data: [DONE]") {
|
||||
console.log("DONE");
|
||||
} else {
|
||||
let data = JSON.parse(line.replace(/^data: /, ""));
|
||||
console.log(data);
|
||||
|
||||
if ("request_id" in data) {
|
||||
console.log(data.request_id);
|
||||
} else {
|
||||
await chrome.runtime.sendMessage({
|
||||
action: "writeText",
|
||||
text: data.choices[0].delta.content ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", down);
|
||||
return () => document.removeEventListener("keydown", down);
|
||||
}, [url, key, model, models, open]);
|
||||
|
||||
const submitHandler = (e) => {
|
||||
e.preventDefault();
|
||||
console.log(searchValue);
|
||||
setSearchValue("");
|
||||
|
||||
window.open(
|
||||
`${url}/?q=${encodeURIComponent(searchValue)}&models=${model}`,
|
||||
"_blank"
|
||||
);
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const initHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
chrome.storage.local
|
||||
.set({ url: url, key: key, model: model })
|
||||
.then(() => {
|
||||
console.log("Value is set");
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
localStorage.setItem("url", url);
|
||||
localStorage.setItem("key", key);
|
||||
localStorage.setItem("model", model);
|
||||
}
|
||||
|
||||
setShowConfig(false);
|
||||
};
|
||||
|
||||
return open ? (
|
||||
<div
|
||||
className="tlwd-fixed tlwd-top-0 tlwd-right-0 tlwd-left-0 tlwd-bottom-0 tlwd-w-full tlwd-min-h-screen tlwd-h-screen tlwd-flex tlwd-justify-center tlwd-z-[9999999999] tlwd-overflow-hidden tlwd-overscroll-contain"
|
||||
onMouseDown={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{showConfig ? (
|
||||
<>
|
||||
<div className=" tlwd-m-auto tlwd-max-w-sm tlwd-w-full tlwd-pb-32">
|
||||
<div className="tlwd-w-full tlwd-flex tlwd-flex-col tlwd-justify-between tlwd-py-2.5 tlwd-px-3.5 tlwd-rounded-2xl tlwd-outline tlwd-outline-1 tlwd-outline-gray-850 tlwd-backdrop-blur-3xl tlwd-bg-gray-850/70 shadow-4xl modal-animation">
|
||||
<form
|
||||
className="tlwd-text-gray-200 tlwd-w-full tlwd-p-0 tlwd-m-0"
|
||||
onSubmit={initHandler}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
||||
<div className=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="open-webui-url-input"
|
||||
placeholder="Open WebUI URL"
|
||||
className="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
value={url}
|
||||
onChange={(e) => {
|
||||
setUrl(e.target.value);
|
||||
}}
|
||||
autoComplete="one-time-code"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full tlwd-mt-2">
|
||||
<div className=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
placeholder="Open WebUI API Key"
|
||||
className="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
autoComplete="one-time-code"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
className=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
let _url = url;
|
||||
|
||||
if (_url.endsWith("/")) {
|
||||
_url = _url.slice(0, -1);
|
||||
setUrl(_url);
|
||||
}
|
||||
|
||||
setModels(await getModels(key, _url));
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{models && models.length > 0 && (
|
||||
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full tlwd-mt-2">
|
||||
<div className=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<select
|
||||
id="open-webui-model-input"
|
||||
className="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
autoComplete="off"
|
||||
required
|
||||
>
|
||||
<option value="">Select a model</option>
|
||||
{models.map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{model.name ?? model.id}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m4.5 12.75 6 6 9-13.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className=" tlwd-m-auto tlwd-max-w-xl tlwd-w-full tlwd-pb-32">
|
||||
<div className="tlwd-w-full tlwd-flex tlwd-flex-col tlwd-justify-between tlwd-py-2.5 tlwd-px-3.5 tlwd-rounded-2xl tlwd-outline tlwd-outline-1 tlwd-outline-gray-850 tlwd-backdrop-blur-3xl tlwd-bg-gray-850/70 shadow-4xl modal-animation">
|
||||
<form
|
||||
className="tlwd-text-gray-200 tlwd-w-full tlwd-p-0 tlwd-m-0"
|
||||
onSubmit={submitHandler}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
||||
<div className=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="open-webui-search-input"
|
||||
placeholder="Search Open WebUI"
|
||||
className="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
autoComplete="one-time-code"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className=" tlwd-flex tlwd-justify-end tlwd-gap-1">
|
||||
<p className="tlwd-text-right tlwd-text-[0.7rem] tlwd-p-0 tlwd-m-0 tlwd-text-neutral-300">
|
||||
Press ⌘Space+Shift to toggle
|
||||
</p>
|
||||
<button
|
||||
className=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowConfig(true);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="tlwd-size-3"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
456
extension/src/lib/components/SpotlightSearch.svelte
Normal file
456
extension/src/lib/components/SpotlightSearch.svelte
Normal file
@ -0,0 +1,456 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { generateOpenAIChatCompletion, getModels } from "../apis";
|
||||
import { splitStream } from "../utils";
|
||||
|
||||
let show = false;
|
||||
let showConfig = false;
|
||||
|
||||
let url = "";
|
||||
let key = "";
|
||||
let model = "";
|
||||
|
||||
let searchValue = "";
|
||||
let models = [];
|
||||
|
||||
const resetConfig = () => {
|
||||
console.log("resetConfig");
|
||||
|
||||
try {
|
||||
chrome.storage.local.clear().then(() => {
|
||||
console.log("Value is cleared");
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
localStorage.setItem("url", "");
|
||||
localStorage.setItem("key", "");
|
||||
localStorage.setItem("model", "");
|
||||
}
|
||||
|
||||
url = "";
|
||||
key = "";
|
||||
model = "";
|
||||
showConfig = true;
|
||||
};
|
||||
|
||||
const submitHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
window.open(
|
||||
`${url}/?q=${encodeURIComponent(searchValue)}&models=${model}`,
|
||||
"_blank"
|
||||
);
|
||||
|
||||
searchValue = "";
|
||||
show = false;
|
||||
};
|
||||
|
||||
const initHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
chrome.storage.local
|
||||
.set({ url: url, key: key, model: model })
|
||||
.then(() => {
|
||||
console.log("Value is set");
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
localStorage.setItem("url", url);
|
||||
localStorage.setItem("key", key);
|
||||
localStorage.setItem("model", model);
|
||||
}
|
||||
|
||||
showConfig = false;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
let _storageCache = null;
|
||||
|
||||
try {
|
||||
_storageCache = await chrome.storage.local.get();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
if (_storageCache) {
|
||||
url = _storageCache.url ?? "";
|
||||
key = _storageCache.key ?? "";
|
||||
model = _storageCache.model ?? "";
|
||||
if (_storageCache.url && _storageCache.key && _storageCache.model) {
|
||||
models = await getModels(_storageCache.key, _storageCache.url);
|
||||
showConfig = false;
|
||||
}
|
||||
}
|
||||
|
||||
const down = async (e) => {
|
||||
// Reset the configuration when ⌘Shift+Escape is pressed
|
||||
if (show && e.shiftKey && e.key === "Escape") {
|
||||
resetConfig();
|
||||
} else if (e.key === "Escape") {
|
||||
show = false;
|
||||
}
|
||||
|
||||
if (
|
||||
e.key === " " &&
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(e.shiftKey || e.altKey)
|
||||
) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "getSelection",
|
||||
});
|
||||
|
||||
if (response?.data ?? false) {
|
||||
searchValue = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("catch", error);
|
||||
}
|
||||
|
||||
show = !show;
|
||||
|
||||
setTimeout(() => {
|
||||
const inputElement = document.getElementById(
|
||||
"open-webui-search-input"
|
||||
);
|
||||
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (key !== "" && url !== "") {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(e.shiftKey || e.altKey)
|
||||
) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "getSelection",
|
||||
});
|
||||
|
||||
if (response?.data ?? false) {
|
||||
await chrome.runtime.sendMessage({
|
||||
action: "writeText",
|
||||
text: "\n",
|
||||
});
|
||||
|
||||
const [res, controller] = await generateOpenAIChatCompletion(
|
||||
key,
|
||||
{
|
||||
model: model,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: response.data,
|
||||
},
|
||||
],
|
||||
stream: true,
|
||||
},
|
||||
models.find((m) => m.id === model)?.url
|
||||
);
|
||||
|
||||
if (res && res.ok) {
|
||||
const reader = res.body
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(splitStream("\n"))
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
let lines = value.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line !== "") {
|
||||
console.log(line);
|
||||
if (line === "data: [DONE]") {
|
||||
console.log("DONE");
|
||||
} else {
|
||||
let data = JSON.parse(line.replace(/^data: /, ""));
|
||||
console.log(data);
|
||||
|
||||
if ("request_id" in data) {
|
||||
console.log(data.request_id);
|
||||
} else {
|
||||
await chrome.runtime.sendMessage({
|
||||
action: "writeText",
|
||||
text: data.choices[0].delta.content ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", down);
|
||||
return () => document.removeEventListener("keydown", down);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="tlwd-fixed tlwd-top-0 tlwd-right-0 tlwd-left-0 tlwd-bottom-0 tlwd-w-full tlwd-min-h-screen tlwd-h-screen tlwd-flex tlwd-justify-center tlwd-z-[9999999999] tlwd-overflow-hidden tlwd-overscroll-contain"
|
||||
on:mousedown={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
{#if showConfig}
|
||||
<div class=" tlwd-m-auto tlwd-max-w-sm tlwd-w-full tlwd-pb-32">
|
||||
<div
|
||||
class="tlwd-w-full tlwd-flex tlwd-flex-col tlwd-justify-between tlwd-py-2.5 tlwd-px-3.5 tlwd-rounded-2xl tlwd-outline tlwd-outline-1 tlwd-outline-gray-850 tlwd-backdrop-blur-3xl tlwd-bg-gray-850/70 shadow-4xl modal-animation"
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<form
|
||||
class="tlwd-text-gray-200 tlwd-w-full tlwd-p-0 tlwd-m-0"
|
||||
on:submit={initHandler}
|
||||
on:mousedown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
||||
<div class=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="open-webui-url-input"
|
||||
placeholder="Open WebUI URL"
|
||||
class="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
bind:value={url}
|
||||
autocomplete="one-time-code"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full tlwd-mt-2"
|
||||
>
|
||||
<div class=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
placeholder="Open WebUI API Key"
|
||||
class="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
bind:value={key}
|
||||
autocomplete="one-time-code"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
class=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (url.endsWith("/")) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
|
||||
models = await getModels(key, url);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if models && models.length > 0}
|
||||
<div
|
||||
class="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full tlwd-mt-2"
|
||||
>
|
||||
<div class=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<select
|
||||
id="open-webui-model-input"
|
||||
class="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
bind:value={model}
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
<option value="">Select a model</option>
|
||||
{#each models as model}
|
||||
<option value={model.id}>{model.name ?? model.id}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button
|
||||
class=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m4.5 12.75 6 6 9-13.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class=" tlwd-m-auto tlwd-max-w-xl tlwd-w-full tlwd-pb-32">
|
||||
<div
|
||||
class="tlwd-w-full tlwd-flex tlwd-flex-col tlwd-justify-between tlwd-py-2.5 tlwd-px-3.5 tlwd-rounded-2xl tlwd-outline tlwd-outline-1 tlwd-outline-gray-850 tlwd-backdrop-blur-3xl tlwd-bg-gray-850/70 shadow-4xl modal-animation"
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<form
|
||||
class="tlwd-text-gray-200 tlwd-w-full tlwd-p-0 tlwd-m-0"
|
||||
on:submit={submitHandler}
|
||||
on:mousedown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
||||
<div class=" tlwd-flex tlwd-items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="open-webui-search-input"
|
||||
placeholder="Search Open WebUI"
|
||||
class="tlwd-p-0 tlwd-m-0 tlwd-text-xl tlwd-w-full tlwd-font-medium tlwd-bg-transparent tlwd-border-none placeholder:tlwd-text-gray-500 tlwd-text-neutral-100 tlwd-outline-none"
|
||||
bind:value={searchValue}
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class=" tlwd-flex tlwd-justify-end tlwd-gap-1">
|
||||
<p
|
||||
class="tlwd-text-right tlwd-text-[0.7rem] tlwd-p-0 tlwd-m-0 tlwd-text-neutral-300"
|
||||
>
|
||||
Press ⌘Space+Shift to toggle
|
||||
</p>
|
||||
<button
|
||||
class=" tlwd-flex tlwd-items-center tlwd-bg-transparent tlwd-text-neutral-100 tlwd-cursor-pointer tlwd-p-0 tlwd-m-0 tlwd-outline-none tlwd-border-none"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showConfig = true;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={2.5}
|
||||
stroke="currentColor"
|
||||
class="tlwd-size-3"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -1,10 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('extension-app')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
8
extension/src/main.ts
Normal file
8
extension/src/main.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("extension-app")!,
|
||||
});
|
||||
|
||||
export default app;
|
2
extension/src/vite-env.d.ts
vendored
Normal file
2
extension/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
7
extension/svelte.config.js
Normal file
7
extension/svelte.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
20
extension/tsconfig.json
Normal file
20
extension/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
10
extension/tsconfig.node.json
Normal file
10
extension/tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [svelte()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
Loading…
Reference in New Issue
Block a user