Introduce writing styles
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -23,7 +23,8 @@
|
||||
"radash": "^12.1.0",
|
||||
"url": "^0.11.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
"vue-router": "^4.5.0",
|
||||
"zod": "^3.25.55"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.30.6",
|
||||
@@ -13202,12 +13203,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.24.2",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
||||
"version": "3.25.55",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.55.tgz",
|
||||
"integrity": "sha512-219huNnkSLQnLsQ3uaRjXsxMrVm5C9W3OOpEVt2k5tvMKuA8nBSu38e0B//a+he9Iq2dvmk2VyYVlHqiHa4YBA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"radash": "^12.1.0",
|
||||
"url": "^0.11.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
"vue-router": "^4.5.0",
|
||||
"zod": "^3.25.55"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.30.6",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { diff, omit } from "radash";
|
||||
import { trackEvent } from "@aptabase/web";
|
||||
|
||||
useHead({ title: "Ghostwriter" });
|
||||
@@ -14,24 +13,23 @@ interface FormData {
|
||||
enabled: boolean;
|
||||
language: string;
|
||||
units: string;
|
||||
tone: string[];
|
||||
}
|
||||
|
||||
const preferences = useState<FormData>("preferences", () => ({
|
||||
enabled: false,
|
||||
language: "English",
|
||||
units: "Metric",
|
||||
tone: [],
|
||||
}));
|
||||
|
||||
const { status } = useFetch("/api/preferences", {
|
||||
onResponse({ error, response }) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.value = omit(response._data, ["tone"]) as FormData;
|
||||
},
|
||||
const { data, status } = useFetch("/api/preferences", {
|
||||
lazy: true,
|
||||
});
|
||||
|
||||
// @ts-expect-error typing issue - missing transform
|
||||
syncRef(data, preferences, { direction: "ltr", deep: true });
|
||||
|
||||
onMounted(() => {
|
||||
saveOp.resume();
|
||||
});
|
||||
@@ -40,7 +38,7 @@ const saveOp = watchPausable(
|
||||
preferences,
|
||||
async (newData) => {
|
||||
trackEvent("update_preferences", {
|
||||
...newData,
|
||||
enabled: newData.enabled,
|
||||
});
|
||||
|
||||
await $fetch("/api/preferences", {
|
||||
@@ -75,7 +73,9 @@ const saveOp = watchPausable(
|
||||
<div class="font-bold text-lg">❤️ Support</div>
|
||||
<UCard class="">
|
||||
<div class="flex flex-col gap-8">
|
||||
Ghostwriter 👻 is free to use, but it takes time and resources to keep it running smoothly. If you enjoy it, consider supporting the app and its creator - every bit helps!
|
||||
Ghostwriter 👻 is free to use, but it takes time and resources to keep
|
||||
it running smoothly. If you enjoy it, consider supporting the app and
|
||||
its creator - every bit helps!
|
||||
</div>
|
||||
<template #footer>
|
||||
<ULink href="https://buymeacoffee.com/mariosant" target="_blank">
|
||||
@@ -133,6 +133,22 @@ const saveOp = watchPausable(
|
||||
/>
|
||||
</template>
|
||||
</CardField>
|
||||
|
||||
<CardField>
|
||||
<template #title> Tone </template>
|
||||
<template #description>
|
||||
Choose one or more writing styles you like.
|
||||
</template>
|
||||
<template #value>
|
||||
<USelect
|
||||
multiple
|
||||
class="min-w-28 max-w-64"
|
||||
v-model="preferences.tone"
|
||||
:items="tones"
|
||||
placeholder="None specified (Use all)"
|
||||
/>
|
||||
</template>
|
||||
</CardField>
|
||||
</div>
|
||||
</UCard>
|
||||
</UContainer>
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import * as z from "zod";
|
||||
import {
|
||||
availableLanguages,
|
||||
availableTones,
|
||||
availableUnits,
|
||||
} from "~/shared/constants";
|
||||
//
|
||||
const bodySchema = z.strictObject({
|
||||
enabled: z.boolean(),
|
||||
language: z.enum(availableLanguages),
|
||||
units: z.enum(availableUnits),
|
||||
tone: z.array(z.enum(availableTones)),
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await requireUserSession(event);
|
||||
const body = await readValidatedBody(event, (body) => bodySchema.parse(body));
|
||||
|
||||
const db = useDrizzle();
|
||||
const body = await readBody(event);
|
||||
|
||||
const [preferences] = await db
|
||||
.update(tables.preferences)
|
||||
@@ -10,6 +25,7 @@ export default defineEventHandler(async (event) => {
|
||||
enabled: body.enabled,
|
||||
language: body.language,
|
||||
units: body.units,
|
||||
tone: body.tone,
|
||||
},
|
||||
})
|
||||
.where(eq(tables.preferences.userId, session.user.id))
|
||||
|
||||
@@ -33,11 +33,13 @@ export const preferences = pgTable("preferences", {
|
||||
enabled: boolean;
|
||||
language: string;
|
||||
units: "Imperial" | "Metric";
|
||||
tone?: string[];
|
||||
}>()
|
||||
.$defaultFn(() => ({
|
||||
enabled: true,
|
||||
language: "English",
|
||||
units: "Metric",
|
||||
tone: [],
|
||||
})),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { chain, draw, get, omit, pick, tryit } from "radash";
|
||||
import { chain, draw, get, isEmpty, omit, pick, tryit } from "radash";
|
||||
import { safeDestr } from "destr";
|
||||
import { User } from "./drizzle";
|
||||
import { availableTones } from "~/shared/constants";
|
||||
|
||||
const promo = "Written by https://ghostwriter.rocks 👻";
|
||||
|
||||
@@ -111,18 +112,13 @@ export const createActivityContent = async ({
|
||||
}: {
|
||||
currentActivity: Activity;
|
||||
previousActivities: Activity[];
|
||||
user: User & { preferences: any };
|
||||
user: User & { preferences: Preferences };
|
||||
}) => {
|
||||
const openai = useOpenAI();
|
||||
|
||||
const tone = draw([
|
||||
"casual",
|
||||
"funny",
|
||||
"epic",
|
||||
"poetic",
|
||||
"reflective",
|
||||
"snarky",
|
||||
]);
|
||||
const tone = isEmpty(user.preferences.data?.tone)
|
||||
? (draw(availableTones) as string)
|
||||
: draw(user.preferences.data!.tone!);
|
||||
|
||||
const length = draw([
|
||||
"short",
|
||||
@@ -135,7 +131,7 @@ export const createActivityContent = async ({
|
||||
const prompt = `
|
||||
Generate a short title and a ${length}-lengthed description for my strava activity. Use my preferred language and unit system.
|
||||
Try to not exaggerate as I am using Strava often and I want my activites to be unique and easy to read. Don't say things like nothing too fancy or wild.
|
||||
Use a little bit of ${tone} to make things less boring. Highlight any PR only if available, do not mention them if no PRs.
|
||||
Use a little bit of ${tone} tone to make things less boring. Highlight any PR only if available, do not mention them if no PRs.
|
||||
Maybe comment if any interesting fact in comparison to previous activities.
|
||||
|
||||
Add #${tone} at the end of the description. Depending the length of the description, maybe add more hashtags.
|
||||
|
||||
@@ -12,4 +12,5 @@ export function useDrizzle() {
|
||||
}
|
||||
|
||||
export type User = typeof schema.users.$inferSelect;
|
||||
export type Preferences = typeof schema.preferences.$inferSelect;
|
||||
export type Tokens = typeof schema.tokens.$inferSelect;
|
||||
|
||||
24
shared/constants.ts
Normal file
24
shared/constants.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const availableTones = [
|
||||
"Adventure",
|
||||
"Casual",
|
||||
"Competitive",
|
||||
"Epic",
|
||||
"Funny",
|
||||
"Minimalist",
|
||||
"Motivational",
|
||||
"Poetic",
|
||||
"Reflective",
|
||||
"Snarky",
|
||||
"Witty",
|
||||
] as const;
|
||||
|
||||
export const availableLanguages = [
|
||||
"English",
|
||||
"Greek",
|
||||
"German",
|
||||
"Italian",
|
||||
"Polish",
|
||||
"Spanish",
|
||||
] as const;
|
||||
|
||||
export const availableUnits = ["Imperial", "Metric"] as const;
|
||||
@@ -1,23 +1,11 @@
|
||||
export const languages = ref([
|
||||
"English",
|
||||
"Greek",
|
||||
"German",
|
||||
"Italian",
|
||||
"Polish",
|
||||
"Spanish",
|
||||
]);
|
||||
import {
|
||||
availableLanguages,
|
||||
availableTones,
|
||||
availableUnits,
|
||||
} from "~/shared/constants";
|
||||
|
||||
export const tones = ref([
|
||||
"Motivational",
|
||||
"Casual",
|
||||
"Funny",
|
||||
"Epic",
|
||||
"Minimalist",
|
||||
"Reflective",
|
||||
"Poetic",
|
||||
"Competitive",
|
||||
"Adventure",
|
||||
"Snarky",
|
||||
]);
|
||||
export const languages = ref(availableLanguages);
|
||||
|
||||
export const units = ref(["Imperial", "Metric"]);
|
||||
export const tones = ref(availableTones);
|
||||
|
||||
export const units = ref(availableUnits);
|
||||
|
||||
Reference in New Issue
Block a user