@christianriedl/media 1.0.172 → 1.0.173

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.173",
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,315 @@
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
+ const id = await booksService.addAuthor(author);
36
+ if (id > 0) {
37
+ author.id = id;
38
+ mustAddAuthor.value = false;
39
+ }
40
+ }
41
+ async function updateAuthor() {
42
+ const rc = await booksService.updateAuthor(author);
43
+ if (rc)
44
+ mustUpdateAuthor.value = false;
45
+ }
46
+ async function deleteAuthor() {
47
+ const rc = await booksService.deleteAuthor(author.id);
48
+ if (rc) {
49
+ mustUpdateAuthor.value = false;
50
+ initAuthor ("");
51
+ }
52
+ }
53
+ function initAuthor(surName: string) {
54
+ author.id = 0;
55
+ author.surName = surName;
56
+ author.givenName = "";
57
+ author.yearOfBirth = 0;
58
+ author.yearOfDeath = undefined;
59
+ author.country = undefined;
60
+ author.books = [];
61
+ }
62
+ async function getAuthor (id: number, withBooks: boolean) : Promise<boolean> {
63
+ const authors = await booksService.getAuthors(undefined, undefined, withBooks, id);
64
+ if (authors && authors.length > 0) {
65
+ const a = authors[0];
66
+ author.id = a.id;
67
+ author.surName = a.surName;
68
+ author.givenName = a.givenName;
69
+ author.yearOfBirth = a.yearOfBirth;
70
+ author.yearOfDeath = a.yearOfDeath;
71
+ author.country = a.country;
72
+ author.books = a.books ? a.books : [];
73
+ return true;
74
+ }
75
+ else {
76
+ initAuthor ("");
77
+ return false;
78
+ }
79
+ }
80
+ async function nameChanged(arg: IAuthorShort | string) {
81
+ showCurrentBook.value = false;
82
+ currentBook.value = { id: 0, authorId: 0, title: ""};
83
+ mustAddAuthor.value = typeof arg == 'string';
84
+ if (!mustAddAuthor.value) {
85
+ const authorShort = arg as IAuthorShort;
86
+ const rc = await getAuthor (authorShort.id, true);
87
+ }
88
+ else {
89
+ initAuthor (arg as string);
90
+ mustUpdateAuthor.value = false;
91
+ }
92
+ }
93
+ function fieldChanged() {
94
+ if (!mustAddAuthor.value) {
95
+ mustUpdateAuthor.value = true;
96
+ }
97
+ }
98
+ async function givenNameFocused(focus: boolean) {
99
+ if (!focus && mustAddAuthor.value) {
100
+ await getAuthorDataFromAI(author.surName, author.givenName);
101
+ }
102
+ }
103
+ async function addBook() {
104
+ if (lastAllBooksId.value != author.id) {
105
+ allBooks.value = await getAuthorBooksFromAI (author.surName, author.givenName, author.id);
106
+ lastAllBooksId.value = author.id;
107
+ }
108
+ newBook = reactive<IBook>({ id: 0, authorId: author.id, title: "", rating: 0 });
109
+ showAddBookLine.value = true;
110
+ }
111
+ function cancelAddBook() {
112
+ showAddBookLine.value = false;
113
+ }
114
+ async function selectBook(id: number, func: () => void) {
115
+ if (currentBook.value.id == id) {
116
+ return;
117
+ }
118
+ setBookChanged = func;
119
+ if (author.books) {
120
+ currentBook.value = author.books.find (x => x.id == id) as IBook;
121
+ if (currentBook.value && !currentBook.value.description && !currentBook.value.comment) {
122
+ const books = await booksService.getBooks (author.id, currentBook.value.id);
123
+ if (books && books.length > 0) {
124
+ currentBook.value.description = books[0].description;
125
+ currentBook.value.comment = books[0].comment;
126
+ }
127
+ showCurrentBook.value = true;
128
+ if (!currentBook.value.description) {
129
+ currentBook.value.description ="WAITING..."
130
+ currentBook.value.description = await getBookDescriptionFromAI (author.surName, author.givenName, currentBook.value.title);
131
+ if (currentBook.value.description && setBookChanged)
132
+ setBookChanged();
133
+ }
134
+
135
+ }
136
+ }
137
+ }
138
+ function descriptionChanged () {
139
+ if (setBookChanged)
140
+ setBookChanged();
141
+
142
+ }
143
+ async function deleteBook(id: number) {
144
+ setBookChanged = () => {};
145
+ const rc = await booksService.deleteBook (id);
146
+ if (rc && author.books) {
147
+ const idx = author.books.findIndex ((x) => x.id == id);
148
+ if (idx >= 0)
149
+ author.books.splice (idx, 1);
150
+ }
151
+ }
152
+ async function saveBook(book: IBook) {
153
+ if (showAddBookLine.value) {
154
+ const id = await booksService.addBook (book);
155
+ if (id > 0 && author.books) {
156
+ book.id = id;
157
+ author.books.splice (0, 0, book);
158
+ showAddBookLine.value = false;
159
+ }
160
+ }
161
+ else {
162
+ const rc = await booksService.updateBook (book);
163
+ }
164
+ }
165
+ async function getAuthorDataFromAI(surName: string, givenName: string) {
166
+ const schema =
167
+ {
168
+ "name": "author_data",
169
+ "schema": {
170
+ "$schema": "http://json-schema.oeg/draft-07/schema#",
171
+ "additionalProperties": false,
172
+ "properties": {
173
+ "geburtstag": {
174
+ "type": "string"
175
+ },
176
+ "sterbetag": {
177
+ "type": ["string", "null"]
178
+ },
179
+ "geburtsland": {
180
+ "type": ["string", "null"]
181
+ },
182
+ },
183
+ "required": ["geburtstag", "sterbetag", "geburtsland"],
184
+ "type": "object"
185
+ },
186
+ "strict": true
187
+ }
188
+ const prompt = `Geburtstag, Sterbetag, Geburtsland von ${givenName} ${surName} ?`;
189
+ const result = await openAI.completeJson(prompt, schema) as any;
190
+ if (result) {
191
+ author.yearOfBirth = getYear(result.geburtstag);
192
+ author.yearOfDeath = result.sterbetag ? getYear(result.sterbetag) : undefined;
193
+ author.country = result.geburtsland;
194
+ }
195
+ else {
196
+ alert("No AI result for : " + prompt);
197
+ }
198
+ }
199
+ function getYear(dt: string): number {
200
+ if (dt) {
201
+ const arr = dt.split(' ');
202
+ if (arr.length == 3)
203
+ return Number(arr[2]);
204
+ }
205
+ return 0;
206
+ }
207
+ async function getAuthorBooksFromAI(surName: string, givenName: string, authorId: number) : Promise <IBook[]> {
208
+ const schema =
209
+ {
210
+ "name": "books",
211
+ "schema": {
212
+ "$schema": "http://json-schema.oeg/draft-07/schema#",
213
+ "additionalProperties": false,
214
+ "properties": {
215
+ "werke": {
216
+ "items": {
217
+ "additionalProperties": false,
218
+ "properties": {
219
+ "werk": {
220
+ "type": "string"
221
+ },
222
+ "erscheinungsjahr": {
223
+ "type": "number"
224
+ }
225
+ },
226
+ "required": ["werk", "erscheinungsjahr"],
227
+ "type": "object"
228
+ },
229
+ "type": "array",
230
+ }
231
+ },
232
+ "required": ["werke"],
233
+ "type": "object"
234
+ },
235
+ "strict": true
236
+ }
237
+ const prompt = `Alle Werke mit Erscheinungsjahr von ${givenName} ${surName}`;
238
+ const result = await openAI.completeJson(prompt, schema) as any;
239
+ if (result && result.werke) {
240
+ const books: IBook[] = [];
241
+ for (let i = 0; i < result.werke.length; i++) {
242
+ books.push ({ id: i, authorId: authorId, title: result.werke[i].werk, year : result.werke[i].erscheinungsjahr});
243
+ }
244
+ return books;
245
+ }
246
+ else {
247
+ alert("No AI result for : " + prompt);
248
+ return [];
249
+ }
250
+ }
251
+ async function getBookDescriptionFromAI(surName: string, givenName: string, title: string, prefix?: string) : Promise <string> {
252
+ const prompt = `Eine ${prefix} Inhaltsangabe von dem Buch "${title}" von ${givenName} ${surName}`;
253
+ const result = await openAI.complete(prompt);
254
+ if (result && result.choices) {
255
+ return result.choices[0];
256
+ }
257
+ else {
258
+ alert("No AI result for : " + prompt);
259
+ return "";
260
+ }
261
+ }
262
+ </script>
263
+
264
+ <template>
265
+ <v-container fluid class="bg-office" :style="heightStyle">
266
+ <v-defaults-provider :defaults="{'VBtn':{'size':'large','variant':'flat','class':'bg-office'}}">
267
+ <v-row dense align="center">
268
+ <v-col cols="2">Name</v-col>
269
+ <v-col cols="2">Vorname</v-col>
270
+ <v-col cols="2">Von</v-col>
271
+ <v-col cols="2">Bis</v-col>
272
+ <v-col cols="2">Land</v-col>
273
+ </v-row>
274
+ <v-row dense align="center">
275
+ <v-col cols="2">
276
+ <v-combobox v-model="selected" :items="authors" item-value="id" item-title="sN" hide-details density="compact" single-line @update:modelValue="nameChanged"></v-combobox>
277
+ </v-col>
278
+ <v-col cols="2">
279
+ <v-text-field v-model="author.givenName" hide-details density="compact" @update:modelValue="fieldChanged" @update:focused="givenNameFocused"></v-text-field>
280
+ </v-col>
281
+ <v-col cols="2">
282
+ <v-text-field v-model="author.yearOfBirth" hide-details density="compact" type="number" @update:modelValue="fieldChanged"></v-text-field>
283
+ </v-col>
284
+ <v-col cols="2">
285
+ <v-text-field v-model="author.yearOfDeath" hide-details density="compact" type="number" @update:modelValue="fieldChanged"></v-text-field>
286
+ </v-col>
287
+ <v-col cols="2">
288
+ <v-text-field v-model="author.country" hide-details density="compact" @update:modelValue="fieldChanged"></v-text-field>
289
+ </v-col>
290
+ <v-col cols="2">
291
+ <v-btn v-if="mustAddAuthor" @click="addAuthor" append-icon="$plus">AUTHOR</v-btn>
292
+ <v-btn v-if="mustUpdateAuthor" @click="updateAuthor" icon="$save"></v-btn>
293
+ <v-btn v-if="author.id > 0" @click="deleteAuthor" icon="$delete"></v-btn>
294
+ </v-col>
295
+ </v-row>
296
+ <v-divider></v-divider>
297
+ <v-row dense align="center">
298
+ <v-col cols="6">TITEL</v-col>
299
+ <v-col cols="2">JAHR</v-col>
300
+ <v-col cols="2">RATING</v-col>
301
+ <v-col v-if="author.id" cols="2">
302
+ <v-btn v-if="!showAddBookLine" @click="addBook" append-icon="$plus">BOOK</v-btn>
303
+ <v-btn v-if="showAddBookLine" @click="cancelAddBook" icon="$cancel"></v-btn>
304
+ </v-col>
305
+ </v-row>
306
+ <book-line v-if="showAddBookLine" :book="newBook" :add="true" :allbooks="allBooks" @selectbook="selectBook" @deletebook="deleteBook" @savebook="saveBook"></book-line>
307
+ <book-line v-for="book in author.books" :key="book.id" :book="book" @selectbook="selectBook" @deletebook="deleteBook" @savebook="saveBook"></book-line>
308
+ <v-textarea v-if="showCurrentBook" clearable auto-grow variant="outlined" label="Description" v-model="currentBook.description" @update:modelValue="descriptionChanged">
309
+ </v-textarea>
310
+ <v-textarea v-if="showCurrentBook" clearable auto-grow variant="outlined" label="Comment" v-model="currentBook.comment" @update:modelValue="descriptionChanged">
311
+ </v-textarea>
312
+ </v-defaults-provider>
313
+ </v-container>
314
+ </template>
315
+