@mixd-id/web-scaffold 0.2.250801009 → 0.2.250801010

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.
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div class="flex-1 flex flex-col bg-base-400">
8
8
 
9
- <div class="p-3 pr-5 flex justify-center gap-4 bg-base-400 border-b-[1px] border-border-50">
9
+ <div class="p-3 pr-5 flex justify-center gap-4 border-b-[1px] border-border-50">
10
10
  <div class="flex-1 flex flex-row gap-4 items-center"
11
11
  @click.meta="log(page)"
12
12
  @click.alt="log(layout)">
@@ -356,6 +356,82 @@
356
356
 
357
357
  </div>
358
358
 
359
+ <div v-else-if="store.tabIndex === 7" class="flex-1 flex flex-col">
360
+ <div class="flex flex-row justify-end p-4">
361
+ <button type="button" @click="newChat()">
362
+ <svg width="19" height="19" class="fill-primary hover:fill-primary-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256C397.4 512 512 397.4 512 256S397.4 0 256 0zM352 280H280V352c0 13.2-10.8 24-23.1 24C242.8 376 232 365.2 232 352V280H160C146.8 280 136 269.2 136 256c0-13.2 10.8-24 24-24H232V160c0-13.2 10.8-24 24-24C269.2 136 280 146.8 280 160v72h72C365.2 232 376 242.8 376 256C376 269.2 365.2 280 352 280z"/></svg>
363
+ </button>
364
+ </div>
365
+ <div class="flex-1 overflow-y-auto">
366
+
367
+ <div class="flex flex-col gap-4 p-4">
368
+
369
+ <div v-for="message in chat.messages"
370
+ :class="$style.chat + ' ' + (message.direction === 1 ? $style.chatIn : $style.chatOut)">
371
+
372
+ <p>{{ message.body }}</p>
373
+
374
+ <div v-if="message.direction !== 1" class="flex text-text-300 flex-row items-center gap-2 self-stretch pl-3">
375
+ <small class="text-xs text-text-300">{{ message.llmOutputTokenCount }} token</small>
376
+ &bull;
377
+ <small class="text-xs text-text-300">{{ message.llmEllapsedMs }} ms</small>
378
+ <div class="flex-1"></div>
379
+ <button type="button" class="text-xs group hover:text-primary" v-if="(message.attachments ?? [])[0]" @click="chatSet(message.attachments[0])">
380
+ <svg width="9" height="9" class="inline fill-text-300 group-hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M176 480C148.6 480 128 457.6 128 432v-352c0-25.38 20.4-47.98 48.01-47.98c8.686 0 17.35 2.352 25.02 7.031l288 176C503.3 223.8 512 239.3 512 256s-8.703 32.23-22.97 40.95l-288 176C193.4 477.6 184.7 480 176 480z"/></svg>
381
+ Apply
382
+ </button>
383
+ </div>
384
+ </div>
385
+
386
+ </div>
387
+
388
+ <pre class="text-xs break-all whitespace-pre-wrap">
389
+
390
+ </pre>
391
+
392
+ </div>
393
+ <div class="p-2">
394
+ <div class="flex flex-col gap-2">
395
+ <div class="flex flex-row gap-2 items-start">
396
+ <div class="w-[22px]"></div>
397
+ <div class="flex-1 flex flex-row flex-wrap gap-2">
398
+ <div v-for="(attachment, idx) in chat.attachments">
399
+ <Image :src="attachment.image"
400
+ @click="chat.attachments.splice(idx, 1)"
401
+ class="w-[48px] h-[48px] border-[1px] rounded-lg border-border-200" />
402
+ </div>
403
+ </div>
404
+ </div>
405
+
406
+ <div class="flex flex-row items-start gap-2">
407
+ <div class="pt-1">
408
+ <Button variant="minimal" class="p-0 mt-1" type="button" @click="$refs.chatFile.click()">
409
+ <svg width="19" height="19" class="fill-primary hover:fill-primary-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256C397.4 512 512 397.4 512 256S397.4 0 256 0zM352 280H280V352c0 13.2-10.8 24-23.1 24C242.8 376 232 365.2 232 352V280H160C146.8 280 136 269.2 136 256c0-13.2 10.8-24 24-24H232V160c0-13.2 10.8-24 24-24C269.2 136 280 146.8 280 160v72h72C365.2 232 376 242.8 376 256C376 269.2 365.2 280 352 280z"/></svg>
410
+ </Button>
411
+ <input type="file" accept="image/*" multiple ref="chatFile" class="hidden" @change="onChatUpload" />
412
+ </div>
413
+ <div class="flex-1">
414
+ <Textarea v-model="chat.input" rows="5" class="max-h-[60vh]">
415
+ <template #end>
416
+ <div class="p-2">
417
+ <Button ref="chatBtn" variant="minimal" class="p-0" type="button" @click="chatApply">
418
+ <svg width="19" height="19" class="fill-primary hover:fill-primary-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM372.5 276.5l-144 88C224.7 366.8 220.3 368 216 368c-13.69 0-24-11.2-24-24V168C192 155.3 202.2 144 216 144c4.344 0 8.678 1.176 12.51 3.516l144 88C379.6 239.9 384 247.6 384 256C384 264.4 379.6 272.1 372.5 276.5z"/></svg>
419
+ </Button>
420
+ </div>
421
+ </template>
422
+ </Textarea>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="flex flex-row justify-end px-3 gap-2 text-text-300 text-xs">
427
+ <div class="text-text-300">{{ totalChatTokens }} tokens</div>
428
+ &bull;
429
+ <div class="text-text-300">{{ chat.model }}</div>
430
+ </div>
431
+ </div>
432
+ </div>
433
+ </div>
434
+
359
435
  </div>
