mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 15:51:01 +08:00
Made a working settings panel & includes the user's info, what is
missing? well, all the actions that requires the data to be sent to the server is still not there yet. Tried to add onboarding, but I have just no idea how to do it (Maybe I can do it w/ a video?
This commit is contained in:
parent
1eb15058d7
commit
29760dda96
@ -21,6 +21,10 @@ Reverse engineering 文檔: [about](/about/)
|
|||||||
## Note for developing
|
## Note for developing
|
||||||
The desktop enviroment is super unstable when even using a beefy computer, even so, the desktop will lag when opening the newsView, like it's just hates being in a dev env. Prod app works tho, so you can demo it using `bun run build && bun run preview` for demoing. Please don't file a issue request for this matter. If you have the fix, please contribute using Github PRs.
|
The desktop enviroment is super unstable when even using a beefy computer, even so, the desktop will lag when opening the newsView, like it's just hates being in a dev env. Prod app works tho, so you can demo it using `bun run build && bun run preview` for demoing. Please don't file a issue request for this matter. If you have the fix, please contribute using Github PRs.
|
||||||
|
|
||||||
|
## 如果要開發,你需要
|
||||||
|
- 一個 Postgres 資料庫 (你可以用 Zeabur 跑開發用資料庫,可以用我的 [優惠連結(?](https://zeabur.com/referral?referralCode=hpware),你可以拿到大約150塊的試用金額
|
||||||
|
- 一個 Groq 的 API
|
||||||
|
|
||||||
## 為什麼?
|
## 為什麼?
|
||||||
|
|
||||||
我們使用這個新聞來舉例:
|
我們使用這個新聞來舉例:
|
||||||
|
@ -1,22 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BadgeCheckIcon, OctagonAlertIcon } from "lucide-vue-next";
|
import { BadgeCheckIcon, OctagonAlertIcon } from "lucide-vue-next";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const user = ref("");
|
const user = ref("");
|
||||||
const enterFirstName = ref();
|
const enterFirstName = ref();
|
||||||
const useremail = ref();
|
const useremail = ref();
|
||||||
|
const userData = ref({
|
||||||
|
userAccount: "",
|
||||||
|
firstName: "",
|
||||||
|
requested_action: "",
|
||||||
|
email: "",
|
||||||
|
avatarURL: "",
|
||||||
|
firstName: "",
|
||||||
|
});
|
||||||
const enteruseremail = ref();
|
const enteruseremail = ref();
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const req = await fetch("/api/user/validateUserToken");
|
const req = await fetch("/api/user/validateUserToken");
|
||||||
const res = await req.json();
|
const res = await req.json();
|
||||||
user.value = res.firstName;
|
user.value = res.firstName;
|
||||||
useremail.value = res;
|
userData.value = res;
|
||||||
|
useremail.value = res.email;
|
||||||
});
|
});
|
||||||
const setFirstName = async () => {
|
const setFirstName = async () => {
|
||||||
const staticFirstName = "";
|
const staticFirstName = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits(["windowopener"]);
|
||||||
|
|
||||||
const logoutAction = () => {};
|
const logoutAction = () => {};
|
||||||
|
|
||||||
const groqApiKeyRegex = /^gsk_[a-zA-Z0-9]{52}$/;
|
const groqApiKeyRegex = /^gsk_[a-zA-Z0-9]{52}$/;
|
||||||
const customApiKey = ref();
|
const customApiKey = ref();
|
||||||
const isCorrect = ref(false);
|
const isCorrect = ref(false);
|
||||||
@ -27,6 +49,32 @@ const submitCustomApiKey = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkValidApiKey = () => {
|
||||||
|
const apiKey = customApiKey.value;
|
||||||
|
if (!apiKey) {
|
||||||
|
isCorrect.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isCorrect.value = groqApiKeyRegex.test(apiKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteDialog = ref(false);
|
||||||
|
const showLogoutDialog = ref(false);
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
await deleteAccount();
|
||||||
|
showDeleteDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmLogout = async () => {
|
||||||
|
showLogoutDialog.value = false;
|
||||||
|
};
|
||||||
|
const deleteAccount = async () => {
|
||||||
|
const req = await fetch("/api/user/action", {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
};
|
||||||
const apiKey = customApiKey.value;
|
const apiKey = customApiKey.value;
|
||||||
try {
|
try {
|
||||||
const sendApi = await fetch("/api/ai/loadCustomGroqApi", {
|
const sendApi = await fetch("/api/ai/loadCustomGroqApi", {
|
||||||
@ -41,44 +89,9 @@ const submitCustomApiKey = async () => {
|
|||||||
const data = await sendApi.json();
|
const data = await sendApi.json();
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
};
|
console.log(e);
|
||||||
|
|
||||||
const checkValidApiKey = () => {
|
|
||||||
const apiKey = customApiKey.value;
|
|
||||||
if (!apiKey) {
|
|
||||||
isCorrect.value = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
isCorrect.value = groqApiKeyRegex.test(apiKey);
|
|
||||||
};
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
const showDeleteDialog = ref(false);
|
|
||||||
const showLogoutDialog = ref(false);
|
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
|
||||||
await deleteAccount();
|
|
||||||
showDeleteDialog.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
showLogoutDialog.value = false;
|
|
||||||
};
|
|
||||||
const deleteAccount = async () => {
|
|
||||||
const req = await fetch("/api/user/action", {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* userAccount: fetchViaSQL[0].username,
|
* userAccount: fetchViaSQL[0].username,
|
||||||
@ -87,11 +100,44 @@ const deleteAccount = async () => {
|
|||||||
avatarURL: fetchViaSQL[0].avatarurl,
|
avatarURL: fetchViaSQL[0].avatarurl,
|
||||||
firstName: fetchViaSQL[0].firstName,
|
firstName: fetchViaSQL[0].firstName,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{ name: "NAME", sendValue: enterFirstName.value },
|
||||||
|
{ name: "USER_EMAIL", sendValue: enteruseremail.value },
|
||||||
|
];
|
||||||
|
|
||||||
|
const submitChangeAction = async (action: string) => {
|
||||||
|
const actionMatch = actions.find((a) => a.name === action);
|
||||||
|
if (!actionMatch) {
|
||||||
|
console.error("Invalid action type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const req = await fetch("/api/user/sendUserChanges", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: actionMatch.name,
|
||||||
|
value: actionMatch.sendValue,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await req.json();
|
||||||
|
if (response.error) {
|
||||||
|
console.error("Error updating user data:", response.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to submit change:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="justify-center align-center text-center">
|
<div class="justify-center align-center text-center">
|
||||||
<h1 class="text-3xl text-bold p-2">
|
<h1 class="text-3xl text-bold p-2">
|
||||||
{{ t("settings.greet") }}{{ user || t("settings.defaultname") }}
|
{{ t("settings.greet")
|
||||||
|
}}{{ user || userData.userAccount || t("settings.defaultname") }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-row text-center align-center justify-center p-1">
|
<div class="flex flex-row text-center align-center justify-center p-1">
|
||||||
<span class="text-md p-1 text-nowrap">Change your name: </span>
|
<span class="text-md p-1 text-nowrap">Change your name: </span>
|
||||||
@ -112,35 +158,37 @@ const deleteAccount = async () => {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
||||||
@click="setFirstName"
|
@click="submitChangeAction('NAME')"
|
||||||
|
:disabled="!enterFirstName"
|
||||||
>
|
>
|
||||||
{{ t("settings.submit") }}
|
{{ t("settings.submit") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row text-center align-center justify-center p-1">
|
<div class="flex flex-row text-center align-center justify-center p-1">
|
||||||
<span class="text-md p-1 text-nowrap">Current email: </span>
|
<span class="text-md p-1 text-nowrap">Current email: </span>
|
||||||
<span>{{ useremail }}</span>
|
<span>{{ useremail || "Oh, It's empty." }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row text-center align-center justify-center p-1">
|
<div class="flex flex-row text-center align-center justify-center p-1">
|
||||||
<span class="text-md p-1 text-nowrap">Change your email: </span>
|
<span class="text-md p-1 text-nowrap">Change your email: </span>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
class="h-6 m-1 py-3 rounded"
|
class="h-6 m-1 py-3 rounded"
|
||||||
v-model="enterFirstName"
|
v-model="enteruseremail"
|
||||||
placeholder="Ex: example@gmail.com"
|
placeholder="Ex: example@gmail.com"
|
||||||
/>
|
/>
|
||||||
<!--If it is a valid api key or not.-->
|
<!--If it is a valid api key or not.-->
|
||||||
<BadgeCheckIcon
|
<BadgeCheckIcon
|
||||||
v-if="enterFirstName"
|
v-if="enteruseremail"
|
||||||
class="w-8 h-8 p-1/2 mr-1 text-green-700"
|
class="w-8 h-8 p-1/2 mr-1 text-green-700"
|
||||||
/>
|
/>
|
||||||
<OctagonAlertIcon
|
<OctagonAlertIcon
|
||||||
v-if="!enterFirstName"
|
v-if="!enteruseremail"
|
||||||
class="w-8 h-8 p-1/2 mr-1 text-red-700"
|
class="w-8 h-8 p-1/2 mr-1 text-red-700"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
||||||
@click="setFirstName"
|
@click="submitChangeAction('USER_EMAIL')"
|
||||||
|
:disabled="!enteruseremail"
|
||||||
>
|
>
|
||||||
{{ t("settings.submit") }}
|
{{ t("settings.submit") }}
|
||||||
</button>
|
</button>
|
||||||
@ -233,18 +281,20 @@ const deleteAccount = async () => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
||||||
|
@click="emit('windowopener', 'privacypolicy')"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
||||||
|
@click="emit('windowopener', 'tos')"
|
||||||
>
|
>
|
||||||
TOS
|
TOS
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="justiy-center align-center text-center">
|
<div class="justiy-center align-center text-center">
|
||||||
{{ t("app.settings") }} v0.0.2
|
{{ t("app.settings") }} v0.0.3
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -220,6 +220,88 @@ const associAppWindow = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// OnBoarding
|
||||||
|
// Feedback from: https://hackclub.slack.com/archives/C090DPG6681/p1749303838738019
|
||||||
|
const currentStep = ref(0);
|
||||||
|
const showOnboarding = ref(true);
|
||||||
|
onMounted(() => {
|
||||||
|
showOnboarding.value = !localStorage.getItem("onboardingComplete");
|
||||||
|
});
|
||||||
|
const nextStep = () => {
|
||||||
|
currentStep.value++;
|
||||||
|
};
|
||||||
|
const finishOnboarding = () => {
|
||||||
|
showOnboarding.value = false;
|
||||||
|
localStorage.setItem("onboardingComplete", "true");
|
||||||
|
};
|
||||||
|
/*const onBoarding = [
|
||||||
|
{
|
||||||
|
step: 0,
|
||||||
|
point: "none",
|
||||||
|
text: "Hi! Welcome to the news analyze desktop enviroment!",
|
||||||
|
buttons: [
|
||||||
|
"bypass": nextStep,
|
||||||
|
"contuine": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
point: "top-left",
|
||||||
|
text: "Click here to open applications",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
point: "left-navbar-1",
|
||||||
|
text: "Click here to open the news window",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 3,
|
||||||
|
point: "center",
|
||||||
|
text: "Click here open a news article",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 4,
|
||||||
|
point: "center-close-translate-left",
|
||||||
|
text: "Click here to translate the page.",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 5,
|
||||||
|
point: "center-close-x-left",
|
||||||
|
text: "Click here to close the window",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 6,
|
||||||
|
point: "more-top-right-3",
|
||||||
|
text: "Click here to change the app's language. (YOU WILL LOSE ALL YOUR WINDOWS)",
|
||||||
|
buttons: [
|
||||||
|
"ok": nextStep
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 7,
|
||||||
|
point: "none",
|
||||||
|
text: "That's it, welcome! If you want to learn more, you can go to yhw.tw/newsanalyzedocs.",
|
||||||
|
buttons: [
|
||||||
|
"ok": finishOnboarding
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];*/
|
||||||
|
|
||||||
// Confeti
|
// Confeti
|
||||||
const successcanvas = ref();
|
const successcanvas = ref();
|
||||||
const confetiActive = ref(false);
|
const confetiActive = ref(false);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import sql from "~/server/components/postgres";
|
import sql from "~/server/components/postgres";
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
const { request_change } = body;
|
/*
|
||||||
const userToken = getCookie(event, "token");
|
const userToken = getCookie(event, "token");
|
||||||
if (!userToken) {
|
if (!userToken) {
|
||||||
return {
|
return {
|
||||||
@ -19,5 +19,6 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
if (request_change === "groq_api_key") {
|
if (request_change === "groq_api_key") {
|
||||||
const updateListing = await sql``;
|
const updateListing = await sql``;
|
||||||
}
|
}*/
|
||||||
|
return { body: body };
|
||||||
});
|
});
|
@ -39,6 +39,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
userAccount: fetchViaSQL[0].username,
|
userAccount: fetchViaSQL[0].username,
|
||||||
|
firstName: fetchViaSQL[0].firstName,
|
||||||
requested_action: "CONTINUE",
|
requested_action: "CONTINUE",
|
||||||
email: fetchViaSQL[0].email,
|
email: fetchViaSQL[0].email,
|
||||||
avatarURL: fetchViaSQL[0].avatarurl,
|
avatarURL: fetchViaSQL[0].avatarurl,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user