@christianriedl/utils 1.0.140 → 1.0.142

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/dist/iOpenAI.d.ts CHANGED
@@ -81,9 +81,13 @@ export interface IComparisonFilter {
81
81
  value: ItemType;
82
82
  }
83
83
  export interface ICompoundFilter {
84
- filters: IComparisonFilter[] | unknown;
84
+ filters: IComparisonFilter[];
85
85
  type: 'and' | 'or';
86
86
  }
87
+ export interface IOpenAIFilter {
88
+ fileFilter: ICompoundFilter;
89
+ vectorStoreFilter: Dictionary<string>;
90
+ }
87
91
  export interface IOpenAIOptions {
88
92
  model: string;
89
93
  instructions?: string | null;
@@ -98,8 +102,8 @@ export interface IOpenAIService {
98
102
  log: ILogger;
99
103
  options: IOpenAIOptions;
100
104
  modelNames: string[];
101
- initialize(): Promise<boolean>;
102
- response(prompt: string | string[], tools?: string[], imageUrl?: string, json?: ISchema, fileSearchFilter?: ICompoundFilter | IComparisonFilter): Promise<IResponseResult>;
105
+ initializeModelNames(): Promise<boolean>;
106
+ response(prompt: string | string[], tools?: string[], imageUrl?: string, json?: ISchema, fileSearchFilter?: IOpenAIFilter): Promise<IResponseResult>;
103
107
  clearContext(): void;
104
108
  getModels(): Promise<string[]>;
105
109
  }
@@ -139,6 +143,7 @@ export interface IOpenAIServiceWithVectorStore extends IOpenAIServiceWithTools {
139
143
  vsMetadata: IDataItem[];
140
144
  fileAttributes: IDataItem[];
141
145
  initializeVectorStore(): Promise<boolean>;
146
+ initializeVectorStoreFiles(): Promise<boolean>;
142
147
  getVectorStore(vectorStoreId: string): IVectorStore | undefined;
143
148
  addVectorStore(vectorStoreName: string, metadata?: Dictionary<string>): Promise<boolean>;
144
149
  addFile(fileUrl: string | File, vectorStoreName: string, attributes?: Dictionary<ItemType>, wait?: boolean): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianriedl/utils",
3
- "version": "1.0.140",
3
+ "version": "1.0.142",
4
4
  "description": "Interfaces, local storage, service worker, configuration, application state",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,10 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { inject, ref, toRef, computed, onUnmounted } from 'vue';
3
3
  import { appStateSymbol, appConfigSymbol, ESize, EDevice } from '@christianriedl/utils';
4
- import { getOpenAISymbol, IOpenAIService, IResponseResult } from '@christianriedl/utils';
4
+ import { getOpenAISymbol, IOpenAIServiceWithVectorStore, IResponseResult, IOpenAIFilter } from '@christianriedl/utils';
5
5
  import Microphone from '@christianriedl/utils/src/components/Microphone.vue';
6
6
  import Camera from '@christianriedl/utils/src/components/Camera.vue';
7
7
  import OpenAIConfig from '@christianriedl/utils/src/components/OpenAIConfig.vue';
8
+ import OpenAIFilter from '@christianriedl/utils/src/components/OpenAIFilter.vue';
8
9
 
9
10
  const props = defineProps<{ prompt: string, onlymicrophone?: boolean, tools?: string[] }>();
10
11
  const emits = defineEmits<{
@@ -13,7 +14,7 @@
13
14
  }>();
14
15
 
15
16
  const getOpenAI = inject(getOpenAISymbol)!;
16
- const openAI = getOpenAI();
17
+ const openAI = getOpenAI() as IOpenAIServiceWithVectorStore;
17
18
  const appState = inject(appStateSymbol)!;
18
19
  const isMobile = appState.isMobile && (appState.device != EDevice.iPad);
19
20
  const appConfig = inject(appConfigSymbol)!;
@@ -23,11 +24,13 @@
23
24
  const isCamera = ref(false);
24
25
  const isUpload = ref(false);
25
26
  const showConfig = ref(false);
27
+ const showFilter = ref(false);
26
28
  const files = ref<File[]>([]);
27
29
  const imageUrl = ref("");
28
30
  const camera = ref<InstanceType<typeof Camera>>();
29
31
  const htmlText = ref('');
30
32
  const tools = ref(props.tools);
33
+ const filter = ref<IOpenAIFilter | undefined>(undefined);
31
34
 
32
35
  onUnmounted(() => {
33
36
  openAI.clearContext();
@@ -52,10 +55,10 @@
52
55
  }
53
56
  else {
54
57
  if (tools.value !== undefined) {
55
- answer = await openAI.response(question.value, tools.value);
58
+ answer = await openAI.response(question.value, tools.value, undefined, undefined, filter.value);
56
59
  }
57
60
  else {
58
- answer = await openAI.response(question.value);
61
+ answer = await openAI.response(question.value, undefined, undefined, undefined, filter.value);
59
62
  }
60
63
  }
61
64
  if (answer && answer.text) {
@@ -150,6 +153,14 @@
150
153
  return '';
151
154
  }
152
155
  }
156
+ function setFilter(newFilter: IOpenAIFilter) {
157
+ filter.value = newFilter;
158
+ showFilter.value = false;
159
+ }
160
+ async function showConfigDialog() {
161
+ if (await openAI.initializeModelNames())
162
+ showConfig.value = true;
163
+ }
153
164
  </script>
154
165
 
155
166
  <template>
@@ -157,7 +168,7 @@
157
168
  <v-container v-else fluid class="bg-office">
158
169
  <v-row dense align="center" class="font-weight-bold">
159
170
  <v-col :cols="qcols">
160
- <v-text-field name="subject" type="text" v-model="question" density="compact" hide-details></v-text-field>
171
+ <v-text-field name="subject" type="text" v-model="question" density="compact" hide-details append-inner-icon="$search" @click:appendInner="showFilter=true"></v-text-field>
161
172
  </v-col>
162
173
  <v-col :cols="12 - qcols">
163
174
  <v-btn class="aibutton" @click="onComplete">AI</v-btn>
@@ -168,7 +179,7 @@
168
179
  <v-btn variant="tonal" class="bg-office aibutton" @click="isUpload=true;replyLines=[]">
169
180
  <v-icon size="large" icon="$image" color="primary"></v-icon>
170
181
  </v-btn>
171
- <v-btn variant="tonal" class="bg-office aibutton" @click="showConfig=true">
182
+ <v-btn variant="tonal" class="bg-office aibutton" @click="showConfigDialog">
172
183
  <v-icon size="large" icon="$settings" color="primary"></v-icon>
173
184
  </v-btn>
174
185
  </v-col>
@@ -192,7 +203,7 @@
192
203
  <img width="100%" :src="imageUrl">
193
204
  </v-col>
194
205
  </v-row>
195
- <v-row v-if="htmlText" dense >
206
+ <v-row v-if="htmlText" dense>
196
207
  <v-col cols="12">
197
208
  <div v-html="htmlText"></div>
198
209
  </v-col>
@@ -203,6 +214,9 @@
203
214
  <v-dialog v-model="showConfig" :fullscreen="isMobile">
204
215
  <OpenAIConfig :tools="tools" :ismobile="isMobile" @use="onUse" @back="showConfig=false"></OpenAIConfig>
205
216
  </v-dialog>
217
+ <v-dialog v-model="showFilter" :fullscreen="isMobile">
218
+ <OpenAIFilter :vsmetadata="openAI.vsMetadata" :fileattributes="openAI.fileAttributes" :filter="filter" @back="setFilter"></OpenAIFilter>
219
+ </v-dialog>
206
220
  </v-container>
207
221
  </template>
208
222
 
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { inject, ref, reactive } from 'vue';
3
- import { appConfigSymbol, AppConfig, Dictionary } from '@christianriedl/utils';
3
+ import { appConfigSymbol, AppConfig, Dictionary, IDataItem, ItemType, IVectorFile } from '@christianriedl/utils';
4
4
  import { getOpenAISymbol, IOpenAIServiceWithVectorStore, IOpenAIAppConfig, IOpenAIOptions, ITool, IMcpTool, IFileSearchTool } from '@christianriedl/utils';
5
5
  import OpenAIMetaData from '@christianriedl/utils/src/components/OpenAIMetaData.vue';
6
6
 
@@ -32,8 +32,8 @@
32
32
  const selectedVectorStoreFile = ref('');
33
33
  const showFunctions = ref(false);
34
34
  const showMetaData = ref(false);
35
- const editVectorStore = ref(false);
36
- const metadata = ref<Dictionary<ItemType>>({});
35
+ const editVectorStoreMetaData = ref(false);
36
+ const metadata = ref<IDataItem[]>([]);
37
37
  const toolName = ref('');
38
38
  const toolFunctions = reactive<IFunction[]>([]);
39
39
  let fileHandle: FileSystemFileHandle | null = null;
@@ -147,12 +147,17 @@
147
147
  fileSearch[name] = [];
148
148
  }
149
149
  }
