Change font

This commit is contained in:
2025-04-11 22:39:56 +03:00
parent 6aceb65369
commit 966f6e8dde
8 changed files with 5477 additions and 2001 deletions

View File

@@ -2,6 +2,8 @@
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme static { @theme static {
--font-sans: "Geist", sans-serif;
--ui-primary: var(--color-orange-500); --ui-primary: var(--color-orange-500);
--ui-color-primary-50: var(--color-orange-50); --ui-color-primary-50: var(--color-orange-50);

View File

@@ -7,7 +7,7 @@ const { user, clear } = useUserSession();
<UContainer class="max-w-2xl flex justify-between items-center"> <UContainer class="max-w-2xl flex justify-between items-center">
<div class="flex gap-3 items-center"> <div class="flex gap-3 items-center">
<NuxtImg src="/strivify.png" width="36" height="36" /> <NuxtImg src="/strivify.png" width="36" height="36" />
<div class="font-bold text-xl">Strivify</div> <div class="font-bold text-xl tracking-tight">Strivify</div>
</div> </div>
<UDropdownMenu :items="[{ label: 'Log out', onSelect: () => clear() }]"> <UDropdownMenu :items="[{ label: 'Log out', onSelect: () => clear() }]">
<UAvatar <UAvatar

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import AppFooter from "./app-footer.vue"; import AppFooter from "./app-footer.vue";
useHead({ title: "Strivify - Please sign in" });
const { openInPopup } = useUserSession(); const { openInPopup } = useUserSession();
</script> </script>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
useHead({ title: "Strivify" });
const { user } = useUserSession(); const { user } = useUserSession();
const stravaLink = computed(() => { const stravaLink = computed(() => {

View File

@@ -12,6 +12,7 @@ export default defineNuxtConfig({
runtimeConfig: { runtimeConfig: {
webhooksUrl: "", webhooksUrl: "",
stravaVerifyToken: "", stravaVerifyToken: "",
openaiApiKey: "",
}, },
future: { compatibilityVersion: 4 }, future: { compatibilityVersion: 4 },
compatibilityDate: "2025-03-01", compatibilityDate: "2025-03-01",
@@ -19,6 +20,7 @@ export default defineNuxtConfig({
ai: true, ai: true,
database: true, database: true,
}, },
app: { app: {
head: { head: {
link: [ link: [

7308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,14 @@
import { get, omit, tryit } from "radash"; import { get, omit } from "radash";
import { OpenAI } from "openai";
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const body = await readBody(event); const body = await readBody(event);
const db = useDrizzle(); const db = useDrizzle();
const config = useRuntimeConfig();
const ai = hubAI(); const openai = new OpenAI({
apiKey: config.openaiApiKey,
});
const user = await db.query.users.findFirst({ const user = await db.query.users.findFirst({
where: (f, o) => o.eq(f.id, body.owner_id), where: (f, o) => o.eq(f.id, body.owner_id),
@@ -19,35 +23,14 @@ export default defineEventHandler(async (event) => {
const strava = await useStrava(body.owner_id); const strava = await useStrava(body.owner_id);
const activity = (await strava!(`/activities/${body.object_id}`)) as any; const [, activity] = await strava!<any>(`/activities/${body.object_id}`);
const promptActivity = ` const aiResponse = await openai.chat.completions.create({
type: ${get(activity, "type")} model: "gpt-4o-mini",
distance: ${get(activity, "distance")}m messages: [
moving time: ${get(activity, "moving_time")}sec
elapsed time: ${get(activity, "elapsed_time")}sec
total elevation gain: ${get(activity, "total_elevation_gain")}m
start (local): ${get(activity, "start_date_local")}
trainer: ${get(activity, "trainer")}
commute: ${get(activity, "commute")}
calories: ${get(activity, "calories")}
`;
const [aiError, aiResponse] = await tryit(ai.run)(
"@cf/meta/llama-3.1-8b-instruct",
{ {
response_format: { role: "user",
type: "json_schema", content: `
json_schema: {
type: "object",
properties: {
title: "string",
description: "string",
},
required: ["title", "description"],
},
},
prompt: `
Generate a title and a short description for my strava activity. Use my preferred language. Generate a title and a short description for my strava activity. Use my preferred language.
Use ${user?.preferences.data.tone} tone to generate content. Use ${user?.preferences.data.tone} tone to generate content.
Add emojis unless tone is set to minimalist. Add emojis unless tone is set to minimalist.
@@ -57,27 +40,76 @@ export default defineEventHandler(async (event) => {
Weight: ${user?.weight} Weight: ${user?.weight}
Language: ${user?.preferences.data.language} Language: ${user?.preferences.data.language}
The activity data: Activity notes:
${promptActivity} distance is in meters, time is in seconds, don't include average speed. Convert time to hours or minutes, whatever's closer.
`, The activity data in json format from strava:
}, ${JSON.stringify(
);
console.log(
omit(activity, [ omit(activity, [
"map",
"laps", "laps",
"segment_efforts",
"splits_metric",
"splits_standard",
"hide_from_home",
"available_zones",
"map",
"start_date_local",
"gear",
"stats_visibility", "stats_visibility",
"embed_token", "embed_token",
"private_note", "name",
"description",
]), ]),
); )}
`,
await strava!(`activities/${body.object_id}`, { },
method: "PUT", ],
body: { tools: [
name: get(aiResponse, "response.title"), {
description: get(aiResponse, "response.description"), type: "function",
function: {
name: "generate_strava_meta",
description: "Generates Strava metadata.",
parameters: {
type: "object",
properties: {
title: {
type: "string",
description: "The title of the activity",
},
description: {
type: "string",
description: "A short description of the activity",
},
},
required: ["title", "description"],
},
},
},
],
tool_choice: {
type: "function",
function: {
name: "generate_strava_meta",
},
}, },
}); });
const responseObject = JSON.parse(
get(aiResponse, "choices.0.message.tool_calls.0.function.arguments"),
) as { title: string; description: string };
const [stravaError] = await strava!(`activities/${body.object_id}`, {
method: "PUT",
body: {
name: responseObject.title,
description: responseObject.description,
},
});
if (stravaError) {
throw createError({
statusCode: 500,
message: `Strava API: ${stravaError.message}`,
});
}
}); });

View File

@@ -1,4 +1,4 @@
import { get, isEmpty } from "radash"; import { get, isEmpty, tryit } from "radash";
import { isAfter } from "@formkit/tempo"; import { isAfter } from "@formkit/tempo";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { URLSearchParams } from "url"; import { URLSearchParams } from "url";
@@ -57,10 +57,12 @@ export const useStrava = async (userId: number) => {
.where(eq(tables.tokens.userId, userId)); .where(eq(tables.tokens.userId, userId));
} }
return $fetch.create({ return tryit(
$fetch.create({
baseURL: "https://www.strava.com/api/v3/", baseURL: "https://www.strava.com/api/v3/",
onRequest({ options }) { onRequest({ options }) {
options.headers.set("Authorization", `Bearer ${tokens?.accessToken}`); options.headers.set("Authorization", `Bearer ${tokens?.accessToken}`);
}, },
}); }),
);
}; };