feature: about page, some enhancement.
This commit is contained in:
parent
858a18bf4e
commit
2c1f4f28bf
22
backend/route.ts
Normal file
22
backend/route.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Express } from "express";
|
||||||
|
import { completeGoogle } from "search-engine-autocomplete";
|
||||||
|
|
||||||
|
export function configureBackendRoutes(app: Express) {
|
||||||
|
app.get('/api/v1/suggestion', async (req, res) => {
|
||||||
|
const query = req.query.q as string;
|
||||||
|
const t = parseInt(req.query.t as string || "0") || null;
|
||||||
|
let language = req.query.l as string || 'en-US';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await completeGoogle(query, language);
|
||||||
|
//logger.info({ type: "onesearch_search_autocomplete", query: query, data: data });
|
||||||
|
res.json({ ...data, time: t });
|
||||||
|
} catch (error) {
|
||||||
|
//logger.error({ type: "onesearch_search_autocomplete_error", error: error.message });
|
||||||
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.get("/api/v1/ping", async (_, res) => {
|
||||||
|
res.status(200).json({message: "pong"});
|
||||||
|
})
|
||||||
|
}
|
@ -1,26 +1,12 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { bgFocusAtom } from "../lib/state/background";
|
import { bgFocusAtom } from "../lib/state/background";
|
||||||
import BackgroundContainer from "./backgroundContainer";
|
import BackgroundContainer from "./backgroundContainer";
|
||||||
|
import useDarkMode from "lib/darkModeHook";
|
||||||
|
|
||||||
export default function Background() {
|
export default function Background() {
|
||||||
const [isFocus, setFocus] = useAtom(bgFocusAtom);
|
const [isFocus, setFocus] = useAtom(bgFocusAtom);
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const darkMode = useDarkMode();
|
||||||
const colorSchemeQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
setDarkMode(colorSchemeQueryList.matches ? true : false);
|
|
||||||
|
|
||||||
const handleChange = () => {
|
|
||||||
setDarkMode(colorSchemeQueryList.matches ? true : false);
|
|
||||||
};
|
|
||||||
|
|
||||||
colorSchemeQueryList.addEventListener("change", handleChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
colorSchemeQueryList.removeEventListener("change", handleChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -36,7 +36,7 @@ export default function OneSearch() {
|
|||||||
cleanSuggestion("QUERY", "NAVIGATION");
|
cleanSuggestion("QUERY", "NAVIGATION");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch(`/api/suggestion?q=${query}&l=${lang}&t=${time}&engine=${engine}`)
|
fetch(`/api/v1/suggestion?q=${query}&l=${lang}&t=${time}&engine=${engine}`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data: suggestionsResponse) => {
|
.then((data: suggestionsResponse) => {
|
||||||
try {
|
try {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>SparkHome</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
23
lib/darkModeHook.ts
Normal file
23
lib/darkModeHook.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
// Custom React Hook for dark mode detect
|
||||||
|
export default function useDarkMode() {
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const colorSchemeQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
setDarkMode(colorSchemeQueryList.matches ? true : false);
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
setDarkMode(colorSchemeQueryList.matches ? true : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
colorSchemeQueryList.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
colorSchemeQueryList.removeEventListener("change", handleChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return darkMode;
|
||||||
|
}
|
10246
lib/license.txt
Normal file
10246
lib/license.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,13 +10,6 @@ import { LangEn } from "@nlpjs/lang-en-min";
|
|||||||
import { LangZh } from "@nlpjs/lang-zh";
|
import { LangZh } from "@nlpjs/lang-zh";
|
||||||
import * as fflate from 'fflate';
|
import * as fflate from 'fflate';
|
||||||
|
|
||||||
let zh: TrainData = {};
|
|
||||||
let en: TrainData = {};
|
|
||||||
|
|
||||||
type TrainData = {
|
|
||||||
[key: string]: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class NLU {
|
export class NLU {
|
||||||
manager: any;
|
manager: any;
|
||||||
inited: boolean = false;
|
inited: boolean = false;
|
||||||
@ -41,7 +34,6 @@ export class NLU {
|
|||||||
const modelText = fflate.strFromU8(decompressed);
|
const modelText = fflate.strFromU8(decompressed);
|
||||||
manager.fromJSON(JSON.parse(modelText));
|
manager.fromJSON(JSON.parse(modelText));
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
// console.log(this.manager);
|
|
||||||
}
|
}
|
||||||
async init() {
|
async init() {
|
||||||
await this.loadIntentionModel();
|
await this.loadIntentionModel();
|
||||||
|
@ -3,7 +3,7 @@ import pjson from "package.json"
|
|||||||
const CLIENT_VERSION = pjson.version;
|
const CLIENT_VERSION = pjson.version;
|
||||||
|
|
||||||
export function sendError(error: Error) {
|
export function sendError(error: Error) {
|
||||||
fetch("/api/error", {
|
fetch("/api/v1/error", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
8
lib/version.ts
Normal file
8
lib/version.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as pjson from "package.json";
|
||||||
|
|
||||||
|
export default function getVersion(){
|
||||||
|
return pjson.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clientNLUVersion = 2;
|
||||||
|
export const apiVersion = 1;
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sparkhome",
|
"name": "sparkhome",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "5.2.3",
|
"version": "5.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun server.ts",
|
"dev": "bun server.ts",
|
||||||
@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/react": "^5.0.1",
|
"@iconify/react": "^5.0.1",
|
||||||
"@nextui-org/react": "^2.4.2",
|
"@nextui-org/react": "^2.4.2",
|
||||||
|
"@types/bun": "^1.1.6",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
|
69
pages/about/index.tsx
Normal file
69
pages/about/index.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import useDarkMode from "lib/darkModeHook";
|
||||||
|
import getVersion, { apiVersion, clientNLUVersion } from "lib/version";
|
||||||
|
|
||||||
|
export default function AboutPage() {
|
||||||
|
const darkMode = useDarkMode();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dark:bg-[rgb(23,25,29)] dark:text-white min-h-screen w-screen overflow-x-hidden">
|
||||||
|
<main
|
||||||
|
className="relative h-full w-full md:w-3/4 lg:w-1/2 left-0 md:left-[12.5%] lg:left-1/4
|
||||||
|
pt-12"
|
||||||
|
>
|
||||||
|
<h1 className="text-4xl font-bold mb-6">About SparkHome</h1>
|
||||||
|
<div className="flex mb-8">
|
||||||
|
<img src="/favicon.ico" className="relative w-20 h-20" />
|
||||||
|
<div className="flex flex-col ml-4">
|
||||||
|
<span className="text-3xl font-bold">SparkHome</span>
|
||||||
|
<p className="mt-2 text-xl">
|
||||||
|
Made with <span className="text-red-500">♥️</span> by
|
||||||
|
<a className="underline text-red-500 mx-1" href="https://alikia2x.com">
|
||||||
|
alikia2x
|
||||||
|
</a>
|
||||||
|
from Luminara Studio
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Version title="Overall Version" version={getVersion()} versionClass="bg-red-500" />
|
||||||
|
<Version
|
||||||
|
title="Browser NLU Model Version"
|
||||||
|
version={"Build " + clientNLUVersion}
|
||||||
|
versionClass="bg-purple-500"
|
||||||
|
/>
|
||||||
|
<Version
|
||||||
|
title="Backend API Version"
|
||||||
|
version={"/v" + apiVersion}
|
||||||
|
versionClass="bg-orange-500"
|
||||||
|
/>
|
||||||
|
<p className="flex items-center my-3">
|
||||||
|
<span className="font-bold text-2xl mr-4 w-[36rem]">License</span>
|
||||||
|
<span className="relative px-2 py-1 text-sm font-bold rounded-md text-nowrap underline bg-green-600">
|
||||||
|
<a href="/about/license">→ view</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="relative font-bold text-2xl mt-12">Presented By</p>
|
||||||
|
{!darkMode && <img src="/LuminaraStudio.png" className="relative h-56 mt-6" />}
|
||||||
|
{darkMode && <img src="/LuminaraStudioDark.png" className="relative h-56 mt-6" />}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Version(props: { title: string; version: string; versionClass?: string }) {
|
||||||
|
document.title = "About SparkHome";
|
||||||
|
return (
|
||||||
|
<p className="flex items-center my-3">
|
||||||
|
<span className="font-bold text-2xl mr-4 w-[36rem]">{props.title}</span>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"relative px-2 py-1 text-sm font-bold rounded-md text-nowrap " +
|
||||||
|
props.versionClass ?? ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.version}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
17
pages/about/license/index.tsx
Normal file
17
pages/about/license/index.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import LICENSE from "lib/license.txt?raw";
|
||||||
|
|
||||||
|
export default function LicensePage() {
|
||||||
|
return (
|
||||||
|
<div className="dark:bg-[rgb(23,25,29)] dark:text-white min-h-screen w-screen overflow-x-hidden">
|
||||||
|
<main
|
||||||
|
className="relative h-full w-full md:w-3/4 lg:w-1/2 left-0 md:left-[12.5%] lg:left-1/4
|
||||||
|
pt-12"
|
||||||
|
>
|
||||||
|
<h1 className="text-4xl font-bold mb-6">LICENSE</h1>
|
||||||
|
<div className="font-mono text-justify whitespace-break-spaces">
|
||||||
|
{LICENSE}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
BIN
public/LuminaraStudio.png
Normal file
BIN
public/LuminaraStudio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
BIN
public/LuminaraStudioDark.png
Normal file
BIN
public/LuminaraStudioDark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
109
server.ts
109
server.ts
@ -4,9 +4,52 @@ import ViteExpress from "vite-express";
|
|||||||
import pjson from "./package.json";
|
import pjson from "./package.json";
|
||||||
import { networkInterfaces } from "os";
|
import { networkInterfaces } from "os";
|
||||||
import cac from "cac";
|
import cac from "cac";
|
||||||
import { completeGoogle } from "search-engine-autocomplete";
|
import { configureBackendRoutes } from "./backend/route";
|
||||||
const start = new Date();
|
|
||||||
|
|
||||||
|
|
||||||
|
async function helloMessage() {
|
||||||
|
const { base } = await ViteExpress.getViteConfig();
|
||||||
|
const timeCost = new Date().getTime() - start.getTime();
|
||||||
|
console.log("");
|
||||||
|
console.log(
|
||||||
|
" ",
|
||||||
|
chalk.redBright("SparkHome"),
|
||||||
|
chalk.redBright("v" + pjson.version),
|
||||||
|
chalk.whiteBright(" ready in"),
|
||||||
|
`${Math.round(timeCost)} ms`
|
||||||
|
);
|
||||||
|
console.log("");
|
||||||
|
console.log(" ", chalk.redBright("➜ "), "Local:\t", chalk.cyan(`http://${host}:${port}${base}`));
|
||||||
|
if (host !== "localhost") {
|
||||||
|
for (const ip of ips) {
|
||||||
|
console.log(" ", chalk.redBright("➜ "), "Network:\t", chalk.cyan(`http://${ip}:${port}${base}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(" ", chalk.red("➜ "), chalk.whiteBright("press"), "h + enter", chalk.whiteBright("to show help"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleInput() {
|
||||||
|
for await (const line of console) {
|
||||||
|
switch (line) {
|
||||||
|
case "h":
|
||||||
|
console.log(" Shortcuts");
|
||||||
|
console.log(" ", chalk.whiteBright("press"), "c + enter ", chalk.whiteBright("to clear console"));
|
||||||
|
console.log(" ", chalk.whiteBright("press"), "q + enter ", chalk.whiteBright("to quit"));
|
||||||
|
break;
|
||||||
|
case "c":
|
||||||
|
console.clear();
|
||||||
|
break;
|
||||||
|
case "q":
|
||||||
|
server.on("vite:close", ()=>{});
|
||||||
|
server.close();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = new Date();
|
||||||
const cli = cac();
|
const cli = cac();
|
||||||
const nets = networkInterfaces();
|
const nets = networkInterfaces();
|
||||||
const ips: string[] = [];
|
const ips: string[] = [];
|
||||||
@ -36,68 +79,10 @@ if (parsed.options.host!==undefined && typeof parsed.options.host == "boolean" &
|
|||||||
host = "0.0.0.0";
|
host = "0.0.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/message", (_, res) => res.send("Hello from express!"));
|
configureBackendRoutes(app);
|
||||||
|
|
||||||
app.get('/api/suggestion', async (req, res) => {
|
|
||||||
const query = req.query.q as string;
|
|
||||||
const t = parseInt(req.query.t as string || "0") || null;
|
|
||||||
let language = req.query.l as string || 'en-US';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await completeGoogle(query, language);
|
|
||||||
//logger.info({ type: "onesearch_search_autocomplete", query: query, data: data });
|
|
||||||
res.json({ ...data, time: t });
|
|
||||||
} catch (error) {
|
|
||||||
//logger.error({ type: "onesearch_search_autocomplete_error", error: error.message });
|
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function helloMessage() {
|
|
||||||
const { base } = await ViteExpress.getViteConfig();
|
|
||||||
//console.clear();
|
|
||||||
const timeCost = new Date().getTime() - start.getTime();
|
|
||||||
console.log("");
|
|
||||||
console.log(
|
|
||||||
" ",
|
|
||||||
chalk.redBright("SparkHome"),
|
|
||||||
chalk.redBright("v" + pjson.version),
|
|
||||||
chalk.whiteBright(" ready in"),
|
|
||||||
`${Math.round(timeCost)} ms`
|
|
||||||
);
|
|
||||||
console.log("");
|
|
||||||
console.log(" ", chalk.redBright("➜ "), "Local:\t", chalk.cyan(`http://${host}:${port}${base}`));
|
|
||||||
if (host !== "localhost") {
|
|
||||||
for (const ip of ips) {
|
|
||||||
console.log(" ", chalk.redBright("➜ "), "Network:\t", chalk.cyan(`http://${ip}:${port}${base}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(" ", chalk.red("➜ "), chalk.whiteBright("press"), "h + enter", chalk.whiteBright("to show help"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = app.listen(port, host);
|
const server = app.listen(port, host);
|
||||||
|
|
||||||
ViteExpress.bind(app, server, helloMessage);
|
ViteExpress.bind(app, server, helloMessage);
|
||||||
|
|
||||||
async function a() {
|
handleInput();
|
||||||
for await (const line of console) {
|
|
||||||
switch (line) {
|
|
||||||
case "h":
|
|
||||||
console.log(" Shortcuts");
|
|
||||||
console.log(" ", chalk.whiteBright("press"), "c + enter ", chalk.whiteBright("to clear console"));
|
|
||||||
console.log(" ", chalk.whiteBright("press"), "q + enter ", chalk.whiteBright("to quit"));
|
|
||||||
break;
|
|
||||||
case "c":
|
|
||||||
console.clear();
|
|
||||||
break;
|
|
||||||
case "q":
|
|
||||||
server.on("vite:close", ()=>{});
|
|
||||||
server.close();
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a();
|
|
@ -7,7 +7,8 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
|
"types": ["bun"]
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user