150
- function vectorStoreSelected() {
151
- selectedVectorStoreFile.value = '';
150
+ async function vectorStoreSelected() {
151
+
152
+ selectedVectorStoreFile.value = 'Get vectorestore files ...';
153
+ if (!await openAI.initializeVectorStoreFiles())
154
+ return;
152
155
  const vs = openAI.vectorStore![selectedVectorStoreName.value];
153
156
  vectorStoreFiles.value.splice(0, vectorStoreFiles.value.length);
154
157
  if (vs) {
155
- vectorStoreFiles.value.splice(0, 0, ...Object.values(vs.files) as string[]);
158
+ for (const key in vs.files) {
159
+ vectorStoreFiles.value.splice(0, 0, vs.files[key].filename);
160
+ }
156
161
  if (vectorStoreFiles.value.length > 0) {
157
162
  selectedVectorStoreFile.value = vectorStoreFiles.value[0];
158
163
  }
@@ -163,7 +168,12 @@
163
168
  alert('Vector Store already exists, add a new name !');
164
169
  return;
165
170
  }
166
- if (!openAI.addVectorStore(selectedVectorStoreName.value)) {
171
+ const metadata: Dictionary<string> = {};
172
+ for (const md of openAI.vsMetadata) {
173
+ metadata[md.name] = md.default.toString();
174
+ }
175
+ showMetaData.value = true;
176
+ if (!openAI.addVectorStore(selectedVectorStoreName.value, metadata)) {
167
177
  alert('Vector Store not added, error ! ');
168
178
  return;
169
179
  }
@@ -179,8 +189,14 @@
179
189
  async function editVectorStore() {
180
190
  const vs = openAI.vectorStore![selectedVectorStoreName.value];
181
191
  if (vs) {
182
- editVectorStore.value = true;
183
- metadata.value = vs.metadata ? vs.metadata : openAI.vsMetadata;
192
+ editVectorStoreMetaData.value = true;
193
+ metadata.value = [];
194
+ for (const md of openAI.vsMetadata) {
195
+ const mdCopy = Object.assign({}, md);
196
+ if (vs.metadata && vs.metadata[md.name])
197
+ mdCopy.value = vs.metadata[md.name];
198
+ metadata.value.push(mdCopy);
199
+ }
184
200
  showMetaData.value = true;
185
201
  }
186
202
  }
@@ -189,15 +205,27 @@
189
205
  alert('File already exists, add a new file path or url !');
190
206
  return;
191
207
  }
208
+ const attributes: Dictionary<ItemType> = {};
209
+ for (const att of openAI.fileAttributes) {
210
+ attributes[att.name] = att.default;
211
+ }
212
+ const vs = openAI.vectorStore![selectedVectorStoreName.value];
213
+ if (vs && vs.metadata) {
214
+ for (const key in vs.metadata) {
215
+ attributes[key] = vs.metadata[key];
216
+ }
217
+ }
192
218
  if (fileHandle) {
193
219
  const file = await fileHandle.getFile();
194
220
  fileHandle = null;
195
- if (!await openAI.addFile(file, selectedVectorStoreName.value)) {
221
+ if (!await openAI.addFile(file, selectedVectorStoreName.value, attributes)) {
196
222
  alert('File not added, error ! ');
197
223
  }
198
224
  }
199
- if (!await openAI.addFile(selectedVectorStoreFile.value, selectedVectorStoreName.value)) {
200
- alert('File not added, error ! ');
225
+ else {
226
+ if (!await openAI.addFile(selectedVectorStoreFile.value, selectedVectorStoreName.value, attributes)) {
227
+ alert('File not added, error ! ');
228
+ }
201
229
  }
202
230
  }
203
231
  async function deleteFile() {
@@ -216,29 +244,63 @@
216
244
  async function editFile() {
217
245
  const file = openAI.getFile(selectedVectorStoreFile.value);
218
246
  if (file) {
219
- editVectorStore.value = false;
220
- metadata.value = file.attributes ? file.attributes : openAI.fileAttributes;
247
+ editVectorStoreMetaData.value = false;
248
+ metadata.value = [];
249
+ const vs = openAI.vectorStore![selectedVectorStoreName.value];
250
+ for (const md of openAI.fileAttributes) {
251
+ const mdCopy = Object.assign({}, md);
252
+ if (file.attributes && file.attributes[md.name])
253
+ mdCopy.value = file.attributes[md.name];
254
+ else if (vs && vs.metadata && vs.metadata[md.name])
255
+ mdCopy.value = vs.metadata[md.name];
256
+ metadata.value.push(mdCopy);
257
+ }
221
258
  showMetaData.value = true;
222
259
  }
223
260
  }
224
- async function saveMetaData(items?: Dictionary<ItemType>) {
261
+ async function saveMetaData(items?: IDataItem[]) {
225
262
  showMetaData.value = false;
226
263
  if (items) {
227
- if (editVectorStore.value) {
228
- if (await openAI.setAttributes(selectedVectorStoreName.value, null, items as Dictionary<string>)) {
229
- vs.metadata = items;
264
+ if (editVectorStoreMetaData.value) {
265
+ const vs = openAI.vectorStore![selectedVectorStoreName.value];
266
+ if (vs) {
267
+ const md: Dictionary<string> = {};
268
+ for (const it of items) {
269
+ md[it.name] = it.value.toString();
270
+ }
271
+ if (await openAI.setAttributes(selectedVectorStoreName.value, null, md)) {
272
+ vs.metadata = md;
273
+ }
230
274
  }
231
275
  }
232
276
  else {
233
277
  const file = openAI.getFile(selectedVectorStoreFile.value);
234
278
  if (file) {
235
- if (await openAI.setAttributes(selectedVectorStoreName.value, file.id, items)) {
236
- file.attributes = items;
279
+ const att: Dictionary<ItemType> = {};
280
+ for (const it of items) {
281
+ att[it.name] = it.value;
282
+ }
283
+ if (await openAI.setAttributes(selectedVectorStoreName.value, file.id, att)) {
284
+ file.attributes = att;
237
285
  }
238
286
  }
239
287
  }
240
288
  }
241
289
  }
290
+ async function searchFile() {
291
+ let files: FileSystemFileHandle[] = [];
292
+ try {
293
+ files = await (window as any).showOpenFilePicker();
294
+ if (files.length > 0) {
295
+ fileHandle = files[0];
296
+ selectedVectorStoreFile.value = fileHandle.name;
297
+ }
298
+ }
299
+ catch (e) {
300
+ console.error('Error opening file picker:', e);
301
+ return;
302
+ }
303
+ }
242
304
  </script>
243
305
 
244
306
  <template>
@@ -375,9 +437,9 @@
375
437
  <v-combobox v-model="selectedVectorStoreName" :items="vectorStoreNames" hide-details density="compact" @update:modelValue="vectorStoreSelected()"></v-combobox>
376
438
  </v-col>
377
439
  <v-col cols="2">
378
- <v-btn disabled="!selectedVectorStoreName" variant="text" prepend-icon="$delete" @click="deleteVectorStore">VS</v-btn>
379
- <v-btn disabled="!selectedVectorStoreName" variant="text" prepend-icon="$plus" @click="addVectorStore">VS</v-btn>
380
- <v-btn disabled="!selectedVectorStoreName" variant="text" prepend-icon="$pencil" @click="editVectorStore">VS</v-btn>
440
+ <v-btn :disabled="!selectedVectorStoreName" variant="text" prepend-icon="$delete" @click="deleteVectorStore">VS</v-btn>
441
+ <v-btn :disabled="!selectedVectorStoreName" variant="text" prepend-icon="$plus" @click="addVectorStore">VS</v-btn>
442
+ <v-btn :disabled="!selectedVectorStoreName" variant="text" prepend-icon="$pencil" @click="editVectorStore">VS</v-btn>
381
443
  </v-col>
382
444
  <v-col cols="5">
383
445
  <v-combobox v-model="selectedVectorStoreFile" :items="vectorStoreFiles" hide-details density="compact"></v-combobox>
@@ -385,15 +447,15 @@
385
447
  <v-col cols="1">
386
448
  <v-btn variant="text" prepend-icon="$search" @click="searchFile">FILE</v-btn>
387
449
  </v-col>
388
- <v-col cols="1">
389
- <v-btn disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$delete" @click="deleteFile">FILE</v-btn>
390
- <v-btn disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$plus" @click="addFile">FILE</v-btn>
391
- <v-btn disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$pencil" @click="editFile">VS</v-btn>
450
+ <v-col cols="2">
451
+ <v-btn :disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$delete" @click="deleteFile">FILE</v-btn>
452
+ <v-btn :disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$plus" @click="addFile">FILE</v-btn>
453
+ <v-btn :disabled="!selectedVectorStoreFile" variant="text" prepend-icon="$pencil" @click="editFile">FILE</v-btn>
392
454
  </v-col>
393
455
  </v-row>
394
456
  </template>
395
457
  <v-dialog v-model="showMetaData">
396
- <OpenAIMetaData :items="metadata" :vectorstore="editVectorStore" @back="saveMetaData"></OpenAIMetaData>
458
+ <OpenAIMetaData :items="metadata" :vectorstore="editVectorStoreMetaData" @back="saveMetaData"></OpenAIMetaData>
397
459
  </v-dialog>
398
460
  <v-dialog v-model="showFunctions">
399
461
  <v-container fluid class="bg-office overflow-y-auto">
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed, ref, StyleValue } from 'vue';
3
+ import { IDataItem, Dictionary, IComparisonFilter, ICompoundFilter, IOpenAIFilter } from '@christianriedl/utils'
4
+ import SettingsLine from '../components/SettingsLine.vue';
5
+
6
+ const props = defineProps<{ vsmetadata: IDataItem[], fileattributes: IDataItem[], filter?: IOpenAIFilter }>();
7
+ const emits = defineEmits<{ (e: 'back', filter: IOpenAIFilter): void }>();
8
+ const items: IDataItem[] = [];
9
+ const opsvs = ['', 'eq'];
10
+ const opsfile = ['', 'eq', 'ne', 'gt', 'gte', 'lt', 'lte'];
11
+ const operators: Dictionary<string> = {};;
12
+ const numvs = props.vsmetadata.length;
13
+
14
+ for (const it of props.vsmetadata) {
15
+ items.push(Object.assign({}, it));
16
+ operators[it.name] = '';
17
+ }
18
+ for (const it of props.fileattributes) {
19
+ items.push(Object.assign({}, it));
20
+ operators[it.name] = '';
21
+ }
22
+ if (props.filter) {
23
+ if (props.filter.fileFilter) {
24
+ for (const f of props.filter.fileFilter.filters) {
25
+ const it = items.find(i => i.name === f.key);
26
+ if (it) {
27
+ it.value = f.value;
28
+ operators[it.name] = f.type;
29
+ }
30
+ }
31
+ }
32
+ if (props.filter.vectorStoreFilter) {
33
+ for (const name in props.filter.vectorStoreFilter) {
34
+ const it = items.find(i => i.name === name);
35
+ if (it) {
36
+ it.value = props.filter.vectorStoreFilter[name];
37
+ operators[it.name] = 'eq';
38
+ }
39
+ }
40
+ }
41
+ }
42
+ function onChange(item: IDataItem, hasChanged: boolean) {
43
+ if (!hasChanged)
44
+ operators[item.name] = '';
45
+ }
46
+ function onBack() {
47
+ const filter: IOpenAIFilter = {
48
+ fileFilter: { type: 'and', filters: [] },
49
+ vectorStoreFilter: {}
50
+ };
51
+ for (let i = 0; i < items.length; i++) {
52
+ const item = items[i];
53
+ if (operators[item.name]) {
54
+ if (i < numvs) {
55
+ filter.vectorStoreFilter[item.name] = item.value;
56
+ }
57
+ else {
58
+ const f: IComparisonFilter = { key: item.name, type: operators[item.name] as 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte', value: item.value };
59
+ filter.fileFilter.filters.push(f);
60
+ }
61
+ }
62
+ }
63
+ emits('back', filter);
64
+ }
65
+ function onOperation(item: IDataItem, op: string) {
66
+ operators[item.name] = op;
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <v-container fluid class="bg-office">
72
+ <h4>FileSearch filter</h4>
73
+ <v-table>
74
+ <thead>
75
+ <tr>
76
+ <th class="font-weight-bold" style="width:40%">Name</th>
77
+ <th class="font-weight-bold" style="width:40%">Value</th>
78
+ <th class="font-weight-bold" style="width:20%">Op</th>
79
+ </tr>
80
+ </thead>
81
+ <tbody>
82
+ <settings-line v-for="(item, index) in items" :key="item.name" :item="item" :op="operators[item.name]"
83
+ :operations="index < numvs ? opsvs : opsfile" clearable @change="onChange(item, true)"
84
+ @clear="onChange(item, false)" @operation="(op) => onOperation(item, op)">
85
+ </settings-line>
86
+ <tr>
87
+ <td>
88
+ <v-btn @click="onBack()">
89
+ BACK
90
+ <v-icon icon="$back"></v-icon>
91
+ </v-btn>
92
+ </td>
93
+ <td></td>
94
+ <td></td>
95
+ </tr>
96
+ </tbody>
97
+ </v-table>
98
+ </v-container>
99
+ </template>
@@ -7,14 +7,14 @@
7
7
  const emits = defineEmits<{ (e: 'back', items?: IDataItem[]): void }>();
8
8
  const items: IDataItem[] = [];
9
9
  for (const it of props.items) {
10
- items.push(Object.assign({}, it);
10
+ items.push(Object.assign({}, it));
11
11
  }
12
12
  const header = props.vectorstore ? "Vector Store Metadata" : "File Attributes";
13
13
  const changedItems = new Set<string>();
14
14
  const changed = ref(false);
15
15
 
16
- function onChange(item: IDataItem, changed: boolean) {
17
- if (changed)
16
+ function onChange(item: IDataItem, hasChanged: boolean) {
17
+ if (hasChanged)
18
18
  changedItems.add(item.name);
19
19
  else
20
20
  changedItems.delete(item.name);
@@ -27,27 +27,26 @@
27
27
  else {
28
28
  emits('back');
29
29
  }
30
-
30
+ }
31
31
  </script>
32
32
 
33
33
  <template>
34
- <v-container fluid >
34
+ <v-container fluid class="bg-office">
35
35
  <h4>{{header}}</h4>
36
36
  <v-table>
37
37
  <thead>
38
38
  <tr>
39
- <th style="width:50%">Name</th>
40
- <th style="width:50%">Value</th>
39
+ <th class="font-weight-bold" style="width:50%">Name</th>
40
+ <th class="font-weight-bold" style="width:50%">Value</th>
41
41
  </tr>
42
42
  </thead>
43
43
  <tbody>
44
- <settings-line v-for="item in items" :key="item.name" :item="item" @change="onChange(item, true)" @clear="onChange(item, false)"></settings-line>
44
+ <settings-line v-for="item in items" :key="item.name" :item="item" @change="onChange(item, true)" clearable @clear="onChange(item, false)"></settings-line>
45
45
  <tr>
46
- <td style="width:50%">
47
- <v-btn @click="onBack(false)">BACK</v-btn>
48
- <v-btn v-if="changed" @click="onBack(true)">SAVE</v-btn>
46
+ <td>
47
+ <v-btn @click="onBack">BACK</v-btn>
49
48
  </td>
50
- <td style="width:50%"></td>
49
+ <td></td>
51
50
  </tr>
52
51
  </tbody>
53
52
  </v-table>
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { reactive } from 'vue';
2
+ import { reactive, ref } from 'vue';
3
3
  import { IConfigItem, IDataItem, ItemType, EScope } from '@christianriedl/utils';
4
4
 
5
- const props = defineProps<{ item: IConfigItem | IDataItem; scopes?: EScope, clearable?: boolean }>();
6
- const emits = defineEmits<{ (e: 'change'): void, (e: 'clear'): void }>();
5
+ const props = defineProps<{ item: IConfigItem | IDataItem; scopes?: EScope, clearable?: boolean, op?: string, operations?: string[] }>();
6
+ const emits = defineEmits<{ (e: 'change'): void, (e: 'clear'): void, (e: 'operation', op: string): void }>();
7
7
 
8
8
  const item = reactive(props.item);
9
9
  const isString = typeof item.default === 'string' && !item.values;
@@ -14,10 +14,14 @@
14
14
  const isVisible = !props.scopes || ((item as IConfigItem).scopes & props.scopes) != 0;
15
15
  const enumValues: ItemType[] = item.values!;
16
16
  const description = (item as IConfigItem).description || item.name;
17
+ const operation = ref(props.op ? props.op : '');
17
18
 
18
19
  function onChange() {
19
20
  emits('change');
20
21
  }
22
+ function onChangeOperation() {
23
+ emits('operation', operation.value);
24
+ }
21
25
  function onClear() {
22
26
  item.value = item.default;
23
27
  emits('clear');
@@ -26,8 +30,8 @@
26
30
 
27
31
  <template>
28
32
  <tr v-if="isVisible" class="settingsline">
29
- <td style="width:50%">{{description}}</td>
30
- <td style="width:50%">
33
+ <td >{{description}}</td>
34
+ <td>
31
35
  <v-switch v-if="isBoolean" v-model="item.value" :disabled="isDisabled" density="compact" hide-details color="primary"
32
36
  :append-icon="props.clearable ? '$clear' : undefined" @update:modelValue="onChange" @click:append="onClear"></v-switch>
33
37
  <v-text-field v-if="isString" v-model="item.value" :disabled="isDisabled" :clearable="props.clearable" density="compact"
@@ -38,6 +42,10 @@
38
42
  :items="enumValues" persistent-hint @update:modelValue="onChange" @click:clear="onClear" dense solo hide-details single-line>
39
43
  </v-select>
40
44
  </td>
45
+ <td v-if="props.operations">
46
+ <v-select density="compact" v-model="operation" :items="props.operations" persistent-hint @update:modelValue="onChangeOperation" dense solo hide-details single-line>
47
+ </v-select>
48
+ </td>
41
49
  </tr>
42
50
  </template>
43
51
 
@@ -65,8 +65,8 @@
65
65
  <v-table>
66
66
  <thead>
67
67
  <tr>
68
- <th style="width:50%">Topic</th>
69
- <th style="width:50%">Parameter</th>
68
+ <th class="font-weight-bold" style="width:50%">Topic</th>
69
+ <th class="font-weight-bold" style="width:50%">Parameter</th>
70
70
  </tr>
71
71
  </thead>
72
72
  <tbody>