mirror of
https://github.com/open-webui/extension
synced 2025-06-26 18:25:58 +00:00
feat: extension config
This commit is contained in:
parent
344262154d
commit
b5d41d86f6
@ -13,6 +13,29 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
|
|||||||
console.log(res);
|
console.log(res);
|
||||||
sendResponse({ data: res[0]["result"] });
|
sendResponse({ data: res[0]["result"] });
|
||||||
});
|
});
|
||||||
|
} else if (request.action == "writeText") {
|
||||||
|
function writeTextToInput(text) {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
if (
|
||||||
|
activeElement &&
|
||||||
|
(activeElement.tagName === "INPUT" ||
|
||||||
|
activeElement.tagName === "TEXTAREA")
|
||||||
|
) {
|
||||||
|
activeElement.value = `${activeElement.value}${text}`;
|
||||||
|
|
||||||
|
if (activeElement.tagName === "TEXTAREA") {
|
||||||
|
activeElement.scrollTop = activeElement.scrollHeight;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("No active input or textarea field found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId: id, allFrames: true },
|
||||||
|
func: writeTextToInput,
|
||||||
|
args: [request.text],
|
||||||
|
});
|
||||||
|
sendResponse({});
|
||||||
} else {
|
} else {
|
||||||
sendResponse({});
|
sendResponse({});
|
||||||
}
|
}
|
||||||
|
|||||||
23
extension/dist/main.js
vendored
23
extension/dist/main.js
vendored
File diff suppressed because one or more lines are too long
2
extension/dist/style.css
vendored
2
extension/dist/style.css
vendored
File diff suppressed because one or more lines are too long
89
extension/src/apis/index.js
Normal file
89
extension/src/apis/index.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
export const getOpenAIModels = async (token = "", url = "") => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${url}/models`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(token && { authorization: `Bearer ${token}` }),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error = `OpenAI: ${err?.error?.message ?? "Network Problem"}`;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = Array.isArray(res) ? res : res?.data ?? null;
|
||||||
|
|
||||||
|
return models
|
||||||
|
? models
|
||||||
|
.map((model) => ({
|
||||||
|
id: model.id,
|
||||||
|
name: model.name ?? model.id,
|
||||||
|
url: url,
|
||||||
|
custom_info: model.custom_info,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
})
|
||||||
|
: models;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getModels = async (key, url) => {
|
||||||
|
let models = await Promise.all([
|
||||||
|
getOpenAIModels(key, `${url}/ollama/v1`).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
getOpenAIModels(key, `${url}/openai/api`).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
models = models
|
||||||
|
.filter((models) => models)
|
||||||
|
.reduce((a, e, i, arr) => a.concat(e), []);
|
||||||
|
|
||||||
|
console.log(models);
|
||||||
|
|
||||||
|
return models;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateOpenAIChatCompletion = async (
|
||||||
|
api_key = "",
|
||||||
|
body = {},
|
||||||
|
url = "http://localhost:8080"
|
||||||
|
) => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${url}/chat/completions`, {
|
||||||
|
signal: controller.signal,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${api_key}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
error = err;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [res, controller];
|
||||||
|
};
|
||||||
@ -1,12 +1,40 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { generateOpenAIChatCompletion, getModels } from "../apis";
|
||||||
|
import { splitStream } from "../utils";
|
||||||
|
|
||||||
export const SpotlightSearch = () => {
|
export const SpotlightSearch = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
|
||||||
|
const [url, setUrl] = useState(localStorage.getItem("url") ?? "");
|
||||||
|
const [key, setKey] = useState(localStorage.getItem("key") ?? "");
|
||||||
|
const [model, setModel] = useState(localStorage.getItem("model") ?? "");
|
||||||
|
|
||||||
|
const [showConfig, setShowConfig] = useState(url === "" || key === "");
|
||||||
|
const [models, setModels] = useState(null);
|
||||||
|
|
||||||
|
const resetConfig = () => {
|
||||||
|
console.log("resetConfig");
|
||||||
|
localStorage.setItem("url", "");
|
||||||
|
localStorage.setItem("key", "");
|
||||||
|
localStorage.setItem("model", "");
|
||||||
|
|
||||||
|
setUrl("");
|
||||||
|
setKey("");
|
||||||
|
setModel("");
|
||||||
|
setShowConfig(true);
|
||||||
|
};
|
||||||
|
|
||||||
// Toggle the menu when ⌘Space+Shift is pressed
|
// Toggle the menu when ⌘Space+Shift is pressed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const down = async (e) => {
|
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 (
|
if (
|
||||||
e.key === " " &&
|
e.key === " " &&
|
||||||
(e.metaKey || e.ctrlKey) &&
|
(e.metaKey || e.ctrlKey) &&
|
||||||
@ -15,6 +43,7 @@ export const SpotlightSearch = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setOpen((open) => !open);
|
setOpen((open) => !open);
|
||||||
|
|
||||||
|
try {
|
||||||
const response = await chrome.runtime.sendMessage({
|
const response = await chrome.runtime.sendMessage({
|
||||||
action: "getSelection",
|
action: "getSelection",
|
||||||
});
|
});
|
||||||
@ -22,6 +51,9 @@ export const SpotlightSearch = () => {
|
|||||||
if (response?.data ?? false) {
|
if (response?.data ?? false) {
|
||||||
setSearchValue(response.data);
|
setSearchValue(response.data);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("catch", error);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const inputElement = document.getElementById(
|
const inputElement = document.getElementById(
|
||||||
@ -34,14 +66,94 @@ export const SpotlightSearch = () => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
if (key !== "" && url !== "") {
|
||||||
setOpen(false);
|
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, { capture: true });
|
document.addEventListener("keydown", down);
|
||||||
return () => document.removeEventListener("keydown", down);
|
return () => document.removeEventListener("keydown", down);
|
||||||
}, []);
|
}, [url, key, model, models, open]);
|
||||||
|
|
||||||
const submitHandler = (e) => {
|
const submitHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -49,13 +161,25 @@ export const SpotlightSearch = () => {
|
|||||||
setSearchValue("");
|
setSearchValue("");
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`http://localhost:8080/?q=${encodeURIComponent(searchValue)}`,
|
`${url}/?q=${encodeURIComponent(searchValue)}&models=${model}`,
|
||||||
"_blank"
|
"_blank"
|
||||||
);
|
);
|
||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initHandler = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
localStorage.setItem("url", url);
|
||||||
|
localStorage.setItem("key", key);
|
||||||
|
localStorage.setItem("model", model);
|
||||||
|
|
||||||
|
console.log(localStorage);
|
||||||
|
|
||||||
|
setShowConfig(false);
|
||||||
|
};
|
||||||
|
|
||||||
return open ? (
|
return open ? (
|
||||||
<div
|
<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"
|
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"
|
||||||
@ -63,6 +187,164 @@ export const SpotlightSearch = () => {
|
|||||||
setOpen(false);
|
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="off"
|
||||||
|
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
|
||||||
|
id="open-webui-key-input"
|
||||||
|
placeholder="Open WebUI API Key"
|
||||||
|
type="password"
|
||||||
|
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="off"
|
||||||
|
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 && (
|
||||||
|
<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-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">
|
<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
|
<form
|
||||||
@ -71,6 +353,7 @@ export const SpotlightSearch = () => {
|
|||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
<div className="tlwd-flex tlwd-items-center tlwd-gap-2 tlwd-w-full">
|
||||||
<div className=" tlwd-flex tlwd-items-center">
|
<div className=" tlwd-flex tlwd-items-center">
|
||||||
@ -99,14 +382,44 @@ export const SpotlightSearch = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<p className="tlwd-text-right tlwd-text-[0.7rem] tlwd-p-0 tlwd-m-0 tlwd-text-neutral-300">
|
||||||
Press ⌘Space+Shift to toggle
|
Press ⌘Space+Shift to toggle
|
||||||
</p>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<> </>
|
<></>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
14
extension/src/utils/index.js
Normal file
14
extension/src/utils/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const splitStream = (splitOn) => {
|
||||||
|
let buffer = "";
|
||||||
|
return new TransformStream({
|
||||||
|
transform(chunk, controller) {
|
||||||
|
buffer += chunk;
|
||||||
|
const parts = buffer.split(splitOn);
|
||||||
|
parts.slice(0, -1).forEach((part) => controller.enqueue(part));
|
||||||
|
buffer = parts[parts.length - 1];
|
||||||
|
},
|
||||||
|
flush(controller) {
|
||||||
|
if (buffer) controller.enqueue(buffer);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user