@christianriedl/media 1.0.172 → 1.0.174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianriedl/media",
3
- "version": "1.0.172",
3
+ "version": "1.0.174",
4
4
  "description": "RIC media interfaces",
5
5
 
6
6
  "main": "dist/index.js",
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+ import { IBook } from '@christianriedl/media';
4
+
5
+ const props = defineProps<{ book: IBook, add?: boolean, allbooks?: IBook[] }>();
6
+ const book = props.book;
7
+ const emits = defineEmits<{
8
+ (e: 'selectbook', id: number, func: () => void): void,
9
+ (e: 'deletebook', id: number): void,
10
+ (e: 'savebook', book: IBook): void
11
+ }>();
12
+
13
+ const cls = props.add ? ["bg-climate"] : ["bg-office"];
14
+ const showDelete = ref(!props.add);
15
+ const showSelect = ref(!props.add);
16
+ const showSave = ref(false);
17
+ const selected = ref<IBook | string | undefined>();
18
+
19
+ async function titleChanged(arg: IBook | string) {
20
+ if (typeof arg == 'string') {
21
+ book.id = 0;
22
+ book.title = arg as string;
23
+ }
24
+ else {
25
+ const b = arg as IBook;
26
+ book.id = 0;
27
+ book.title = b.title;
28
+ book.year = b.year;
29
+ }
30
+ showSave.value = true;
31
+ }
32
+
33
+ function onDelete () {
34
+ if (!props.add)
35
+ emits("deletebook", book.id);
36
+ }
37
+ function onSelect() {
38
+ if (!props.add)
39
+ emits("selectbook", book.id, fieldChanged);
40
+ }
41
+ function onSave() {
42
+ emits("savebook", book);
43
+ showSave.value = false;
44
+ }
45
+ function fieldChanged () {
46
+ showSave.value = true;
47
+ }
48
+ function ratingChanged (value: string | number) { // modelValue not working
49
+ book.rating = Number(value);
50
+ showSave.value = true;
51
+ }
52
+ </script>
53
+
54
+ <template>
55
+ <v-row dense align="center" :class="cls" @click="onSelect" >
56
+ <v-col v-if="props.add" cols="6" >
57
+ <v-combobox v-model="selected" :items="props.allbooks" item-value="id" item-title="title" hide-details density="compact" single-line @update:modelValue="titleChanged"></v-combobox>
58
+ </v-col>
59
+ <v-col v-else cols="6" >
60
+ <v-text-field v-model="book.title" hide-details density="compact" @update:modelValue="fieldChanged"></v-text-field>
61
+ </v-col>
62
+ <v-col cols="2" >
63
+ <v-text-field v-model="book.year" hide-details density="compact" type="number" @update:modelValue="fieldChanged"></v-text-field>
64
+ </v-col>
65
+ <v-col cols="2" >
66
+ <v-rating hover clearable :length="5" :size="32" :model-value="book.rating" active-color="primary" @update:modelValue="ratingChanged" />
67
+ </v-col>
68
+ <v-col cols="2">
69
+ <v-btn v-if="showDelete" icon="$delete" @click="onDelete"></v-btn>
70
+ <v-btn v-if="showSave" icon="$save" @click="onSave"></v-btn>
71
+ </v-col>
72
+ </v-row>
73
+ </template>
@@ -0,0 +1,375 @@
1
+ <script setup lang="ts">
2
+ import { inject, ref, reactive, onMounted, onUnmounted, computed, StyleValue } from 'vue';
3
+ import { IAppState, appStateSymbol, getOpenAISymbol, IOpenAIService, ICompleteData } from '@christianriedl/utils';
4
+ import { BooksService, getBooksSymbol, IAuthorShort, IAuthor, IBook } from '@christianriedl/media';
5
+ import BookLine from '../components/BookLine.vue';
6
+
7
+ const appState = inject(appStateSymbol)!;
8
+ const getBooksService = inject(getBooksSymbol)!;
9
+ const booksService = getBooksService() as BooksService;
10
+ const getOpenAI = inject(getOpenAISymbol)!;
11
+ const openAI = getOpenAI();
12
+ const authors = reactive<IAuthorShort[]>([]);
13
+ const selected = ref<IAuthorShort | string | undefined>();
14
+ const author = reactive<IAuthor>({ id: 0, surName: "", givenName: "", yearOfBirth: 0, books: [] });
15
+ const currentBook = ref<IBook>({ id: 0, authorId: 0, title: ""});
16
+ let newBook: IBook;
17
+ const allBooks = ref<IBook[]>([]);
18
+ const lastAllBooksId = ref(0);
19
+ const mustAddAuthor = ref(false);
20
+ const mustUpdateAuthor = ref(false);
21
+ const showAddBookLine = ref(false);
22
+ const showCurrentBook = ref(false);
23
+ let setBookChanged: () => void;
24
+
25
+ const heightStyle = computed<StyleValue>(() => { return { height: appState.bodyHeight.value + "px", overflowY: "auto" } });
26
+ start();
27
+
28
+ async function start() {
29
+ const authorsShort = await booksService.getAuthorsShort();
30
+ if (authorsShort) {
31
+ authors.splice(0, authors.length, ...authorsShort);
32
+ }
33
+ }
34
+ async function addAuthor() {
35
+ if (!checkAuthor(author))
36
+ return;
37
+ const id = await booksService.addAuthor(author);
38
+ if (id > 0) {
39
+ author.id = id;
40
+ mustAddAuthor.value = false;
41
+ }
42
+ else
43
+ alert (`AddAuthor - error ${id}`);
44
+ }
45
+ async function updateAuthor() {
46
+ if (!checkAuthor(author))
47
+ return;
48
+ const rc = await booksService.updateAuthor(author);
49
+ if (rc)
50
+ mustUpdateAuthor.value = false;
51
+ else
52
+ alert ('UpdateAuthor - error');
53
+ }
54
+ async function deleteAuthor() {
55
+ if (!window.confirm(`Willst du ${author.surName} wirklich l�schen ?`))
56
+ return;
57
+ const rc = await booksService.deleteAuthor(author.id);
58
+ if (rc) {
59
+ mustUpdateAuthor.value = false;
60
+ initAuthor ("");
61
+ }
62
+ else
63
+ alert (`DeleteAuthor ${author.id} - error`);
64
+ }
65
+ function initAuthor(surName: string) {
66
+ author.id = 0;
67
+ author.surName = surName;
68
+ author.givenName = "";
69
+ author.yearOfBirth = 0;
70
+ author.yearOfDeath = undefined;
71
+ author.country = undefined;
72
+ author.books = [];
73
+ }
74
+ async function getCurrentAuthor (id: number, withBooks: boolean) : Promise<boolean> {
75
+ const authors = await booksService.getAuthors(undefined, undefined, withBooks, id);
76
+ if (authors && authors.length > 0) {
77
+ const a = authors[0];
78
+ author.id = a.id;
79
+ author.surName = a.surName;
80
+ author.givenName = a.givenName;
81
+ author.yearOfBirth = a.yearOfBirth;
82
+ author.yearOfDeath = a.yearOfDeath;
83
+ author.country = a.country;
84
+ author.books = a.books ? a.books : [];
85
+ return true;
86
+ }
87
+ else {
88
+ initAuthor ("");
89
+ return false;
90
+ }
91
+ }
92
+ async function authorNameChanged(arg: IAuthorShort | string) {
93
+ showCurrentBook.value = false;
94
+ currentBook.value = { id: 0, authorId: 0, title: ""};
95
+ mustAddAuthor.value = typeof arg == 'string';
96
+ if (!mustAddAuthor.value) {
97
+ const authorShort = arg as IAuthorShort;
98
+ const rc = await getCurrentAuthor (authorShort.id, true);
99
+ }
100
+ else {
101
+ initAuthor (arg as string);
102
+ mustUpdateAuthor.value = false;
103
+ }
104
+ }
105
+ function fieldChanged() {
106
+ if (!mustAddAuthor.value) {
107
+ mustUpdateAuthor.value = true;
108
+ }
109
+ }
110
+ async function givenNameFocused(focus: boolean) {
111
+ if (!focus && mustAddAuthor.value) {
112
+ await getAuthorDataFromAI(author.surName, author.givenName);
113
+ }
114
+ }
115
+ async function addNewBook() {
116
+ if (lastAllBooksId.value != author.id) {
117
+ allBooks.value = await getAuthorBooksFromAI (author.surName, author.givenName, author.id);
118
+ lastAllBooksId.value = author.id;
119
+ }
120
+ newBook = reactive<IBook>({ id: 0, authorId: author.id, title: "", rating: 0 });
121
+ showAddBookLine.value = true;
122
+ }
123
+ function cancelAddNewBook() {
124
+ showAddBookLine.value = false;
125
+ }
126
+ async function selectBook(id: number, func: () => void) {
127
+ if (currentBook.value.id == id) {
128
+ return;
129
+ }
130
+ setBookChanged = func;
131
+ if (author.books) {
132
+ currentBook.value = author.books.find (x => x.id == id) as IBook;
133
+ if (currentBook.value && !currentBook.value.description && !currentBook.value.comment) {
134
+ const books = await booksService.getBooks (author.id, currentBook.value.id);
135
+ if (books && books.length > 0) {
136
+ currentBook.value.description = books[0].description;
137
+ currentBook.value.comment = books[0].comment;
138
+ }
139
+ showCurrentBook.value = true;
140
+ if (!currentBook.value.description) {
141
+ currentBook.value.description ="WAITING..."
142
+ currentBook.value.description = await getBookDescriptionFromAI (author.surName, author.givenName, currentBook.value.title);
143
+ if (currentBook.value.description && setBookChanged)
144
+ setBookChanged();
145
+ }
146
+
147
+ }
148
+ }
149
+ }
150
+ function descriptionChanged () {
151
+ if (setBookChanged)
152
+ setBookChanged();
153
+
154
+ }
155
+ async function deleteBook(id: number) {
156
+ if (!window.confirm(`Willst du das Buch wirklich l�schen ?`))
157
+ return;
158
+ setBookChanged = () => {};
159
+ const rc = await booksService.deleteBook (id);
160
+ if (!rc)
161
+ alert (`DeleteBook ${id}- error`);
162
+ if (rc && author.books) {
163
+ const idx = author.books.findIndex ((x) => x.id == id);
164
+ if (idx >= 0)
165
+ author.books.splice (idx, 1);
166
+ }
167
+ }
168
+ async function saveBook(book: IBook) {
169
+ if (!checkBook(book))
170
+ return;
171
+ if (showAddBookLine.value) {
172
+ const id = await booksService.addBook (book);
173
+ if (id < 0)
174
+ alert (`AddBook - error ${id}`);
175
+ if (id > 0 && author.books) {
176
+ book.id = id;
177
+ author.books.splice (0, 0, book);
178
+ showAddBookLine.value = false;
179
+ }
180
+ }
181
+ else {
182
+ const rc = await booksService.updateBook (book);
183
+ if (!rc)
184
+ alert ('UpdateBook - error');
185
+ }
186
+ }
187
+ function checkAuthor (author: IAuthor): boolean {
188
+ if (!author.surName || !author.givenName) {
189
+ alert("Vor oder Nachname fehlt !");
190
+ return false;
191
+ }
192
+ if (author.surName.length >= 64 || author.givenName.length >= 64) {
193
+ alert("Vor oder Nachname zu lang (>64) !");
194
+ return false;
195
+ }
196
+ if (author.country && author.country.length >= 64) {
197
+ alert("Geburtsland zu lang (>64) !");
198
+ return false;
199
+ }
200
+ return true;
201
+ }
202
+ function checkBook (book: IBook): boolean {
203
+ if (!book.title) {
204
+ alert("Titel fehlt !");
205
+ return false;
206
+ }
207
+ if (book.title.length >= 256) {
208
+ alert("Titel zu lang (>256) !");
209
+ return false;
210
+ }
211
+ if (!book.authorId) {
212
+ alert("Autor fehlt !");
213
+ return false;
214
+ }
215
+ if (book.description && book.description.length >= 4000) {
216
+ alert("Beschreibung zu lang (>4000) !");
217
+ return false;
218
+ }
219
+ if (book.comment && book.comment.length >= 1024) {
220
+ alert("Kommentar zu lang (>1024) !");
221
+ return false;
222
+ }
223
+ return true;
224
+ }
225
+ async function getAuthorDataFromAI(surName: string, givenName: string) {
226
+ const schema =
227
+ {
228
+ "name": "author_data",
229
+ "schema": {
230
+ "$schema": "http://json-schema.oeg/draft-07/schema#",
231
+ "additionalProperties": false,
232
+ "properties": {
233
+ "geburtstag": {
234
+ "type": "string"
235
+ },
236
+ "sterbetag": {
237
+ "type": ["string", "null"]
238
+ },
239
+ "geburtsland": {
240
+ "type": ["string", "null"]
241
+ },
242
+ },
243
+ "required": ["geburtstag", "sterbetag", "geburtsland"],
244
+ "type": "object"
245
+ },
246
+ "strict": true
247
+ }
248
+ const prompt = `Geburtstag, Sterbetag, Geburtsland von ${givenName} ${surName} ?`;
249
+ const result = await openAI.completeJson(prompt, schema) as any;
250
+ if (result) {
251
+ author.yearOfBirth = getYear(result.geburtstag);
252
+ author.yearOfDeath = result.sterbetag ? getYear(result.sterbetag) : undefined;
253
+ author.country = result.geburtsland;
254
+ }
255
+ else {
256
+ alert("No AI result for : " + prompt);
257
+ }
258
+ }
259
+ function getYear(dt: string): number {
260
+ if (dt) {
261
+ const arr = dt.split(' ');
262
+ if (arr.length == 3)
263
+ return Number(arr[2]);
264
+ }
265
+ return 0;
266
+ }
267
+ async function getAuthorBooksFromAI(surName: string, givenName: string, authorId: number) : Promise <IBook[]> {
268
+ const schema =
269
+ {
270
+ "name": "books",
271
+ "schema": {
272
+ "$schema": "http://json-schema.oeg/draft-07/schema#",
273
+ "additionalProperties": false,
274
+ "properties": {
275
+ "werke": {
276
+ "items": {
277
+ "additionalProperties": false,
278
+ "properties": {
279
+ "werk": {
280
+ "type": "string"
281
+ },
282
+ "erscheinungsjahr": {
283
+ "type": "number"
284
+ }
285
+ },
286
+ "required": ["werk", "erscheinungsjahr"],
287
+ "type": "object"
288
+ },
289
+ "type": "array",
290
+ }
291
+ },
292
+ "required": ["werke"],
293
+ "type": "object"
294
+ },
295
+ "strict": true
296
+ }
297
+ const prompt = `Alle Werke mit Erscheinungsjahr von ${givenName} ${surName}`;
298
+ const result = await openAI.completeJson(prompt, schema) as any;
299
+ if (result && result.werke) {
300
+ const books: IBook[] = [];
301
+ for (let i = 0; i < result.werke.length; i++) {
302
+ books.push ({ id: i, authorId: authorId, title: result.werke[i].werk, year : result.werke[i].erscheinungsjahr});
303
+ }
304
+ return books;
305
+ }
306
+ else {
307
+ alert("No AI result for : " + prompt);
308
+ return [];
309
+ }
310
+ }
311
+ async function getBookDescriptionFromAI(surName: string, givenName: string, title: string, prefix?: string) : Promise <string> {
312
+ const prompt = `Eine ${prefix} Inhaltsangabe von dem Buch "${title}" von ${givenName} ${surName}`;
313
+ const result = await openAI.complete(prompt);
314
+ if (result && result.choices) {
315
+ return result.choices[0];
316
+ }
317
+ else {
318
+ alert("No AI result for : " + prompt);
319
+ return "";
320
+ }
321
+ }
322
+ </script>
323
+
324
+ <template>
325
+ <v-container fluid class="bg-office" :style="heightStyle">
326
+ <v-defaults-provider :defaults="{'VBtn':{'size':'large','variant':'flat','class':'bg-office'}}">
327
+ <v-row dense align="center">
328
+ <v-col cols="2">Name</v-col>
329
+ <v-col cols="2">Vorname</v-col>
330
+ <v-col cols="2">Von</v-col>
331
+ <v-col cols="2">Bis</v-col>
332
+ <v-col cols="2">Land</v-col>
333
+ </v-row>
334
+ <v-row dense align="center">
335
+ <v-col cols="2">
336
+ <v-combobox v-model="selected" :items="authors" item-value="id" item-title="sN" hide-details density="compact" single-line @update:modelValue="authorNameChanged"></v-combobox>
337
+ </v-col>
338
+ <v-col cols="2">
339
+ <v-text-field v-model="author.givenName" hide-details density="compact" @update:modelValue="fieldChanged" @update:focused="givenNameFocused"></v-text-field>
340
+ </v-col>
341
+ <v-col cols="2">
342
+ <v-text-field v-model="author.yearOfBirth" hide-details density="compact" type="number" @update:modelValue="fieldChanged"></v-text-field>
343
+ </v-col>
344
+ <v-col cols="2">
345
+ <v-text-field v-model="author.yearOfDeath" hide-details density="compact" type="number" @update:modelValue="fieldChanged"></v-text-field>
346
+ </v-col>
347
+ <v-col cols="2">
348
+ <v-text-field v-model="author.country" hide-details density="compact" @update:modelValue="fieldChanged"></v-text-field>
349
+ </v-col>
350
+ <v-col cols="2">
351
+ <v-btn v-if="mustAddAuthor" @click="addAuthor" append-icon="$plus">AUTHOR</v-btn>
352
+ <v-btn v-if="mustUpdateAuthor" @click="updateAuthor" icon="$save"></v-btn>
353
+ <v-btn v-if="author.id > 0" @click="deleteAuthor" icon="$delete"></v-btn>
354
+ </v-col>
355
+ </v-row>
356
+ <v-divider></v-divider>
357
+ <v-row dense align="center">
358
+ <v-col cols="6">TITEL</v-col>
359
+ <v-col cols="2">JAHR</v-col>
360
+ <v-col cols="2">RATING</v-col>
361
+ <v-col v-if="author.id" cols="2">
362
+ <v-btn v-if="!showAddBookLine" @click="addNewBook" append-icon="$plus">BOOK</v-btn>
363
+ <v-btn v-if="showAddBookLine" @click="cancelAddNewBook" icon="$cancel"></v-btn>
364
+ </v-col>
365
+ </v-row>
366
+ <book-line v-if="showAddBookLine" :book="newBook" :add="true" :allbooks="allBooks" @selectbook="selectBook" @deletebook="deleteBook" @savebook="saveBook"></book-line>
367
+ <book-line v-for="book in author.books" :key="book.id" :book="book" @selectbook="selectBook" @deletebook="deleteBook" @savebook="saveBook"></book-line>
368
+ <v-textarea v-if="showCurrentBook" class="pt-2"clearable auto-grow variant="outlined" label="Beschreibung" v-model="currentBook.description" @update:modelValue="descriptionChanged">
369
+ </v-textarea>
370
+ <v-textarea v-if="showCurrentBook" clearable auto-grow variant="outlined" label="Kommentar" v-model="currentBook.comment" @update:modelValue="descriptionChanged">
371
+ </v-textarea>
372
+ </v-defaults-provider>
373
+ </v-container>
374
+ </template>
375
+