mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 13:04:23 +00:00
Made a working settings panel & includes the user's info, what is
Some checks are pending
Build and Push Docker Image / build-and-push (push) Waiting to run
Some checks are pending
Build and Push Docker Image / build-and-push (push) Waiting to run
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
6 changed files with 201 additions and 63 deletions
|
@ -21,6 +21,10 @@ Reverse engineering 文檔: [about](/about/)
|
|||
## 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.
|
||||
|
||||
## 如果要開發,你需要
|
||||
- 一個 Postgres 資料庫 (你可以用 Zeabur 跑開發用資料庫,可以用我的 [優惠連結(?](https://zeabur.com/referral?referralCode=hpware),你可以拿到大約150塊的試用金額
|
||||
- 一個 Groq 的 API
|
||||
|
||||
## 為什麼?
|
||||
|
||||
我們使用這個新聞來舉例:
|
||||
|
|
|
@ -1,57 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { BadgeCheckIcon, OctagonAlertIcon } from "lucide-vue-next";
|
||||
import { Input } from "~/components/ui/input";
|
||||
const { t, locale } = useI18n();
|
||||
const user = ref("");
|
||||
const enterFirstName = ref();
|
||||
const useremail = ref();
|
||||
const enteruseremail = ref();
|
||||
onMounted(async () => {
|
||||
const req = await fetch("/api/user/validateUserToken");
|
||||
const res = await req.json();
|
||||
user.value = res.firstName;
|
||||
useremail.value = res;
|
||||
});
|
||||
const setFirstName = async () => {
|
||||
const staticFirstName = "";
|
||||
};
|
||||
|
||||
const logoutAction = () => {};
|
||||
const groqApiKeyRegex = /^gsk_[a-zA-Z0-9]{52}$/;
|
||||
const customApiKey = ref();
|
||||
const isCorrect = ref(false);
|
||||
const submitCustomApiKey = async () => {
|
||||
if (!isCorrect.value) {
|
||||
checkValidApiKey();
|
||||
if (!isCorrect.value) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const apiKey = customApiKey.value;
|
||||
try {
|
||||
const sendApi = await fetch("/api/ai/loadCustomGroqApi", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
apiKey: apiKey,
|
||||
}),
|
||||
});
|
||||
const data = await sendApi.json();
|
||||
if (data.error) {
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const checkValidApiKey = () => {
|
||||
const apiKey = customApiKey.value;
|
||||
if (!apiKey) {
|
||||
isCorrect.value = false;
|
||||
return;
|
||||
}
|
||||
isCorrect.value = groqApiKeyRegex.test(apiKey);
|
||||
};
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
@ -62,10 +11,57 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
const { t, locale } = useI18n();
|
||||
const user = ref("");
|
||||
const enterFirstName = ref();
|
||||
const useremail = ref();
|
||||
const userData = ref({
|
||||
userAccount: "",
|
||||
firstName: "",
|
||||
requested_action: "",
|
||||
email: "",
|
||||
avatarURL: "",
|
||||
firstName: "",
|
||||
});
|
||||
const enteruseremail = ref();
|
||||
onMounted(async () => {
|
||||
const req = await fetch("/api/user/validateUserToken");
|
||||
const res = await req.json();
|
||||
user.value = res.firstName;
|
||||
userData.value = res;
|
||||
useremail.value = res.email;
|
||||
});
|
||||
const setFirstName = async () => {
|
||||
const staticFirstName = "";
|
||||
};
|
||||
|
||||
const emit = defineEmits(["windowopener"]);
|
||||
|
||||
const logoutAction = () => {};
|
||||
|
||||
const groqApiKeyRegex = /^gsk_[a-zA-Z0-9]{52}$/;
|
||||
const customApiKey = ref();
|
||||
const isCorrect = ref(false);
|
||||
const submitCustomApiKey = async () => {
|
||||
if (!isCorrect.value) {
|
||||
checkValidApiKey();
|
||||
if (!isCorrect.value) {
|
||||
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;
|
||||
|
@ -79,6 +75,23 @@ const deleteAccount = async () => {
|
|||
method: "DELETE",
|
||||
});
|
||||
};
|
||||
const apiKey = customApiKey.value;
|
||||
try {
|
||||
const sendApi = await fetch("/api/ai/loadCustomGroqApi", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
apiKey: apiKey,
|
||||
}),
|
||||
});
|
||||
const data = await sendApi.json();
|
||||
if (data.error) {
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* userAccount: fetchViaSQL[0].username,
|
||||
|
@ -87,11 +100,44 @@ const deleteAccount = async () => {
|
|||
avatarURL: fetchViaSQL[0].avatarurl,
|
||||
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>
|
||||
<template>
|
||||
<div class="justify-center align-center text-center">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -112,35 +158,37 @@ const deleteAccount = async () => {
|
|||
/>
|
||||
<button
|
||||
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
||||
@click="setFirstName"
|
||||
@click="submitChangeAction('NAME')"
|
||||
:disabled="!enterFirstName"
|
||||
>
|
||||
{{ t("settings.submit") }}
|
||||
</button>
|
||||
</div>
|
||||
<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>{{ useremail }}</span>
|
||||
<span>{{ useremail || "Oh, It's empty." }}</span>
|
||||
</div>
|
||||
<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>
|
||||
<Input
|
||||
type="text"
|
||||
class="h-6 m-1 py-3 rounded"
|
||||
v-model="enterFirstName"
|
||||
v-model="enteruseremail"
|
||||
placeholder="Ex: example@gmail.com"
|
||||
/>
|
||||
<!--If it is a valid api key or not.-->
|
||||
<BadgeCheckIcon
|
||||
v-if="enterFirstName"
|
||||
v-if="enteruseremail"
|
||||
class="w-8 h-8 p-1/2 mr-1 text-green-700"
|
||||
/>
|
||||
<OctagonAlertIcon
|
||||
v-if="!enterFirstName"
|
||||
v-if="!enteruseremail"
|
||||
class="w-8 h-8 p-1/2 mr-1 text-red-700"
|
||||
/>
|
||||
<button
|
||||
class="p-1 text-sm bg-gray-400/60 rounded text-nowrap"
|
||||
@click="setFirstName"
|
||||
@click="submitChangeAction('USER_EMAIL')"
|
||||
:disabled="!enteruseremail"
|
||||
>
|
||||
{{ t("settings.submit") }}
|
||||
</button>
|
||||
|
@ -233,18 +281,20 @@ const deleteAccount = async () => {
|
|||
>
|
||||
<button
|
||||
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
||||
@click="emit('windowopener', 'privacypolicy')"
|
||||
>
|
||||
Privacy Policy
|
||||
</button>
|
||||
<button
|
||||
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
|
||||
@click="emit('windowopener', 'tos')"
|
||||
>
|
||||
TOS
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="justiy-center align-center text-center">
|
||||
{{ t("app.settings") }} v0.0.2
|
||||
{{ t("app.settings") }} v0.0.3
|
||||
</div>
|
||||
</div>
|
||||
</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
|
||||
const successcanvas = ref();
|
||||
const confetiActive = ref(false);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sql from "~/server/components/postgres";
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { request_change } = body;
|
||||
/*
|
||||
const userToken = getCookie(event, "token");
|
||||
if (!userToken) {
|
||||
return {
|
||||
|
@ -19,5 +19,6 @@ export default defineEventHandler(async (event) => {
|
|||
}
|
||||
if (request_change === "groq_api_key") {
|
||||
const updateListing = await sql``;
|
||||
}
|
||||
}*/
|
||||
return { body: body };
|
||||
});
|
|
@ -39,6 +39,7 @@ export default defineEventHandler(async (event) => {
|
|||
}
|
||||
return {
|
||||
userAccount: fetchViaSQL[0].username,
|
||||
firstName: fetchViaSQL[0].firstName,
|
||||
requested_action: "CONTINUE",
|
||||
email: fetchViaSQL[0].email,
|
||||
avatarURL: fetchViaSQL[0].avatarurl,
|
||||
|
|
|
@ -19,7 +19,7 @@ export async function checkIfUserHasCustomGroqKey(token?: string) {
|
|||
}
|
||||
const fetchUserToken = await sql`
|
||||
select groq_api_key from user_other_data
|
||||
where username=${checkRealToken[0].username}`;
|
||||
where username = ${checkRealToken[0].username}`;
|
||||
if (fetchUserToken.length === 0) {
|
||||
return {
|
||||
status: false,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue