feat: add some stuff
3
.gitignore
vendored
@ -8,3 +8,6 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
pocketbase
|
||||||
|
pb_data/
|
||||||
|
google-credentials.json
|
||||||
|
35
package.json
@ -12,26 +12,27 @@
|
|||||||
"format": "prettier --plugin-search-dir . --write ."
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^2.0.0",
|
"@sveltejs/adapter-auto": "^2.1.1",
|
||||||
"@sveltejs/kit": "^1.20.4",
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@sveltejs/kit": "^1.27.5",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||||
"eslint": "^8.28.0",
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-plugin-svelte": "^2.30.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"prettier": "^2.8.0",
|
"eslint-plugin-svelte": "^2.35.0",
|
||||||
"prettier-plugin-svelte": "^2.10.1",
|
"prettier": "^3.0.3",
|
||||||
"svelte": "^4.0.5",
|
"prettier-plugin-svelte": "^3.1.0",
|
||||||
"svelte-check": "^3.4.3",
|
"svelte": "^4.2.3",
|
||||||
"tslib": "^2.4.1",
|
"svelte-check": "^3.6.0",
|
||||||
"typescript": "^5.0.0",
|
"tslib": "^2.6.2",
|
||||||
"vite": "^4.4.2"
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^4.5.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.441.0",
|
"googleapis": "^128.0.0",
|
||||||
"@sveltejs/adapter-node": "^1.3.1",
|
|
||||||
"minio": "^7.1.3",
|
"minio": "^7.1.3",
|
||||||
"openai": "^4.15.4"
|
"openai": "^4.17.4",
|
||||||
|
"pocketbase": "^0.19.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
111
pb_migrations/1699786406_created_invites.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const collection = new Collection({
|
||||||
|
"id": "zfbbb4gbdk9dh6k",
|
||||||
|
"created": "2023-11-12 10:53:26.506Z",
|
||||||
|
"updated": "2023-11-12 10:53:26.506Z",
|
||||||
|
"name": "invites",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "oexf4zse",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "cdyuuznb",
|
||||||
|
"name": "noble_name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "5m2msvat",
|
||||||
|
"name": "hair_type",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "runxpwtr",
|
||||||
|
"name": "hair_color",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "p1fnj4yw",
|
||||||
|
"name": "hair_length",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "rdxgmjz0",
|
||||||
|
"name": "portrait_url",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Dao(db).saveCollection(collection);
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db);
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k");
|
||||||
|
|
||||||
|
return dao.deleteCollection(collection);
|
||||||
|
})
|
52
pb_migrations/1699787376_updated_invites.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("rdxgmjz0")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "artkpdru",
|
||||||
|
"name": "portrait",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"maxSize": 5242880,
|
||||||
|
"mimeTypes": [],
|
||||||
|
"thumbs": [],
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "rdxgmjz0",
|
||||||
|
"name": "portrait_url",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("artkpdru")
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
44
pb_migrations/1699787392_updated_invites.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// update
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "oexf4zse",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": true,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// update
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "oexf4zse",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
31
pb_migrations/1699803220_updated_invites.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "ghj1hkic",
|
||||||
|
"name": "confidence",
|
||||||
|
"type": "number",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"noDecimal": false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("ghj1hkic")
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
27
pb_migrations/1699803436_updated_invites.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "ab1nrrnj",
|
||||||
|
"name": "portrait_public",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("ab1nrrnj")
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
16
pb_migrations/1699804056_updated_invites.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
collection.createRule = ""
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
collection.createRule = null
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
52
pb_migrations/1699804231_updated_invites.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("artkpdru")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "nplgrnec",
|
||||||
|
"name": "portrait",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k")
|
||||||
|
|
||||||
|
// add
|
||||||
|
collection.schema.addField(new SchemaField({
|
||||||
|
"system": false,
|
||||||
|
"id": "artkpdru",
|
||||||
|
"name": "portrait",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"maxSize": 5242880,
|
||||||
|
"mimeTypes": [],
|
||||||
|
"thumbs": [],
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.schema.removeField("nplgrnec")
|
||||||
|
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
1809
pnpm-lock.yaml
37
src/lib/components/ImageFrame.svelte
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let src: string;
|
||||||
|
export let alt = '';
|
||||||
|
const u = new URL(src);
|
||||||
|
const filename = u.pathname.split('/').pop() || '9a8sda';
|
||||||
|
const int = parseInt(filename?.slice(0, 8), 16) % 7;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="frame" style="--frame: url(/frames/frame_0{int}.png)">
|
||||||
|
<img {src} {alt} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.frame {
|
||||||
|
position: relative;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
.frame:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--frame);
|
||||||
|
background-position: center center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
filter: drop-shadow(0px 0px 20px black);
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
z-index: -1;
|
||||||
|
max-width: 70%;
|
||||||
|
max-height: 70%;
|
||||||
|
margin-left: 15%;
|
||||||
|
margin-top: 12%;
|
||||||
|
}
|
||||||
|
</style>
|
100
src/lib/components/InputRange.svelte
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let min = 0;
|
||||||
|
export let max = 100;
|
||||||
|
export let value = 50;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="range" {min} {max} bind:value />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input[type='range'] {
|
||||||
|
height: 32px;
|
||||||
|
appearance: none;
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
input[type='range']:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input[type='range']::-webkit-slider-runnable-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
animate: 0.2s;
|
||||||
|
box-shadow: 0px 0px 0px #000000;
|
||||||
|
background: #d9c556;
|
||||||
|
border-radius: 29px;
|
||||||
|
border: 0px solid #010101;
|
||||||
|
}
|
||||||
|
input[type='range']::-webkit-slider-thumb {
|
||||||
|
box-shadow: 0px 0px 0px #000031;
|
||||||
|
border: 0px solid #00001e;
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
border-radius: 26px;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin-top: -9.5px;
|
||||||
|
}
|
||||||
|
input[type='range']:focus::-webkit-slider-runnable-track {
|
||||||
|
background: #d9c556;
|
||||||
|
}
|
||||||
|
input[type='range']::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
animate: 0.2s;
|
||||||
|
box-shadow: 0px 0px 0px #000000;
|
||||||
|
background: #d9c556;
|
||||||
|
border-radius: 29px;
|
||||||
|
border: 0px solid #010101;
|
||||||
|
}
|
||||||
|
input[type='range']::-moz-range-thumb {
|
||||||
|
box-shadow: 0px 0px 0px #000031;
|
||||||
|
border: 0px solid #00001e;
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
border-radius: 26px;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type='range']::-ms-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
animate: 0.2s;
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
input[type='range']::-ms-fill-lower {
|
||||||
|
background: #d9c556;
|
||||||
|
border: 0px solid #010101;
|
||||||
|
border-radius: 58px;
|
||||||
|
box-shadow: 0px 0px 0px #000000;
|
||||||
|
}
|
||||||
|
input[type='range']::-ms-fill-upper {
|
||||||
|
background: #d9c556;
|
||||||
|
border: 0px solid #010101;
|
||||||
|
border-radius: 58px;
|
||||||
|
box-shadow: 0px 0px 0px #000000;
|
||||||
|
}
|
||||||
|
input[type='range']::-ms-thumb {
|
||||||
|
margin-top: 1px;
|
||||||
|
box-shadow: 0px 0px 0px #000031;
|
||||||
|
border: 0px solid #00001e;
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
border-radius: 26px;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type='range']:focus::-ms-fill-lower {
|
||||||
|
background: #d9c556;
|
||||||
|
}
|
||||||
|
input[type='range']:focus::-ms-fill-upper {
|
||||||
|
background: #d9c556;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,64 +1,318 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { persisted } from '$lib/helpers/localStore';
|
import { persisted } from '$lib/helpers/localStore';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade, slide } from 'svelte/transition';
|
||||||
import TextSplit from './TextSplit.svelte';
|
import TextSplit from './TextSplit.svelte';
|
||||||
|
import ImageFrame from './ImageFrame.svelte';
|
||||||
|
import Button from './button.svelte';
|
||||||
|
import InputRange from './InputRange.svelte';
|
||||||
|
|
||||||
let tempName = '';
|
let data = persisted<{
|
||||||
|
name: string;
|
||||||
|
nameAccepted?: boolean;
|
||||||
|
|
||||||
let data = persisted('data', {
|
provideAdelsTitel?: boolean;
|
||||||
|
adelsTitel?: string;
|
||||||
|
adelsTitelAccepted?: boolean;
|
||||||
|
adelsTitelSuggestions?: string[];
|
||||||
|
|
||||||
|
providePortrait?: boolean;
|
||||||
|
portraitUrl?: string;
|
||||||
|
portraitHairType?: 'straight' | 'curly' | 'bald';
|
||||||
|
portraitHairColor?: 'red' | 'brown' | 'blonde' | 'black' | 'grey' | 'white';
|
||||||
|
portraitHairLength?: 'short' | 'medium' | 'long';
|
||||||
|
portraitAccepted?: boolean;
|
||||||
|
portraitPublic?: boolean;
|
||||||
|
|
||||||
|
confidence?: number;
|
||||||
|
confidenceAccepted?: boolean;
|
||||||
|
|
||||||
|
createPersonality?: boolean;
|
||||||
|
}>('data', {
|
||||||
name: '',
|
name: '',
|
||||||
adelsTitel: ''
|
nameAccepted: false,
|
||||||
|
adelsTitel: undefined,
|
||||||
|
adelsTitelSuggestions: [],
|
||||||
|
provideAdelsTitel: undefined,
|
||||||
|
confidence: undefined,
|
||||||
|
confidenceAccepted: false,
|
||||||
|
createPersonality: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let nameInputEl: HTMLInputElement;
|
||||||
|
|
||||||
let loadingAdelsTitel = false;
|
let loadingAdelsTitel = false;
|
||||||
async function fetchAdelsTitel() {
|
async function fetchAdelsTitel() {
|
||||||
if (loadingAdelsTitel) return;
|
if (loadingAdelsTitel) return;
|
||||||
loadingAdelsTitel = true;
|
loadingAdelsTitel = true;
|
||||||
|
|
||||||
const res = await fetch('https://adels-generator.herokuapp.com/');
|
const res = await fetch('/api/ai/name/' + $data.name);
|
||||||
const data = await res.json();
|
const json = await res.json();
|
||||||
$data.adelsTitel = data.adelsTitel;
|
$data.adelsTitelSuggestions = json;
|
||||||
loadingAdelsTitel = false;
|
loadingAdelsTitel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let loadingPortrait = false;
|
||||||
|
async function fetchPortrait() {
|
||||||
|
if (loadingPortrait) return;
|
||||||
|
loadingPortrait = true;
|
||||||
|
|
||||||
|
const res = await fetch(`/api/ai/image/${$data.adelsTitel || $data.name}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
hairType: $data.portraitHairType,
|
||||||
|
hairColor: $data.portraitHairColor,
|
||||||
|
hairLength: $data.portraitHairLength
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
$data.portraitUrl = json.url;
|
||||||
|
loadingPortrait = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isSubmitting = false;
|
||||||
|
async function submit() {
|
||||||
|
if (isSubmitting) return;
|
||||||
|
isSubmitting = true;
|
||||||
|
const res = await fetch(`/api/invites`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify($data)
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section in:fade={{ delay: 2000 }}>
|
<div class="wrapper">
|
||||||
{#if $data.name}
|
<section in:slide>
|
||||||
<h3>Name: {$data.name}</h3>
|
<TextSplit content="Wie lautet euer Name?" />
|
||||||
|
<input bind:this={nameInputEl} placeholder="Name" type="text" bind:value={$data.name} />
|
||||||
|
{#if !$data.nameAccepted}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$data.name = '';
|
$data.nameAccepted = true;
|
||||||
}}>name ändern</button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<TextSplit content="Fantastisch, wie lautet euer Name?" />
|
|
||||||
<input type="text" bind:value={tempName} />
|
|
||||||
{#if tempName}
|
|
||||||
<button
|
|
||||||
on:click={() => {
|
|
||||||
$data.name = tempName;
|
|
||||||
tempName = '';
|
|
||||||
fetchAdelsTitel();
|
|
||||||
}}>name speichern</button
|
}}>name speichern</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if $data.name}
|
{#if ($data.name && $data.nameAccepted) || $data.confidence !== undefined}
|
||||||
<section in:fade />
|
<section in:slide={{ delay: 0, duration: 500 }}>
|
||||||
|
<TextSplit content="Wie sicher können wir mit eurem Erscheinen rechnen?" delay={0} />
|
||||||
|
<div in:fade={{ delay: 1200 }}>
|
||||||
|
<InputRange bind:value={$data.confidence} />
|
||||||
|
</div>
|
||||||
|
{#if !$data.confidenceAccepted}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.confidenceAccepted = true;
|
||||||
|
}}>ok</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if $data.confidence !== undefined && $data.confidenceAccepted && $data.createPersonality === undefined}
|
||||||
|
<section in:slide out:slide>
|
||||||
|
<TextSplit content="Sollen wir eine alternative Persönlichkeit für euch finden?" />
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.createPersonality = true;
|
||||||
|
}}>Ja</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.createPersonality = false;
|
||||||
|
}}>Nein</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{#if $data.adelsTitel}
|
||||||
|
{$data.adelsTitel}
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $data.createPersonality === true}
|
||||||
|
<section in:fade out:slide>
|
||||||
|
{#if $data.provideAdelsTitel === undefined}
|
||||||
|
<TextSplit content="Sollen wir einen Adelstitel vorschlagen?" />
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
fetchAdelsTitel();
|
||||||
|
$data.provideAdelsTitel = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ja</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.adelsTitel = '';
|
||||||
|
$data.provideAdelsTitel = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
nein</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if loadingAdelsTitel}
|
||||||
|
<p>Loading Adelstitel</p>
|
||||||
|
{:else if typeof $data.adelsTitel === 'string'}
|
||||||
|
<TextSplit content="Euer Adelstitel" />
|
||||||
|
<input placeholder="Name" type="text" bind:value={$data.adelsTitel} />
|
||||||
|
{#if !$data.adelsTitelAccepted}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.adelsTitelAccepted = true;
|
||||||
|
}}>akzeptieren</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{:else if $data.adelsTitelSuggestions?.length}
|
||||||
|
<p>Adelstitel Vorschläge</p>
|
||||||
|
<hr />
|
||||||
|
{#each $data.adelsTitelSuggestions as suggestion}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.adelsTitel = suggestion;
|
||||||
|
}}>{suggestion}</button
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
<hr />
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
fetchAdelsTitel();
|
||||||
|
}}>neue vorschläge</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $data.adelsTitel && $data.adelsTitelAccepted && $data.providePortrait !== false}
|
||||||
|
<section transition:slide>
|
||||||
|
{#if $data.providePortrait === undefined}
|
||||||
|
<TextSplit content="Sollen unsere Künstler ein Portrait eurer Figur anfertigen lassen?" />
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.providePortrait = true;
|
||||||
|
}}>ja</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.providePortrait = true;
|
||||||
|
}}>false</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $data.providePortrait && !loadingPortrait && !$data.portraitUrl}
|
||||||
|
<p>
|
||||||
|
Wir werden “{$data.adelsTitel || $data.name}” mit
|
||||||
|
<select placeholder="Typ" bind:value={$data.portraitHairType}>
|
||||||
|
<option value="straight">glatten</option>
|
||||||
|
<option value="curly">lockigen</option>
|
||||||
|
<option value="wavy">welligen</option>
|
||||||
|
</select>
|
||||||
|
<select placeholder="Länge" bind:value={$data.portraitHairLength}>
|
||||||
|
<option value="long">langen</option>
|
||||||
|
<option value="medium">medium-langen</option>
|
||||||
|
<option value="short">kurzen</option>
|
||||||
|
</select>,
|
||||||
|
<select placeholder="Farbe" bind:value={$data.portraitHairColor}>
|
||||||
|
<option value="red">roten</option>
|
||||||
|
<option value="black">schwarzen</option>
|
||||||
|
<option value="blond">blonden</option>
|
||||||
|
<option value="braunem">braunen</option>
|
||||||
|
</select> Haaren zeichnen?
|
||||||
|
</p>
|
||||||
|
<button on:click={() => fetchPortrait()}> porträt malen (~30 Sekunden)</button>
|
||||||
|
{:else if loadingPortrait}
|
||||||
|
<p>Portrait wird gemalt</p>
|
||||||
|
{:else if $data.portraitUrl}
|
||||||
|
<div in:slide={{ duration: 2000 }} class="portrait">
|
||||||
|
<ImageFrame src={$data.portraitUrl} alt="portrait" />
|
||||||
|
|
||||||
|
{#if !$data.portraitAccepted}
|
||||||
|
<input bind:checked={$data.portraitPublic} type="checkbox" id="display-portrait" />
|
||||||
|
<label for="display-portrait">Porträt öffentlich anzeigen</label>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.portraitAccepted = true;
|
||||||
|
}}>portrait akzeptieren</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$data.portraitUrl = undefined;
|
||||||
|
}}>neues portrait</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $data.portraitAccepted}
|
||||||
|
<div class="button-wrapper">
|
||||||
|
<Button on:click={() => submit()}>Einladung abschicken</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-block: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
padding-block: 50px;
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 200px;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50px;
|
||||||
|
padding-bottom: 200px;
|
||||||
|
}
|
||||||
|
.portrait {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type='text'] {
|
||||||
|
font-family: 'Parisienne', cursive;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: fit-content;
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
input[type='text']::after {
|
||||||
|
content: '';
|
||||||
|
background: white;
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
section {
|
section {
|
||||||
color: white;
|
color: white;
|
||||||
|
width: 100%;
|
||||||
font-family: 'Parisienne', cursive;
|
font-family: 'Parisienne', cursive;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
section {
|
||||||
gap: 20px;
|
border-width: 30px;
|
||||||
|
border-style: solid;
|
||||||
|
border-image: url(/border.svg);
|
||||||
|
border-image-repeat: stretch;
|
||||||
|
border-image-slice: 100%;
|
||||||
|
border-image-slice: 24% 23%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,21 +6,20 @@
|
|||||||
let total = delay;
|
let total = delay;
|
||||||
for (let i = 0; i < words.length; i++) {
|
for (let i = 0; i < words.length; i++) {
|
||||||
const delay = total + words[i].length * 35;
|
const delay = total + words[i].length * 35;
|
||||||
|
delays.push(total);
|
||||||
total = delay;
|
total = delay;
|
||||||
delays.push(delay);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#each words as word, i}
|
{#each words as word, i}
|
||||||
<span class="word" style="--delay:{2000 + delays[i]}ms">{word}</span>
|
<span class="word" style="--delay:{delays[i]}ms">{word}</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -31,7 +30,8 @@
|
|||||||
margin: 0 0.1em;
|
margin: 0 0.1em;
|
||||||
animation: fadeIn 1s ease forwards;
|
animation: fadeIn 1s ease forwards;
|
||||||
animation-delay: var(--delay);
|
animation-delay: var(--delay);
|
||||||
color: white;
|
font-size: 0.7em;
|
||||||
|
color: #fffa;
|
||||||
}
|
}
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
|
44
src/lib/components/selection/Option.svelte
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getSelectionContext } from './context';
|
||||||
|
|
||||||
|
const { selected: _selected } = getSelectionContext();
|
||||||
|
|
||||||
|
export let value = '';
|
||||||
|
export let selected: boolean = false;
|
||||||
|
$: if (selected) {
|
||||||
|
$_selected = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelected() {
|
||||||
|
if ($_selected === value) {
|
||||||
|
selected = true;
|
||||||
|
} else {
|
||||||
|
selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if ($_selected) updateSelected();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $_selected !== value}
|
||||||
|
<div class="wrapper" class:selected={$_selected === value}>
|
||||||
|
<input type="checkbox" name="" id="" bind:checked={selected} />
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
$_selected = value;
|
||||||
|
}}><slot /></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
color: black;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.wrapper.selected {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
47
src/lib/components/selection/Selection.svelte
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createSelectionContext } from './context';
|
||||||
|
|
||||||
|
const { selected } = createSelectionContext();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="dropdown">
|
||||||
|
{#if !$selected}
|
||||||
|
<button class="dropbtn">Placeholder</button>
|
||||||
|
{:else}
|
||||||
|
<div class="spacer"></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropbtn {
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
background: #fff;
|
||||||
|
position: absolute;
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1;
|
||||||
|
max-height: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {
|
||||||
|
display: block;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
18
src/lib/components/selection/context.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export function getSelectionContext(): ReturnType<typeof createSelectionContext> {
|
||||||
|
return getContext('selection')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSelectionContext() {
|
||||||
|
|
||||||
|
const ctx = {
|
||||||
|
selected: writable<string | undefined>()
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext('selection', ctx)
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
|
||||||
|
}
|
12
src/lib/components/selection/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import _Selection from './Selection.svelte';
|
||||||
|
import _Option from './Option.svelte';
|
||||||
|
|
||||||
|
type SelectionType = typeof _Selection & {
|
||||||
|
Option: typeof _Option
|
||||||
|
}
|
||||||
|
|
||||||
|
const Selection = _Selection as SelectionType;
|
||||||
|
|
||||||
|
Selection.Option = _Option;
|
||||||
|
|
||||||
|
export { Selection }
|
@ -1,22 +0,0 @@
|
|||||||
import OpenAI from 'openai';
|
|
||||||
import { OPENAI_API_KEY } from '$env/static/private';
|
|
||||||
|
|
||||||
const openai = new OpenAI({
|
|
||||||
apiKey: OPENAI_API_KEY,
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function chat(prompt: string) {
|
|
||||||
|
|
||||||
const chatCompletion = await openai.chat.completions.create({
|
|
||||||
model: "gpt-4",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
"content": prompt,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = chatCompletion.choices[0].message.content;
|
|
||||||
return res;
|
|
||||||
}
|
|
40
src/lib/helpers/openai.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import OpenAI from 'openai';
|
||||||
|
import { OPENAI_API_KEY } from '$env/static/private';
|
||||||
|
|
||||||
|
const openai = new OpenAI({
|
||||||
|
apiKey: OPENAI_API_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
function processChatGptResult(resultString: string) {
|
||||||
|
// Split the string by newline
|
||||||
|
const lines: string[] = resultString.split('\n');
|
||||||
|
|
||||||
|
// Remove enumeration at the beginning of each line
|
||||||
|
const processedLines: string[] = lines.map(line => line.replace(/^\s*[\d.-]+\s*/, ''));
|
||||||
|
|
||||||
|
return processedLines.filter(line => line.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function chat(prompt: string, { isList, temperature }: { isList?: boolean, temperature?: number } = {}) {
|
||||||
|
|
||||||
|
const chatCompletion = await openai.chat.completions.create({
|
||||||
|
model: "gpt-4",
|
||||||
|
temperature: temperature ?? 0.9,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = chatCompletion.choices[0].message.content;
|
||||||
|
|
||||||
|
if (res && isList) return processChatGptResult(res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function image(prompt: string) {
|
||||||
|
return openai.images.generate({ model: "dall-e-3", prompt });
|
||||||
|
}
|
17
src/lib/helpers/pb.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Pocketbase from "pocketbase"
|
||||||
|
import { POCKETBASE_URL } from "$env/dynamic/private"
|
||||||
|
|
||||||
|
const pb = new Pocketbase(POCKETBASE_URL || "http://localhost:8090");
|
||||||
|
|
||||||
|
export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string }) {
|
||||||
|
return pb.collection("invites").create({
|
||||||
|
name,
|
||||||
|
confidence,
|
||||||
|
portrait,
|
||||||
|
portrait_public,
|
||||||
|
noble_name,
|
||||||
|
hair_type,
|
||||||
|
hair_length,
|
||||||
|
hair_color
|
||||||
|
})
|
||||||
|
}
|
114
src/lib/helpers/sheets.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { env } from "$env/dynamic/private";
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
const { GOOGLE_SHEET_ID, GOOGLE_APPLICATION_CREDENTIALS } = env;
|
||||||
|
|
||||||
|
const auth = GOOGLE_APPLICATION_CREDENTIALS && new google.auth.GoogleAuth({
|
||||||
|
keyFile: GOOGLE_APPLICATION_CREDENTIALS, //the key file
|
||||||
|
//url to spreadsheets API
|
||||||
|
scopes: 'https://www.googleapis.com/auth/spreadsheets'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const googleApi = (async () => {
|
||||||
|
|
||||||
|
if (!auth) return;
|
||||||
|
|
||||||
|
const authClientObject = await auth.getClient();
|
||||||
|
|
||||||
|
const googleSheetsInstance = google.sheets({ version: 'v4', auth: authClientObject });
|
||||||
|
|
||||||
|
return googleSheetsInstance;
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function getSheet() {
|
||||||
|
return await (
|
||||||
|
await googleApi
|
||||||
|
).spreadsheets.values.get({
|
||||||
|
auth, //auth object
|
||||||
|
spreadsheetId: GOOGLE_SHEET_ID, // spreadsheet id
|
||||||
|
range: 'Gäste' //range of cells to read from.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBoolean(o, key: string) {
|
||||||
|
if (key in o) {
|
||||||
|
o[key] = o[key].length ? !!parseInt(o[key]) : o[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumber(o, key: string) {
|
||||||
|
if (key in o) {
|
||||||
|
o[key] = o[key].length ? parseInt(o[key]) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getData(): Promise<{ Name: string; Alter: number }[]> {
|
||||||
|
const raw = await getSheet();
|
||||||
|
|
||||||
|
const _rows = raw.data.values;
|
||||||
|
|
||||||
|
if (!_rows) return [];
|
||||||
|
|
||||||
|
const [headers, ...rows] = _rows;
|
||||||
|
|
||||||
|
function parseRow(row: string[]) {
|
||||||
|
const o: {
|
||||||
|
schlafen?: boolean;
|
||||||
|
frauen?: boolean;
|
||||||
|
single?: boolean;
|
||||||
|
männer?: boolean;
|
||||||
|
veggies?: boolean;
|
||||||
|
Name: string;
|
||||||
|
Alter: number;
|
||||||
|
} = {
|
||||||
|
Name: '',
|
||||||
|
Alter: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
row.forEach((v, i) => {
|
||||||
|
o[headers[i]] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
parseBoolean(o, 'Schlafen');
|
||||||
|
parseBoolean(o, 'Frauen');
|
||||||
|
parseBoolean(o, 'Single');
|
||||||
|
parseBoolean(o, 'Männer');
|
||||||
|
parseBoolean(o, 'Veggies');
|
||||||
|
|
||||||
|
parseNumber(o, 'Alter');
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.map((r: string[]) => parseRow(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastGetUpdate: number;
|
||||||
|
let cacheData: { Name: string; Alter: number }[];
|
||||||
|
export async function getData() {
|
||||||
|
if (!lastGetUpdate || Date.now() - 10000 > lastGetUpdate) {
|
||||||
|
cacheData = await _getData();
|
||||||
|
lastGetUpdate = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addPerson({ name, confidence, noble_name }: { name: string, confidence: number, noble_name: string }): Promise<unknown> {
|
||||||
|
|
||||||
|
const api = await googleApi;
|
||||||
|
|
||||||
|
if (!api) return;
|
||||||
|
|
||||||
|
console.log({ name, confidence, noble_name });
|
||||||
|
|
||||||
|
return api.spreadsheets.values.append({
|
||||||
|
auth, //auth object
|
||||||
|
spreadsheetId: GOOGLE_SHEET_ID, // spreadsheet id
|
||||||
|
range: 'Gäste', //range of cells to read from.
|
||||||
|
valueInputOption: 'RAW',
|
||||||
|
resource: {
|
||||||
|
values: [[name, `${Number(confidence).toFixed(2).replace(".", ",")}%`, '', '', '', '', '', '', '', noble_name]]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -17,13 +17,13 @@
|
|||||||
let questionVisible = false;
|
let questionVisible = false;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// curtainsVisible = true;
|
curtainsVisible = true;
|
||||||
curtainsVisible = false;
|
// curtainsVisible = false;
|
||||||
maskVisible = true;
|
// maskVisible = true;
|
||||||
maskSmall = true;
|
// maskSmall = true;
|
||||||
questionVisible = true;
|
// questionVisible = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// buttonVisible = true;
|
buttonVisible = true;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
25
src/routes/api/ai/frame/+server.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { json } from "@sveltejs/kit";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
import { putObject } from "$lib/helpers/minio";
|
||||||
|
import { generateImage } from "$lib/helpers/stability";
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
|
||||||
|
const prompt = `golden brown rectangular picture frame, filled with pure red color, Charles Vess, opulence, mystery, elegance`;
|
||||||
|
const negativePrompt = "blurry, persons, figure"
|
||||||
|
|
||||||
|
const a = performance.now()
|
||||||
|
const image = await generateImage(prompt, negativePrompt);
|
||||||
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
|
console.log({ duration })
|
||||||
|
|
||||||
|
const imageName = `${image.seed}-frame.png`
|
||||||
|
|
||||||
|
const res = await putObject(imageName, Buffer.from(image.base64, 'base64'), { "Content-Type": "image/png" });
|
||||||
|
|
||||||
|
return json({
|
||||||
|
...res,
|
||||||
|
url: `https://s3.app.max-richter.dev/silvester23/${imageName}`
|
||||||
|
})
|
||||||
|
}
|
@ -3,25 +3,38 @@ import type { RequestHandler } from "./$types";
|
|||||||
import { putObject } from "$lib/helpers/minio";
|
import { putObject } from "$lib/helpers/minio";
|
||||||
import { generateImage } from "$lib/helpers/stability";
|
import { generateImage } from "$lib/helpers/stability";
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ params }) => {
|
export const POST: RequestHandler = async ({ params, request }) => {
|
||||||
|
|
||||||
const inputName = params.name;
|
const inputName = params.name;
|
||||||
|
if (!inputName) {
|
||||||
|
throw new Error("Missing name");
|
||||||
|
}
|
||||||
|
if (inputName.length > 50) {
|
||||||
|
throw new Error("Name too long");
|
||||||
|
}
|
||||||
|
|
||||||
const prompt = `realistic profile portrait oil painting of a masked ${inputName}, baroque, Charles Vess, masked ball attire, Charles Vess, opulence, mystery, elegance, medium-length blond hair, darker skin`;
|
const { hairType, hairColor, hairLength } = await request.json();
|
||||||
|
console.log(hairType, hairColor, hairLength)
|
||||||
|
|
||||||
|
if (!hairType || !hairColor || !hairLength) {
|
||||||
|
throw new Error("Missing hairType, hairColor or hairLength");
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = `realistic portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, darker skin`;
|
||||||
const negativePrompt = "blurry, multiple persons, picture frame"
|
const negativePrompt = "blurry, multiple persons, picture frame"
|
||||||
|
|
||||||
const a = performance.now()
|
const a = performance.now()
|
||||||
|
// #const image = await openai.image(prompt);
|
||||||
const image = await generateImage(prompt, negativePrompt);
|
const image = await generateImage(prompt, negativePrompt);
|
||||||
const duration = performance.now() - a;
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
console.log({ duration })
|
|
||||||
|
|
||||||
const imageName = `${image.seed}-${inputName.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25)}.png`
|
const imageName = `${Math.random().toString(16).substring(3, 10)}-${inputName.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25)}.png`
|
||||||
|
|
||||||
const res = await putObject(imageName, Buffer.from(image.base64, 'base64'), { "Content-Type": "image/png" });
|
await putObject(imageName, Buffer.from(image.base64, 'base64'), { "Content-Type": "image/png" });
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
...res,
|
duration,
|
||||||
url: `https://s3.app.max-richter.dev/silvester23/${imageName}`
|
url: `https://s3-api.app.max-richter.dev/silvester23/${imageName}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { type RequestHandler } from "@sveltejs/kit";
|
import { json, type RequestHandler } from "@sveltejs/kit";
|
||||||
import { chat } from "$lib/helpers/chatgpt";
|
import { chat } from "$lib/helpers/openai";
|
||||||
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async function ({ params }) {
|
export const GET: RequestHandler = async function ({ params }) {
|
||||||
|
|
||||||
const inputName = params.name
|
const inputName = params.name
|
||||||
|
|
||||||
const prompt = `Generate 10 variants of the name ${inputName}. The names should sound very much like the original but also like noble names from the 1900 century. Examples could be "lady rosalind of whitmore" "lord byron of castlemore" "Lord Max Richter". Only respond with 10 names seperated be newlines`;
|
const prompt = `Generate 10 variants of the name ${inputName}. The names should sound very much like the original but also like noble names from the 1900 century. Examples could be "lady rosalind of whitmore" "lord byron of castlemore" "Lord Max Richter". Choose english, german, french and italian sounding names. Only respond with 10 names seperated be newlines`;
|
||||||
|
|
||||||
const res = await chat(prompt);
|
const res = await chat(prompt, { isList: true, temperature: 1 });
|
||||||
|
|
||||||
return new Response(res);
|
return json(res);
|
||||||
}
|
}
|
||||||
|
35
src/routes/api/invites/+server.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as sheet from '$lib/helpers/sheets'
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import * as pb from "$lib/helpers/pb"
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
|
||||||
|
const res = await sheet.getData();
|
||||||
|
|
||||||
|
return json(res);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ request }) => {
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
console.log(body)
|
||||||
|
try {
|
||||||
|
await sheet.addPerson({ name: body.name, confidence: body.confidence.toString(), noble_name: body.adelsTitel })
|
||||||
|
|
||||||
|
await pb.createPerson({
|
||||||
|
name: body.name,
|
||||||
|
confidence: body.confidence,
|
||||||
|
noble_name: body.adelsTitel,
|
||||||
|
portrait: body.portraitUrl,
|
||||||
|
hair_length: body.portraitHairLength,
|
||||||
|
hair_type: body.portraitHairType,
|
||||||
|
hair_color: body.portraitHairColor,
|
||||||
|
portrait_public: body.portraitPublic,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(body);
|
||||||
|
}
|
7
src/routes/image/+page.svelte
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import ImageFrame from '$lib/components/ImageFrame.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ImageFrame
|
||||||
|
src="https://s3-api.app.max-richter.dev/silvester23/13770f0-earl-maximus-of-richterla.png"
|
||||||
|
/>
|
5
src/routes/questions/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Questions from '$lib/components/Questions.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Questions />
|
14
static/border.svg
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
<svg width="102" height="102" viewBox="0 0 102 102" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M94.0933 97.1984C93.6629 95.4247 95.645 94.1788 97.4394 95.8329C99.4806 97.7146 96.9614 101.603 94.0933 100.921C91.2253 100.238 90.2693 97.8811 90.2693 93.7847C90.2693 90.5076 90.2693 63.8961 90.2693 51.0001" stroke="#CCB951"/>
|
||||||
|
<path d="M97.0566 88.5549C97.0566 86.7852 99.2069 85.5467 100.45 87.1809C102.007 89.2267 99.9654 91.6083 97.9718 91.2373C95.1038 90.7036 94.1478 88.5684 94.1478 85.3658C94.1478 82.8037 94.1478 61.0824 94.1478 51" stroke="#CCB951"/>
|
||||||
|
<path d="M89.7845 87.6389L51 87.6389" stroke="#CCB951"/>
|
||||||
|
<path d="M94.0933 4.80163C93.6629 6.57529 95.645 7.82122 97.4394 6.1671C99.4806 4.2854 96.9614 0.396727 94.0933 1.07946C91.2253 1.76219 90.2693 4.1189 90.2693 8.2153C90.2693 11.4924 90.2693 38.1039 90.2693 50.9999" stroke="#CCB951"/>
|
||||||
|
<path d="M97.0566 13.4451C97.0566 15.2148 99.2069 16.4533 100.45 14.8191C102.007 12.7733 99.9654 10.3917 97.9718 10.7627C95.1038 11.2964 94.1478 13.4316 94.1478 16.6342C94.1478 19.1963 94.1478 40.9176 94.1478 51" stroke="#CCB951"/>
|
||||||
|
<path d="M89.7845 14.3611L51 14.3611" stroke="#CCB951"/>
|
||||||
|
<path d="M7.90666 97.1984C8.33707 95.4247 6.35496 94.1788 4.56062 95.8329C2.5194 97.7146 5.03862 101.603 7.90666 100.921C10.7747 100.238 11.7307 97.8811 11.7307 93.7847C11.7307 90.5076 11.7307 63.8961 11.7307 51.0001" stroke="#CCB951"/>
|
||||||
|
<path d="M4.94341 88.5549C4.94341 86.7852 2.79309 85.5467 1.54977 87.1809C-0.00664347 89.2267 2.03458 91.6083 4.0282 91.2373C6.89623 90.7036 7.85225 88.5684 7.85225 85.3658C7.85225 82.8037 7.85225 61.0824 7.85224 51" stroke="#CCB951"/>
|
||||||
|
<path d="M12.2155 87.6389L51 87.6389" stroke="#CCB951"/>
|
||||||
|
<path d="M7.90666 4.80163C8.33707 6.57529 6.35496 7.82122 4.56062 6.1671C2.5194 4.2854 5.03862 0.396727 7.90666 1.07946C10.7747 1.76219 11.7307 4.1189 11.7307 8.2153C11.7307 11.4924 11.7307 38.1039 11.7307 50.9999" stroke="#CCB951"/>
|
||||||
|
<path d="M4.94341 13.4451C4.94341 15.2148 2.79309 16.4533 1.54977 14.8191C-0.00664347 12.7733 2.03458 10.3917 4.0282 10.7627C6.89623 11.2964 7.85225 13.4316 7.85225 16.6342C7.85225 19.1963 7.85225 40.9176 7.85224 51" stroke="#CCB951"/>
|
||||||
|
<path d="M12.2155 14.3611L51 14.3611" stroke="#CCB951"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/frames/frame_01.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
static/frames/frame_02.png
Normal file
After Width: | Height: | Size: 669 KiB |
BIN
static/frames/frame_03.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
static/frames/frame_04.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
static/frames/frame_05.png
Normal file
After Width: | Height: | Size: 12 MiB |
BIN
static/frames/frame_06.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
static/frames/frame_07.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
static/frames/frame_08.png
Normal file
After Width: | Height: | Size: 453 KiB |