360
436
 
361
437
  <div :class="$style.resize1"
@@ -564,6 +640,107 @@ import OGSettingModal from "./OGSettingModal.vue";
564
640
 
565
641
  const patchPageCache = {}
566
642
 
643
+ const fontFamilies = {
644
+ '"Anton", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Anton&display=swap');`,
645
+ 'Dosis, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Dosis:wght@400;700&display=swap');`,
646
+ 'Lato, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');`,
647
+ 'Merriweather, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&display=swap');`,
648
+ 'Montserrat, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');`,
649
+ '"Noto Sans", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;800&display=swap');`,
650
+ 'Oswald, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap');`,
651
+ 'Oxygen, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=swap');`,
652
+ 'Poppins, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');`,
653
+ 'Quantico, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Quantico:wght@400;700&display=swap"');`,
654
+ 'Raleway, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap');`,
655
+ '"Reddit Sans", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Reddit+Sans:wght@400;700&display=swap');`,
656
+ '"Roboto", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');`,
657
+ '"Roboto Slab", serif': `@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;800&display=swap');`,
658
+ 'Volkhov, serif': `@import url('https://fonts.googleapis.com/css2?family=Volkhov:wght@400;700&display=swap');`,
659
+ '"Work Sans", serif': `@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;700&display=swap');`,
660
+ }
661
+
662
+ const defaultMedia = {
663
+ ":root": {
664
+ "--spacing-1": [],
665
+ "--spacing-2": [],
666
+ "--spacing-3": [],
667
+ "--spacing-4": [],
668
+ "--spacing-5": [],
669
+ "--spacing-6": [],
670
+ "--spacing-7": [],
671
+ "--spacing-8": [],
672
+ "--spacing-9": [],
673
+ "--spacing-10": [],
674
+ "--spacing-11": [],
675
+ "--spacing-12": []
676
+ },
677
+ "*": {
678
+ "font-family": [ "Poppins, sans-serif" ]
679
+ },
680
+ "html, .html": {
681
+ "font-size": [],
682
+ "--base-300": [ "rgb(235, 235, 235)" ],
683
+ "--base-400": [ "rgb(245, 245, 245)" ],
684
+ "--base-500": [ "rgb(255, 255, 255)" ],
685
+ "--primary-100": [],
686
+ "--primary-200": [],
687
+ "--primary-300": [],
688
+ "--primary-400": [],
689
+ "--primary-500": [ "rgb(0, 0, 0)" ],
690
+ "--primary-600": [ "rgb(15, 15, 15)" ],
691
+ "--primary-700": [],
692
+ "--primary-800": [],
693
+ "--primary-900": [],
694
+ "--secondary-100": [],
695
+ "--secondary-200": [],
696
+ "--secondary-300": [],
697
+ "--secondary-400": [],
698
+ "--secondary-500": [],
699
+ "--secondary-600": [],
700
+ "--secondary-700": [],
701
+ "--secondary-800": [],
702
+ "--secondary-900": [],
703
+ "--text-100": [],
704
+ "--text-200": [],
705
+ "--text-300": [],
706
+ "--text-400": [],
707
+ "--text-500": [ "rgb(33, 33, 33)" ],
708
+ "--text-600": [],
709
+ "--text-700": [],
710
+ "--text-800": [],
711
+ "--text-900": []
712
+ }
713
+ }
714
+
715
+ function collectFontImports(styles) {
716
+ const usedFonts = new Set()
717
+ const media = styles?.media || {}
718
+
719
+ for (const selector in media) {
720
+ const rules = media[selector]
721
+ if (!rules || !rules['font-family']) continue
722
+
723
+ const values = rules['font-family']
724
+ if (!Array.isArray(values)) continue
725
+
726
+ for (const val of values) {
727
+ if (!val) continue
728
+ if (fontFamilies[val]) {
729
+ usedFonts.add(val)
730
+ }
731
+ }
732
+ }
733
+
734
+ return Array.from(usedFonts)
735
+ .map(font => fontFamilies[font])
736
+ .join('\n')
737
+ }
738
+
739
+ function indent(str, spaces = 2) {
740
+ const pad = ' '.repeat(spaces)
741
+ return str.split('\n').map(line => pad + line).join('\n')
742
+ }
743
+
567
744
  export default{
568
745
 
569
746
  components: {OGSettingModal, WebComponentSelector, WebLayoutSelector},
@@ -730,7 +907,7 @@ export default{
730
907
  },
731
908
 
732
909
  createLayout(layout){
733
- this.socket.send(`${this.controller}.create-layout`, layout)
910
+ this.useSocket().send(`${this.controller}.create-layout`, layout)
734
911
  .then(layout => {
735
912
  this.loadLayouts()
736
913
  this.page.layoutId = layout.id
@@ -739,57 +916,78 @@ export default{
739
916
  .catch(err => this.alert(err))
740
917
  },
741
918
 
742
- createStyleSheet(styles){
919
+ createStyleSheet(styles, breakpoints = {
920
+ md: '768px',
921
+ lg: '1024px',
922
+ xl: '1280px'
923
+ }){
924
+ const mediaBuckets = {
925
+ base: [],
926
+ md: [],
927
+ lg: [],
928
+ xl: []
929
+ }
930
+
931
+ const media = styles?.media || {}
932
+
933
+ for (const selector in media) {
934
+ const rules = media[selector]
935
+ if (!rules || Object.keys(rules).length === 0) continue
936
+
937
+ const ruleSets = {
938
+ base: [],
939
+ md: [],
940
+ lg: [],
941
+ xl: []
942
+ }
943
+
944
+ for (const prop in rules) {
945
+ const values = rules[prop]
946
+ if (!Array.isArray(values)) continue
947
+
948
+ if (values[0] !== undefined) ruleSets.base.push(`${prop}: ${values[0]};`)
949
+ if (values[1] !== undefined) ruleSets.md.push(`${prop}: ${values[1]};`)
950
+ if (values[2] !== undefined) ruleSets.lg.push(`${prop}: ${values[2]};`)
951
+ if (values[3] !== undefined) ruleSets.xl.push(`${prop}: ${values[3]};`)
952
+ }
743
953
 
744
- const mediaQueries = {
745
- '': '@media screen',
746
- 'md:': '@media screen and (min-width: 640px)',
954
+ for (const size in ruleSets) {
955
+ if (ruleSets[size].length === 0) continue
956
+ mediaBuckets[size].push(
957
+ `${selector} {\n ${ruleSets[size].join('\n ')}\n}`
958
+ )
959
+ }
747
960
  }
748
961
 
749
- const fontFamilies = {
750
- '"Anton", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Anton&display=swap');`,
751
- 'Dosis, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Dosis:wght@400;700&display=swap');`,
752
- 'Lato, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');`,
753
- 'Merriweather, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&display=swap');`,
754
- 'Montserrat, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');`,
755
- '"Noto Sans", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;800&display=swap');`,
756
- 'Oswald, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap');`,
757
- 'Oxygen, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=swap');`,
758
- 'Poppins, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');`,
759
- 'Quantico, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Quantico:wght@400;700&display=swap"');`,
760
- 'Raleway, sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap');`,
761
- '"Reddit Sans", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Reddit+Sans:wght@400;700&display=swap');`,
762
- '"Roboto", sans-serif': `@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');`,
763
- '"Roboto Slab", serif': `@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;800&display=swap');`,
764
- 'Volkhov, serif': `@import url('https://fonts.googleapis.com/css2?family=Volkhov:wght@400;700&display=swap');`,
765
- '"Work Sans", serif': `@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;700&display=swap');`,
962
+ let css = ''
963
+
964
+ const fontImports = collectFontImports(styles, fontFamilies)
965
+ if (fontImports) {
966
+ css += fontImports + '\n\n'
766
967
  }
767
968
 
768
- let text = ''
769
- const usedFonts = {}
770
- for(let mediaKey in styles?.media){
771
- const css = styles.media[mediaKey]
969
+ const mediaImports = styles?.imports || []
970
+ if (Array.isArray(mediaImports)) {
971
+ css += mediaImports.join('\n') + '\n\n'
972
+ }
772
973
 
773
- text += mediaQueries[mediaKey] + '{ '
774
- for(let key in css){
775
- text += key + '{ '
776
- for(let selector in css[key]){
777
- text += selector + ':' + css[key][selector] + ';'
974
+ if (mediaBuckets.base.length) {
975
+ css += mediaBuckets.base.join('\n\n') + '\n\n'
976
+ }
778
977
 
779
- if(selector === 'font-family' && fontFamilies[css[key][selector]]){
780
- usedFonts[css[key][selector]] = fontFamilies[css[key][selector]]
781
- }
782
- }
783
- text += '}'
784
- }
785
- text += '}\n'
978
+ if (mediaBuckets.md.length) {
979
+ css += `@media (min-width: ${breakpoints.md}) {\n${indent(mediaBuckets.md.join('\n\n'))}\n}\n\n`
786
980
  }
787
981
 
788
- return [
789
- ...Object.values(usedFonts),
790
- text
791
- ]
792
- .join("\n")
982
+ if (mediaBuckets.lg.length) {
983
+ css += `@media (min-width: ${breakpoints.lg}) {\n${indent(mediaBuckets.lg.join('\n\n'))}\n}\n\n`
984
+ }
985
+
986
+ if (mediaBuckets.xl.length) {
987
+ css += `@media (min-width: ${breakpoints.xl}) {\n${indent(mediaBuckets.xl.join('\n\n'))}\n}\n\n`
988
+ }
989
+
990
+ return css.trim()
793
991
  },
794
992
 
795
993
  createInstances(){
@@ -887,7 +1085,7 @@ export default{
887
1085
  },
888
1086
 
889
1087
  listen(){
890
- this.socket.send(`${this.controller}.subscribe`, { name:'page' })
1088
+ this.useSocket().send(`${this.controller}.subscribe`, { name:'page' })
891
1089
  .then(() => {
892
1090
 
893
1091
  })
@@ -895,16 +1093,17 @@ export default{
895
1093
  },
896
1094
 
897
1095
  async load(){
898
- return this.socket.send(`${this.controller}.open`, { uid:this.$route.params.uid })
899
- .then(({ page, host, previewHost, debugMode }) => {
1096
+ return this.useSocket().send(`${this.controller}.open`, { uid:this.$route.params.uid })
1097
+ .then(({ page, previewHost, debugMode, useChat }) => {
900
1098
 
901
1099
  this.patchPage(page)
902
1100
 
903
1101
  if(page) Object.assign(this.page, page)
904
- if(host) this.host = host
905
1102
  this.debugMode = debugMode ?? this.debugMode
906
1103
  if(previewHost) this.previewHost = previewHost
907
1104
 
1105
+ this.useChat = useChat ?? this.useChat
1106
+
908
1107
  this.prevData = {
909
1108
  page: JSON.stringify(this.page),
910
1109
  layout: JSON.stringify(this.layout)
@@ -912,21 +1111,11 @@ export default{
912
1111
 
913
1112
  this.pageHistory.commit()
914
1113
 
915
- host = this.host ?? import.meta.env.VITE_WEB_HOST
916
-
917
- if(host){
918
- this.$nextTick(() => {
919
- this.resize()
1114
+ this.$nextTick(() => {
1115
+ this.resize()
920
1116
 
921
- if(this.previewHost){
922
- this.iframeSrc = this.previewHost + `?edit-mode=${this.store.previewMode}`
923
- }
924
- else{
925
- this.iframeSrc = host + '/' + (this.page.path ?? '') +
926
- `?edit-mode=${this.store.previewMode}`
927
- }
928
- })
929
- }
1117
+ this.iframeSrc = this.previewHost + `?edit-mode=${this.store.previewMode}`
1118
+ })
930
1119
 
931
1120
  this.readyState = 1
932
1121
  })
@@ -1026,7 +1215,7 @@ export default{
1026
1215
  },
1027
1216
 
1028
1217
  async loadLayouts(){
1029
- this.socket.send(`${this.controller}.load-layouts`, {})
1218
+ this.useSocket().send(`${this.controller}.load-layouts`, {})
1030
1219
  .then(_ => {
1031
1220
  this.layouts = _
1032
1221
  })
@@ -1034,7 +1223,7 @@ export default{
1034
1223
 
1035
1224
  loadDatasource(){
1036
1225
  if(this.useDatasource && this.useDatasource[1]){
1037
- this.socket.send(this.useDatasource[1], this.page.datasource)
1226
+ this.useSocket().send(this.useDatasource[1], this.page.datasource)
1038
1227
  .then((data) => {
1039
1228
  Object.assign(this.page, { data })
1040
1229
  this.updateIframe()
@@ -1045,7 +1234,7 @@ export default{
1045
1234
  loadPreset(){
1046
1235
  if(!Object.keys(this.$route.query).map(_ => _.toLowerCase()).includes('reset')){
1047
1236
  if(this.presetKey){
1048
- return this.socket.send(this.presetSrc, { key:this.presetKey })
1237
+ return this.useSocket().send(this.presetSrc, { key:this.presetKey })
1049
1238
  .then(config => {
1050
1239
  if(config){
1051
1240
  Object.assign(this.config.params, config.params)
@@ -1339,7 +1528,7 @@ export default{
1339
1528
  this.createInstances()
1340
1529
 
1341
1530
  this.$refs.saveBtn.setState(2)
1342
- this.socket.send(`${this.controller}.save`, { ...this.page, layout:this.layout })
1531
+ this.useSocket().send(`${this.controller}.save`, { ...this.page, layout:this.layout })
1343
1532
  .then((_) => {
1344
1533
  this.prevData = {
1345
1534
  page: JSON.stringify(this.page),
@@ -1371,7 +1560,7 @@ export default{
1371
1560
 
1372
1561
  savePreset: invokeAfterIdle(function() {
1373
1562
  if(this.presetKey) {
1374
- this.socket.send(this.presetSrc,
1563
+ this.useSocket().send(this.presetSrc,
1375
1564
  {key: this.presetKey, config: this.config})
1376
1565
  }
1377
1566
  }),
@@ -1397,7 +1586,7 @@ export default{
1397
1586
  },
1398
1587
 
1399
1588
  stopListen(){
1400
- this.socket.send(`${this.controller}.unsubscribe`, { name:'page' })
1589
+ this.useSocket().send(`${this.controller}.unsubscribe`, { name:'page' })
1401
1590
  .catch((err) => this.toast(err))
1402
1591
  },
1403
1592
 
@@ -1464,6 +1653,97 @@ export default{
1464
1653
  }
1465
1654
  },
1466
1655
 
1656
+ chatApply(){
1657
+
1658
+ this.$refs.chatBtn.setState(2)
1659
+ return this.useSocket()
1660
+ .send(`${this.controller}.chat`, {
1661
+ key: this.chat.key,
1662
+ prompt: this.chat.input,
1663
+ attachments: this.chat.attachments,
1664
+ pageId: this.page.id,
1665
+ input: {
1666
+ components: this.page.components,
1667
+ styles: this.layout?.styles ?? {}
1668
+ }
1669
+ })
1670
+ .catch(err => this.alert(err))
1671
+ .then(res => {
1672
+ for(let message of res.messages){
1673
+ try{
1674
+ for(let attachment of (message.attachments ?? [])){
1675
+ if(attachment.type === 'page'){
1676
+ this.chatSet(attachment)
1677
+ }
1678
+ }
1679
+
1680
+ if(message.model) this.chat.model = message.model
1681
+
1682
+ this.chat.messages.push(message)
1683
+ }
1684
+ catch(e){
1685
+ console.error(e)
1686
+ }
1687
+ }
1688
+
1689
+ this.chat.input = ''
1690
+ this.chat.attachments = []
1691
+ })
1692
+ .finally(_ => this.$refs.chatBtn?.resetState())
1693
+ },
1694
+
1695
+ onChatUpload(e){
1696
+ for(let file of e.target.files){
1697
+ this.chat.attachments.push({
1698
+ type: "image",
1699
+ image: file
1700
+ })
1701
+ }
1702
+ },
1703
+
1704
+ chatSet(obj){
1705
+
1706
+ if(obj?.styles?.media && this.layout){
1707
+ Object.assign(this.layout, {
1708
+ styles: {
1709
+ media: this.createMedia(obj.styles.media),
1710
+ imports: obj.styles.imports
1711
+ }
1712
+ })
1713
+ }
1714
+
1715
+ if((obj?.components ?? []).length > 0){
1716
+ Object.assign(this.page, {
1717
+ components: obj.components
1718
+ })
1719
+ }
1720
+ },
1721
+
1722
+ createMedia(obj){
1723
+
1724
+ const newMedia = JSON.parse(JSON.stringify(defaultMedia))
1725
+
1726
+ for(let selector in obj){
1727
+ if(!newMedia[selector]){
1728
+ newMedia[selector] = {}
1729
+ }
1730
+ for(let prop in obj[selector]){
1731
+ newMedia[selector][prop] = obj[selector][prop]
1732
+ }
1733
+ }
1734
+
1735
+ return newMedia
1736
+ },
1737
+
1738
+ newChat(){
1739
+ this.config.params.chat = {
1740
+ key: md5('web-page-chat-' + new Date().getTime()),
1741
+ input: '',
1742
+ attachments: [],
1743
+ messages: []
1744
+ }
1745
+ }
1746
+
1467
1747
  },
1468
1748
 
1469
1749
  computed: {
@@ -1501,7 +1781,7 @@ export default{
1501
1781
  },
1502
1782
 
1503
1783
  computedIframeSrc(){
1504
- return `${this.iframeSrc}`.substring(0, `${this.iframeSrc}`.indexOf('?'))
1784
+ return this.page.app.hosts[0].name + this.page.path
1505
1785
  },
1506
1786
 
1507
1787
  currentItem(){
@@ -1604,6 +1884,17 @@ export default{
1604
1884
  return this.config.params
1605
1885
  },
1606
1886
 
1887
+ tabItems(){
1888
+ return [
1889
+ { text:"Page Info", value:1 },
1890
+ { text:"Layout", value:4 },
1891
+ { text:"Components", value:2 },
1892
+ this.useChat ? { text:"Chat", value:7 } : null,
1893
+ /*{ text:"Datasource", value:3 },*/
1894
+ ]
1895
+ .filter(_ => _)
1896
+ },
1897
+
1607
1898
  presetSrc(){
1608
1899
  return this.controller ?
1609
1900
  `${this.controller}.preset` :
@@ -1612,6 +1903,20 @@ export default{
1612
1903
 
1613
1904
  viewIndex(){
1614
1905
  return this.viewTypes.findIndex(_ => _.value === this.store.viewType)
1906
+ },
1907
+
1908
+ chat(){
1909
+ if(!this.config.params.chat)
1910
+ this.newChat()
1911
+ return this.config.params.chat
1912
+ },
1913
+
1914
+ totalChatTokens(){
1915
+ let total = 0
1916
+ for(let message of this.chat.messages){
1917
+ total += message.llmOutputTokenCount ?? 0
1918
+ }
1919
+ return total
1615
1920
  }
1616
1921
 
1617
1922
  },
@@ -1654,6 +1959,8 @@ export default{
1654
1959
  'flexDirection',
1655
1960
  'zIndex',
1656
1961
 
1962
+ 'backdropBlur',
1963
+
1657
1964
  'autoFlow', 'alignItems', 'justifyContent',
1658
1965
  'letterSpacing', 'lineClamp',
1659
1966
 
@@ -1665,12 +1972,11 @@ export default{
1665
1972
  'containerVariant', 'containerGridColumn', 'containerGap'
1666
1973
  ],
1667
1974
  itemClasses: [
1668
- 'itemMinWidth', 'itemRatio', 'itemVariant'
1975
+ 'itemMinWidth', 'itemRatio', 'itemVariant', 'objectFit'
1669
1976
  ],
1670
1977
  styleClasses: [
1671
1978
  'bgColors', 'bgImages', 'bdColor'
1672
1979
  ],
1673
- host: null,
1674
1980
  previewHost: null,
1675
1981
  iframeStyle: {},
1676
1982
  iframeSrc: '',
@@ -1687,12 +1993,6 @@ export default{
1687
1993
  ],
1688
1994
  routerBeforeEach: null,
1689
1995
  readyState: 2,
1690
- tabItems: [
1691
- { text:"Page Info", value:1 },
1692
- { text:"Layout", value:4 },
1693
- { text:"Components", value:2 },
1694
- /*{ text:"Datasource", value:3 },*/
1695
- ],
1696
1996
  stylesheets: [
1697
1997
  'font-family'
1698
1998
  ],
@@ -1700,12 +2000,14 @@ export default{
1700
2000
 
1701
2001
  extRightPane: null,
1702
2002
  debugMode: false,
2003
+
2004
+ useChat: false
1703
2005
  }
1704
2006
  },
1705
2007
 
1706
2008
  emits: [ 'close', 'unmount' ],
1707
2009
 
1708
- inject: [ 'alert', 'appStyle', 'confirm', 'socket', 'toast' ],
2010
+ inject: [ 'alert', 'appStyle', 'confirm', 'useSocket', 'toast' ],
1709
2011
 
1710
2012
  mounted() {
1711
2013
  this.loadPreset()
@@ -1729,7 +2031,7 @@ export default{
1729
2031
  })
1730
2032
 
1731
2033
  this.listen()
1732
- this.socket.onAny(this.onHooks)
2034
+ this.useSocket().onAny(this.onHooks)
1733
2035
  },
1734
2036
 
1735
2037
  unmounted() {
@@ -1739,7 +2041,7 @@ export default{
1739
2041
  window.removeEventListener('beforeunload', this.onUnload)
1740
2042
  this.routerBeforeEach()
1741
2043
  this.stopListen()
1742
- this.socket.offAny(this.onHooks)
2044
+ this.useSocket().offAny(this.onHooks)
1743
2045
  this.$emit('unmount', { uid:this.page.uid })
1744
2046
  },
1745
2047
 
@@ -1825,6 +2127,27 @@ export default{
1825
2127
  @apply border-[1px] border-border-200;
1826
2128
  }
1827
2129
 
2130
+ .chat{
2131
+ @apply max-w-[80%] flex flex-col;
2132
+ }
2133
+
2134
+ .chatIn{
2135
+ @apply self-start;
2136
+ }
2137
+ .chatIn p{
2138
+ @apply bg-border-100 p-3 rounded-lg;
2139
+ }
2140
+
2141
+ .chatOut{
2142
+ @apply self-end flex flex-col items-end;
2143
+ }
2144
+ .chatOut p{
2145
+ @apply bg-primary-100 p-3 rounded-lg;
2146
+ }
2147
+ .chatOut button{
2148
+ @apply p-0 px-2 text-sm mt-1 rounded-sm;
2149
+ }
2150
+
1828
2151
  </style>
1829
2152
 
1830
2153
  <style>