Thomas G. Lopes victor HF Staff commited on
Commit
f36471e
·
unverified ·
1 Parent(s): 84f775e

Enhancements (#79)

Browse files

- Improve checkpoints menu UX
- Allow running conversations individually
- Hide model page if its a custom model
- Regen buttons on messages

---------

Co-authored-by: Victor Muštar <victor.mustar@gmail.com>

package.json CHANGED
@@ -38,14 +38,14 @@
38
  "globals": "^16.0.0",
39
  "highlight.js": "^11.10.0",
40
  "jiti": "^2.4.2",
41
- "melt": "^0.29.3",
42
  "openai": "^4.90.0",
43
  "postcss": "^8.4.38",
44
  "prettier": "^3.1.1",
45
  "prettier-plugin-svelte": "^3.2.6",
46
  "prettier-plugin-tailwindcss": "^0.6.11",
47
  "runed": "^0.25.0",
48
- "svelte": "^5.20.4",
49
  "svelte-check": "^4.0.0",
50
  "tailwind-merge": "^3.0.2",
51
  "tailwindcss": "^4.0.9",
 
38
  "globals": "^16.0.0",
39
  "highlight.js": "^11.10.0",
40
  "jiti": "^2.4.2",
41
+ "melt": "^0.30.1",
42
  "openai": "^4.90.0",
43
  "postcss": "^8.4.38",
44
  "prettier": "^3.1.1",
45
  "prettier-plugin-svelte": "^3.2.6",
46
  "prettier-plugin-tailwindcss": "^0.6.11",
47
  "runed": "^0.25.0",
48
+ "svelte": "^5.28.2",
49
  "svelte-check": "^4.0.0",
50
  "tailwind-merge": "^3.0.2",
51
  "tailwindcss": "^4.0.9",
pnpm-lock.yaml CHANGED
@@ -10,7 +10,7 @@ importers:
10
  dependencies:
11
  eslint-plugin-svelte:
12
  specifier: ^3.3.1
13
- version: 3.3.1(eslint@9.22.0(jiti@2.4.2))(svelte@5.23.0)
14
  typia:
15
  specifier: ^8.0.0
16
  version: 8.0.0(@samchon/openapi@3.0.0)(typescript@5.8.2)
@@ -50,16 +50,16 @@ importers:
50
  version: 3.0.0
51
  '@sveltejs/adapter-auto':
52
  specifier: ^3.2.2
53
- version: 3.3.1(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))
54
  '@sveltejs/adapter-node':
55
  specifier: ^5.2.0
56
- version: 5.2.12(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))
57
  '@sveltejs/kit':
58
  specifier: ^2.5.27
59
- version: 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
60
  '@sveltejs/vite-plugin-svelte':
61
  specifier: ^4.0.0
62
- version: 4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
63
  '@tailwindcss/container-queries':
64
  specifier: ^0.1.1
65
  version: 0.1.1(tailwindcss@4.0.9)
@@ -88,8 +88,8 @@ importers:
88
  specifier: ^2.4.2
89
  version: 2.4.2
90
  melt:
91
- specifier: ^0.29.3
92
- version: 0.29.3(@floating-ui/dom@1.6.13)(svelte@5.23.0)
93
  openai:
94
  specifier: ^4.90.0
95
  version: 4.90.0
@@ -101,19 +101,19 @@ importers:
101
  version: 3.5.3
102
  prettier-plugin-svelte:
103
  specifier: ^3.2.6
104
- version: 3.3.3(prettier@3.5.3)(svelte@5.23.0)
105
  prettier-plugin-tailwindcss:
106
  specifier: ^0.6.11
107
- version: 0.6.11(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.23.0))(prettier@3.5.3)
108
  runed:
109
  specifier: ^0.25.0
110
- version: 0.25.0(svelte@5.23.0)
111
  svelte:
112
- specifier: ^5.20.4
113
- version: 5.23.0
114
  svelte-check:
115
  specifier: ^4.0.0
116
- version: 4.1.5(picomatch@4.0.2)(svelte@5.23.0)(typescript@5.8.2)
117
  tailwind-merge:
118
  specifier: ^3.0.2
119
  version: 3.0.2
@@ -134,7 +134,7 @@ importers:
134
  version: 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
135
  unplugin-icons:
136
  specifier: ^22.1.0
137
- version: 22.1.0(svelte@5.23.0)
138
  vite:
139
  specifier: ^5.4.4
140
  version: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
@@ -1407,8 +1407,8 @@ packages:
1407
  resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
1408
  engines: {node: '>=0.10'}
1409
 
1410
- esrap@1.4.5:
1411
- resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==}
1412
 
1413
  esrecurse@4.3.0:
1414
  resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
@@ -1825,8 +1825,8 @@ packages:
1825
  resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
1826
  engines: {node: '>= 0.4'}
1827
 
1828
- melt@0.29.3:
1829
- resolution: {integrity: sha512-cgorPvV3hSTugEnMpjLarugz8YRhUckM48i2gAgNzmqYx2w4hwvEsxA6GRy9DWst5rj+DKSmiDCcFRrHRpg/lA==}
1830
  peerDependencies:
1831
  '@floating-ui/dom': ^1.6.0
1832
  svelte: ^5.0.0
@@ -2299,8 +2299,8 @@ packages:
2299
  svelte:
2300
  optional: true
2301
 
2302
- svelte@5.23.0:
2303
- resolution: {integrity: sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==}
2304
  engines: {node: '>=18'}
2305
 
2306
  synckit@0.9.2:
@@ -3125,22 +3125,22 @@ snapshots:
3125
  dependencies:
3126
  acorn: 8.14.0
3127
 
3128
- '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))':
3129
  dependencies:
3130
- '@sveltejs/kit': 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3131
  import-meta-resolve: 4.1.0
3132
 
3133
- '@sveltejs/adapter-node@5.2.12(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))':
3134
  dependencies:
3135
  '@rollup/plugin-commonjs': 28.0.2(rollup@4.34.9)
3136
  '@rollup/plugin-json': 6.1.0(rollup@4.34.9)
3137
  '@rollup/plugin-node-resolve': 16.0.0(rollup@4.34.9)
3138
- '@sveltejs/kit': 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3139
  rollup: 4.34.9
3140
 
3141
- '@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3142
  dependencies:
3143
- '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3144
  '@types/cookie': 0.6.0
3145
  cookie: 0.6.0
3146
  devalue: 5.1.1
@@ -3152,26 +3152,26 @@ snapshots:
3152
  sade: 1.8.1
3153
  set-cookie-parser: 2.7.1
3154
  sirv: 3.0.1
3155
- svelte: 5.23.0
3156
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3157
 
3158
- '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3159
  dependencies:
3160
- '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3161
  debug: 4.4.0
3162
- svelte: 5.23.0
3163
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3164
  transitivePeerDependencies:
3165
  - supports-color
3166
 
3167
- '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3168
  dependencies:
3169
- '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.23.0)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3170
  debug: 4.4.0
3171
  deepmerge: 4.3.1
3172
  kleur: 4.1.5
3173
  magic-string: 0.30.17
3174
- svelte: 5.23.0
3175
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3176
  vitefu: 1.0.6(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3177
  transitivePeerDependencies:
@@ -3630,7 +3630,7 @@ snapshots:
3630
  optionalDependencies:
3631
  eslint-config-prettier: 10.1.1(eslint@9.22.0(jiti@2.4.2))
3632
 
3633
- eslint-plugin-svelte@3.3.1(eslint@9.22.0(jiti@2.4.2))(svelte@5.23.0):
3634
  dependencies:
3635
  '@eslint-community/eslint-utils': 4.4.1(eslint@9.22.0(jiti@2.4.2))
3636
  '@jridgewell/sourcemap-codec': 1.5.0
@@ -3642,9 +3642,9 @@ snapshots:
3642
  postcss-load-config: 3.1.4(postcss@8.5.3)
3643
  postcss-safe-parser: 7.0.1(postcss@8.5.3)
3644
  semver: 7.7.1
3645
- svelte-eslint-parser: 1.1.0(svelte@5.23.0)
3646
  optionalDependencies:
3647
- svelte: 5.23.0
3648
  transitivePeerDependencies:
3649
  - ts-node
3650
 
@@ -3713,7 +3713,7 @@ snapshots:
3713
  dependencies:
3714
  estraverse: 5.3.0
3715
 
3716
- esrap@1.4.5:
3717
  dependencies:
3718
  '@jridgewell/sourcemap-codec': 1.5.0
3719
 
@@ -4087,14 +4087,14 @@ snapshots:
4087
 
4088
  math-intrinsics@1.1.0: {}
4089
 
4090
- melt@0.29.3(@floating-ui/dom@1.6.13)(svelte@5.23.0):
4091
  dependencies:
4092
  '@floating-ui/dom': 1.6.13
4093
  dequal: 2.0.3
4094
  jest-axe: 9.0.0
4095
  nanoid: 5.1.5
4096
- runed: 0.23.4(svelte@5.23.0)
4097
- svelte: 5.23.0
4098
 
4099
  merge2@1.4.1: {}
4100
 
@@ -4305,16 +4305,16 @@ snapshots:
4305
  dependencies:
4306
  fast-diff: 1.3.0
4307
 
4308
- prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.23.0):
4309
  dependencies:
4310
  prettier: 3.5.3
4311
- svelte: 5.23.0
4312
 
4313
- prettier-plugin-tailwindcss@0.6.11(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.23.0))(prettier@3.5.3):
4314
  dependencies:
4315
  prettier: 3.5.3
4316
  optionalDependencies:
4317
- prettier-plugin-svelte: 3.3.3(prettier@3.5.3)(svelte@5.23.0)
4318
 
4319
  prettier@3.5.3: {}
4320
 
@@ -4410,15 +4410,15 @@ snapshots:
4410
  dependencies:
4411
  queue-microtask: 1.2.3
4412
 
4413
- runed@0.23.4(svelte@5.23.0):
4414
  dependencies:
4415
  esm-env: 1.2.2
4416
- svelte: 5.23.0
4417
 
4418
- runed@0.25.0(svelte@5.23.0):
4419
  dependencies:
4420
  esm-env: 1.2.2
4421
- svelte: 5.23.0
4422
 
4423
  rxjs@7.8.2:
4424
  dependencies:
@@ -4504,19 +4504,19 @@ snapshots:
4504
 
4505
  supports-preserve-symlinks-flag@1.0.0: {}
4506
 
4507
- svelte-check@4.1.5(picomatch@4.0.2)(svelte@5.23.0)(typescript@5.8.2):
4508
  dependencies:
4509
  '@jridgewell/trace-mapping': 0.3.25
4510
  chokidar: 4.0.3
4511
  fdir: 6.4.3(picomatch@4.0.2)
4512
  picocolors: 1.1.1
4513
  sade: 1.8.1
4514
- svelte: 5.23.0
4515
  typescript: 5.8.2
4516
  transitivePeerDependencies:
4517
  - picomatch
4518
 
4519
- svelte-eslint-parser@1.1.0(svelte@5.23.0):
4520
  dependencies:
4521
  eslint-scope: 8.3.0
4522
  eslint-visitor-keys: 4.2.0
@@ -4525,9 +4525,9 @@ snapshots:
4525
  postcss-scss: 4.0.9(postcss@8.5.3)
4526
  postcss-selector-parser: 7.1.0
4527
  optionalDependencies:
4528
- svelte: 5.23.0
4529
 
4530
- svelte@5.23.0:
4531
  dependencies:
4532
  '@ampproject/remapping': 2.3.0
4533
  '@jridgewell/sourcemap-codec': 1.5.0
@@ -4538,7 +4538,7 @@ snapshots:
4538
  axobject-query: 4.1.0
4539
  clsx: 2.1.1
4540
  esm-env: 1.2.2
4541
- esrap: 1.4.5
4542
  is-reference: 3.0.3
4543
  locate-character: 3.0.0
4544
  magic-string: 0.30.17
@@ -4641,7 +4641,7 @@ snapshots:
4641
 
4642
  undici-types@5.26.5: {}
4643
 
4644
- unplugin-icons@22.1.0(svelte@5.23.0):
4645
  dependencies:
4646
  '@antfu/install-pkg': 1.0.0
4647
  '@iconify/utils': 2.3.0
@@ -4649,7 +4649,7 @@ snapshots:
4649
  local-pkg: 1.1.1
4650
  unplugin: 2.2.0
4651
  optionalDependencies:
4652
- svelte: 5.23.0
4653
  transitivePeerDependencies:
4654
  - supports-color
4655
 
 
10
  dependencies:
11
  eslint-plugin-svelte:
12
  specifier: ^3.3.1
13
+ version: 3.3.1(eslint@9.22.0(jiti@2.4.2))(svelte@5.28.2)
14
  typia:
15
  specifier: ^8.0.0
16
  version: 8.0.0(@samchon/openapi@3.0.0)(typescript@5.8.2)
 
50
  version: 3.0.0
51
  '@sveltejs/adapter-auto':
52
  specifier: ^3.2.2
53
+ version: 3.3.1(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))
54
  '@sveltejs/adapter-node':
55
  specifier: ^5.2.0
56
+ version: 5.2.12(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))
57
  '@sveltejs/kit':
58
  specifier: ^2.5.27
59
+ version: 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
60
  '@sveltejs/vite-plugin-svelte':
61
  specifier: ^4.0.0
62
+ version: 4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
63
  '@tailwindcss/container-queries':
64
  specifier: ^0.1.1
65
  version: 0.1.1(tailwindcss@4.0.9)
 
88
  specifier: ^2.4.2
89
  version: 2.4.2
90
  melt:
91
+ specifier: ^0.30.1
92
+ version: 0.30.1(@floating-ui/dom@1.6.13)(svelte@5.28.2)
93
  openai:
94
  specifier: ^4.90.0
95
  version: 4.90.0
 
101
  version: 3.5.3
102
  prettier-plugin-svelte:
103
  specifier: ^3.2.6
104
+ version: 3.3.3(prettier@3.5.3)(svelte@5.28.2)
105
  prettier-plugin-tailwindcss:
106
  specifier: ^0.6.11
107
+ version: 0.6.11(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.28.2))(prettier@3.5.3)
108
  runed:
109
  specifier: ^0.25.0
110
+ version: 0.25.0(svelte@5.28.2)
111
  svelte:
112
+ specifier: ^5.28.2
113
+ version: 5.28.2
114
  svelte-check:
115
  specifier: ^4.0.0
116
+ version: 4.1.5(picomatch@4.0.2)(svelte@5.28.2)(typescript@5.8.2)
117
  tailwind-merge:
118
  specifier: ^3.0.2
119
  version: 3.0.2
 
134
  version: 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
135
  unplugin-icons:
136
  specifier: ^22.1.0
137
+ version: 22.1.0(svelte@5.28.2)
138
  vite:
139
  specifier: ^5.4.4
140
  version: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
 
1407
  resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
1408
  engines: {node: '>=0.10'}
1409
 
1410
+ esrap@1.4.6:
1411
+ resolution: {integrity: sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==}
1412
 
1413
  esrecurse@4.3.0:
1414
  resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
 
1825
  resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
1826
  engines: {node: '>= 0.4'}
1827
 
1828
+ melt@0.30.1:
1829
+ resolution: {integrity: sha512-Z3X3IMknWSbXFlzQA6On18kdGf1a+Kgqu/TxxvchjGGiS3RINd96PrlLU2Bl/SOxF+UWLLYmH1fohwiMz9UsQQ==}
1830
  peerDependencies:
1831
  '@floating-ui/dom': ^1.6.0
1832
  svelte: ^5.0.0
 
2299
  svelte:
2300
  optional: true
2301
 
2302
+ svelte@5.28.2:
2303
+ resolution: {integrity: sha512-FbWBxgWOpQfhKvoGJv/TFwzqb4EhJbwCD17dB0tEpQiw1XyUEKZJtgm4nA4xq3LLsMo7hu5UY/BOFmroAxKTMg==}
2304
  engines: {node: '>=18'}
2305
 
2306
  synckit@0.9.2:
 
3125
  dependencies:
3126
  acorn: 8.14.0
3127
 
3128
+ '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))':
3129
  dependencies:
3130
+ '@sveltejs/kit': 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3131
  import-meta-resolve: 4.1.0
3132
 
3133
+ '@sveltejs/adapter-node@5.2.12(@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))':
3134
  dependencies:
3135
  '@rollup/plugin-commonjs': 28.0.2(rollup@4.34.9)
3136
  '@rollup/plugin-json': 6.1.0(rollup@4.34.9)
3137
  '@rollup/plugin-node-resolve': 16.0.0(rollup@4.34.9)
3138
+ '@sveltejs/kit': 2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3139
  rollup: 4.34.9
3140
 
3141
+ '@sveltejs/kit@2.18.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3142
  dependencies:
3143
+ '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3144
  '@types/cookie': 0.6.0
3145
  cookie: 0.6.0
3146
  devalue: 5.1.1
 
3152
  sade: 1.8.1
3153
  set-cookie-parser: 2.7.1
3154
  sirv: 3.0.1
3155
+ svelte: 5.28.2
3156
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3157
 
3158
+ '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3159
  dependencies:
3160
+ '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3161
  debug: 4.4.0
3162
+ svelte: 5.28.2
3163
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3164
  transitivePeerDependencies:
3165
  - supports-color
3166
 
3167
+ '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))':
3168
  dependencies:
3169
+ '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)))(svelte@5.28.2)(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3170
  debug: 4.4.0
3171
  deepmerge: 4.3.1
3172
  kleur: 4.1.5
3173
  magic-string: 0.30.17
3174
+ svelte: 5.28.2
3175
  vite: 5.4.14(@types/node@18.19.84)(lightningcss@1.29.1)
3176
  vitefu: 1.0.6(vite@5.4.14(@types/node@18.19.84)(lightningcss@1.29.1))
3177
  transitivePeerDependencies:
 
3630
  optionalDependencies:
3631
  eslint-config-prettier: 10.1.1(eslint@9.22.0(jiti@2.4.2))
3632
 
3633
+ eslint-plugin-svelte@3.3.1(eslint@9.22.0(jiti@2.4.2))(svelte@5.28.2):
3634
  dependencies:
3635
  '@eslint-community/eslint-utils': 4.4.1(eslint@9.22.0(jiti@2.4.2))
3636
  '@jridgewell/sourcemap-codec': 1.5.0
 
3642
  postcss-load-config: 3.1.4(postcss@8.5.3)
3643
  postcss-safe-parser: 7.0.1(postcss@8.5.3)
3644
  semver: 7.7.1
3645
+ svelte-eslint-parser: 1.1.0(svelte@5.28.2)
3646
  optionalDependencies:
3647
+ svelte: 5.28.2
3648
  transitivePeerDependencies:
3649
  - ts-node
3650
 
 
3713
  dependencies:
3714
  estraverse: 5.3.0
3715
 
3716
+ esrap@1.4.6:
3717
  dependencies:
3718
  '@jridgewell/sourcemap-codec': 1.5.0
3719
 
 
4087
 
4088
  math-intrinsics@1.1.0: {}
4089
 
4090
+ melt@0.30.1(@floating-ui/dom@1.6.13)(svelte@5.28.2):
4091
  dependencies:
4092
  '@floating-ui/dom': 1.6.13
4093
  dequal: 2.0.3
4094
  jest-axe: 9.0.0
4095
  nanoid: 5.1.5
4096
+ runed: 0.23.4(svelte@5.28.2)
4097
+ svelte: 5.28.2
4098
 
4099
  merge2@1.4.1: {}
4100
 
 
4305
  dependencies:
4306
  fast-diff: 1.3.0
4307
 
4308
+ prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.28.2):
4309
  dependencies:
4310
  prettier: 3.5.3
4311
+ svelte: 5.28.2
4312
 
4313
+ prettier-plugin-tailwindcss@0.6.11(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.28.2))(prettier@3.5.3):
4314
  dependencies:
4315
  prettier: 3.5.3
4316
  optionalDependencies:
4317
+ prettier-plugin-svelte: 3.3.3(prettier@3.5.3)(svelte@5.28.2)
4318
 
4319
  prettier@3.5.3: {}
4320
 
 
4410
  dependencies:
4411
  queue-microtask: 1.2.3
4412
 
4413
+ runed@0.23.4(svelte@5.28.2):
4414
  dependencies:
4415
  esm-env: 1.2.2
4416
+ svelte: 5.28.2
4417
 
4418
+ runed@0.25.0(svelte@5.28.2):
4419
  dependencies:
4420
  esm-env: 1.2.2
4421
+ svelte: 5.28.2
4422
 
4423
  rxjs@7.8.2:
4424
  dependencies:
 
4504
 
4505
  supports-preserve-symlinks-flag@1.0.0: {}
4506
 
4507
+ svelte-check@4.1.5(picomatch@4.0.2)(svelte@5.28.2)(typescript@5.8.2):
4508
  dependencies:
4509
  '@jridgewell/trace-mapping': 0.3.25
4510
  chokidar: 4.0.3
4511
  fdir: 6.4.3(picomatch@4.0.2)
4512
  picocolors: 1.1.1
4513
  sade: 1.8.1
4514
+ svelte: 5.28.2
4515
  typescript: 5.8.2
4516
  transitivePeerDependencies:
4517
  - picomatch
4518
 
4519
+ svelte-eslint-parser@1.1.0(svelte@5.28.2):
4520
  dependencies:
4521
  eslint-scope: 8.3.0
4522
  eslint-visitor-keys: 4.2.0
 
4525
  postcss-scss: 4.0.9(postcss@8.5.3)
4526
  postcss-selector-parser: 7.1.0
4527
  optionalDependencies:
4528
+ svelte: 5.28.2
4529
 
4530
+ svelte@5.28.2:
4531
  dependencies:
4532
  '@ampproject/remapping': 2.3.0
4533
  '@jridgewell/sourcemap-codec': 1.5.0
 
4538
  axobject-query: 4.1.0
4539
  clsx: 2.1.1
4540
  esm-env: 1.2.2
4541
+ esrap: 1.4.6
4542
  is-reference: 3.0.3
4543
  locate-character: 3.0.0
4544
  magic-string: 0.30.17
 
4641
 
4642
  undici-types@5.26.5: {}
4643
 
4644
+ unplugin-icons@22.1.0(svelte@5.28.2):
4645
  dependencies:
4646
  '@antfu/install-pkg': 1.0.0
4647
  '@iconify/utils': 2.3.0
 
4649
  local-pkg: 1.1.1
4650
  unplugin: 2.2.0
4651
  optionalDependencies:
4652
+ svelte: 5.28.2
4653
  transitivePeerDependencies:
4654
  - supports-color
4655
 
src/lib/actions/click-outside.ts CHANGED
@@ -3,8 +3,8 @@ import type { Action } from "svelte/action";
3
  export const clickOutside: Action<HTMLElement, () => void> = (node, callback) => {
4
  let _callback = callback;
5
 
6
- function update(callback: () => void) {
7
- _callback = callback;
8
  }
9
 
10
  function handleClick(event: MouseEvent) {
@@ -12,17 +12,44 @@ export const clickOutside: Action<HTMLElement, () => void> = (node, callback) =>
12
  // Don't close if text is selected
13
  return;
14
  }
15
- if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  _callback();
17
  }
18
  }
19
 
20
- document.addEventListener("click", handleClick, true);
 
 
 
 
 
 
21
 
22
  return {
23
  update,
24
  destroy() {
25
- document.removeEventListener("click", handleClick, true);
 
 
 
 
26
  },
27
  };
28
  };
 
3
  export const clickOutside: Action<HTMLElement, () => void> = (node, callback) => {
4
  let _callback = callback;
5
 
6
+ function update(newCallback: () => void) {
7
+ _callback = newCallback;
8
  }
9
 
10
  function handleClick(event: MouseEvent) {
 
12
  // Don't close if text is selected
13
  return;
14
  }
15
+
16
+ // For dialog elements, check if click was on the backdrop
17
+ if (node instanceof HTMLDialogElement) {
18
+ const rect = node.getBoundingClientRect();
19
+ const isInDialog =
20
+ event.clientX >= rect.left &&
21
+ event.clientX <= rect.right &&
22
+ event.clientY >= rect.top &&
23
+ event.clientY <= rect.bottom;
24
+
25
+ if (!isInDialog) {
26
+ _callback();
27
+ return;
28
+ }
29
+ }
30
+
31
+ // For non-dialog elements, use the standard contains check
32
+ if (!node.contains(event.target as Node) && !event.defaultPrevented) {
33
  _callback();
34
  }
35
  }
36
 
37
+ // For dialogs, listen on the element itself
38
+ if (node instanceof HTMLDialogElement) {
39
+ node.addEventListener("click", handleClick);
40
+ } else {
41
+ // For other elements, listen on the document
42
+ document.addEventListener("click", handleClick, true);
43
+ }
44
 
45
  return {
46
  update,
47
  destroy() {
48
+ if (node instanceof HTMLDialogElement) {
49
+ node.removeEventListener("click", handleClick);
50
+ } else {
51
+ document.removeEventListener("click", handleClick, true);
52
+ }
53
  },
54
  };
55
  };
src/lib/components/icon-custom.svelte ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { SVGAttributes } from "svelte/elements";
3
+
4
+ type Icon = "regen";
5
+
6
+ interface Props extends SVGAttributes<SVGElement> {
7
+ icon: Icon;
8
+ }
9
+
10
+ let { icon, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ {#if icon === "regen"}
14
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...rest}>
15
+ <path
16
+ d="M9.01857 2.5C7.62667 2.49856 6.26819 2.92612 5.12839 3.7244C3.9886 4.52267 3.12295 5.65278 2.6495 6.96064C2.17605 8.26851 2.11784 9.69045 2.48281 11.0326C2.84778 12.3747 3.61816 13.5717 4.68891 14.4603H2.77351V15.5H6.93688V11.34H5.89639V14.0549C4.86194 13.3824 4.0727 12.3939 3.64631 11.2367C3.21992 10.0795 3.17921 8.81556 3.53025 7.63335C3.88129 6.45115 4.60529 5.41395 5.59432 4.67637C6.58335 3.93878 7.78444 3.54033 9.01857 3.54037V2.5ZM11.1003 3.01983V7.17989H12.1415V4.46559C12.8436 4.92312 13.4366 5.52914 13.8785 6.24083C14.3205 6.95252 14.6007 7.7525 14.6992 8.58426H15.7521C15.576 6.81965 14.7129 5.19487 13.3489 4.06019H15.2636V3.01983H11.1003ZM11.3866 16.8117C11.349 16.8117 11.3118 16.8043 11.277 16.7899C11.2423 16.7756 11.2107 16.7545 11.1841 16.7279C11.1575 16.7013 11.1364 16.6698 11.1221 16.6351C11.1077 16.6004 11.1003 16.5632 11.1003 16.5256V10.2311C11.1003 10.1814 11.1132 10.1325 11.1379 10.0893C11.1626 10.0462 11.1981 10.0101 11.2409 9.98485C11.2837 9.95955 11.3324 9.94584 11.3822 9.94507C11.4319 9.9443 11.481 9.95651 11.5246 9.98047L17.2515 13.1277C17.2964 13.1524 17.3338 13.1887 17.3599 13.2328C17.3859 13.2769 17.3997 13.3272 17.3997 13.3784C17.3997 13.4296 17.3859 13.4798 17.3599 13.5239C17.3338 13.568 17.2964 13.6043 17.2515 13.629L11.5246 16.7762C11.4824 16.7995 11.4349 16.8117 11.3866 16.8117Z"
17
+ fill="currentColor"
18
+ />
19
+ </svg>
20
+ {/if}
src/lib/components/inference-playground/checkpoints-menu.svelte CHANGED
@@ -1,20 +1,27 @@
1
  <script lang="ts">
 
2
  import { checkpoints } from "$lib/state/checkpoints.svelte";
3
  import { session } from "$lib/state/session.svelte.js";
 
4
  import { Popover } from "melt/builders";
5
  import { Tooltip } from "melt/components";
6
  import { fly } from "svelte/transition";
 
7
  import IconHistory from "~icons/carbon/recently-viewed";
8
- import IconDelete from "~icons/carbon/trash-can";
9
  import IconStar from "~icons/carbon/star";
10
  import IconStarFilled from "~icons/carbon/star-filled";
11
- import IconCompare from "~icons/carbon/compare";
12
 
13
  const popover = new Popover({
14
  floatingConfig: {
15
  offset: { crossAxis: -12 },
16
  },
 
 
 
 
17
  });
 
18
 
19
  const projCheckpoints = $derived(checkpoints.for(session.project.id));
20
  </script>
@@ -26,10 +33,16 @@
26
  {/if}
27
  </button>
28
 
29
- <div
30
- class="mb-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
 
 
31
  {...popover.content}
32
  >
 
 
 
 
33
  <div class="max-h-120 w-80 overflow-x-clip overflow-y-auto p-3 pb-1">
34
  <div class="mb-2 flex items-center justify-between px-1">
35
  <h3 class="text-sm font-medium dark:text-white">Checkpoints</h3>
@@ -114,7 +127,10 @@
114
  {...tooltip.content}
115
  transition:fly={{ x: -2 }}
116
  >
117
- <div class="size-4 rounded-tl border-t border-l border-gray-700" {...tooltip.arrow}></div>
 
 
 
118
  {#each state.conversations as conversation, i}
119
  {@const msgs = conversation.messages}
120
  {@const sliced = msgs.slice(0, 4)}
@@ -129,8 +145,7 @@
129
  temp: {conversation.config.temperature}
130
  | max tokens: {conversation.config.max_tokens}
131
  </p>
132
- {#each sliced as msg, i}
133
- {@const isLast = i === sliced.length - 1}
134
  <div class="flex flex-col gap-1 p-2">
135
  <p class="font-mono text-xs font-medium text-gray-400 uppercase">{msg.role}</p>
136
  {#if msg.content?.trim()}
@@ -155,4 +170,4 @@
155
  </div>
156
  {/each}
157
  </div>
158
- </div>
 
1
  <script lang="ts">
2
+ import { clickOutside } from "$lib/actions/click-outside.js";
3
  import { checkpoints } from "$lib/state/checkpoints.svelte";
4
  import { session } from "$lib/state/session.svelte.js";
5
+ import { iterate } from "$lib/utils/array.js";
6
  import { Popover } from "melt/builders";
7
  import { Tooltip } from "melt/components";
8
  import { fly } from "svelte/transition";
9
+ import IconCompare from "~icons/carbon/compare";
10
  import IconHistory from "~icons/carbon/recently-viewed";
 
11
  import IconStar from "~icons/carbon/star";
12
  import IconStarFilled from "~icons/carbon/star-filled";
13
+ import IconDelete from "~icons/carbon/trash-can";
14
 
15
  const popover = new Popover({
16
  floatingConfig: {
17
  offset: { crossAxis: -12 },
18
  },
19
+ onOpenChange: open => {
20
+ if (open) dialog?.showModal();
21
+ else dialog?.close();
22
+ },
23
  });
24
+ let dialog = $state<HTMLDialogElement>();
25
 
26
  const projCheckpoints = $derived(checkpoints.for(session.project.id));
27
  </script>
 
33
  {/if}
34
  </button>
35
 
36
+ <dialog
37
+ bind:this={dialog}
38
+ class="mb-2 !overflow-visible rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
39
+ use:clickOutside={() => (popover.open = false)}
40
  {...popover.content}
41
  >
42
+ <div
43
+ class="size-4 translate-x-3 rounded-tl border-t border-l border-gray-200 dark:border-gray-700"
44
+ {...popover.arrow}
45
+ ></div>
46
  <div class="max-h-120 w-80 overflow-x-clip overflow-y-auto p-3 pb-1">
47
  <div class="mb-2 flex items-center justify-between px-1">
48
  <h3 class="text-sm font-medium dark:text-white">Checkpoints</h3>
 
127
  {...tooltip.content}
128
  transition:fly={{ x: -2 }}
129
  >
130
+ <div
131
+ class="size-4 rounded-tl border-t border-l border-gray-200 dark:border-gray-700"
132
+ {...tooltip.arrow}
133
+ ></div>
134
  {#each state.conversations as conversation, i}
135
  {@const msgs = conversation.messages}
136
  {@const sliced = msgs.slice(0, 4)}
 
145
  temp: {conversation.config.temperature}
146
  | max tokens: {conversation.config.max_tokens}
147
  </p>
148
+ {#each iterate(sliced) as [msg, isLast]}
 
149
  <div class="flex flex-col gap-1 p-2">
150
  <p class="font-mono text-xs font-medium text-gray-400 uppercase">{msg.role}</p>
151
  {#if msg.content?.trim()}
 
170
  </div>
171
  {/each}
172
  </div>
173
+ </dialog>
src/lib/components/inference-playground/conversation.svelte CHANGED
@@ -7,6 +7,8 @@
7
  import IconPlus from "~icons/carbon/add";
8
  import CodeSnippets from "./code-snippets.svelte";
9
  import Message from "./message.svelte";
 
 
10
 
11
  interface Props {
12
  conversation: Conversation;
@@ -52,6 +54,19 @@
52
  function deleteMessage(idx: number) {
53
  conversation.messages = conversation.messages.slice(0, idx);
54
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  </script>
56
 
57
  <div
@@ -61,13 +76,15 @@
61
  id="test-this"
62
  >
63
  {#if !viewCode}
64
- {#each conversation.messages as _msg, idx}
65
  <Message
66
  bind:message={conversation.messages[idx]!}
67
  {conversation}
68
  autofocus={idx === conversation.messages.length - 1}
69
  {loading}
70
  onDelete={() => deleteMessage(idx)}
 
 
71
  />
72
  {/each}
73
 
 
7
  import IconPlus from "~icons/carbon/add";
8
  import CodeSnippets from "./code-snippets.svelte";
9
  import Message from "./message.svelte";
10
+ import { iterate } from "$lib/utils/array.js";
11
+ import { session } from "$lib/state/session.svelte";
12
 
13
  interface Props {
14
  conversation: Conversation;
 
54
  function deleteMessage(idx: number) {
55
  conversation.messages = conversation.messages.slice(0, idx);
56
  }
57
+
58
+ function regenMessage(idx: number) {
59
+ const msg = conversation.messages[idx];
60
+ if (!msg) return;
61
+ if (msg.role === "user") {
62
+ conversation.messages = conversation.messages.slice(0, idx + 1);
63
+ } else {
64
+ conversation.messages = conversation.messages.slice(0, idx);
65
+ }
66
+
67
+ session.stopGenerating();
68
+ session.run(conversation);
69
+ }
70
  </script>
71
 
72
  <div
 
76
  id="test-this"
77
  >
78
  {#if !viewCode}
79
+ {#each iterate(conversation.messages) as [_msg, { isLast }], idx}
80
  <Message
81
  bind:message={conversation.messages[idx]!}
82
  {conversation}
83
  autofocus={idx === conversation.messages.length - 1}
84
  {loading}
85
  onDelete={() => deleteMessage(idx)}
86
+ onRegen={() => regenMessage(idx)}
87
+ {isLast}
88
  />
89
  {/each}
90
 
src/lib/components/inference-playground/custom-model-config.svelte CHANGED
@@ -21,10 +21,10 @@
21
 
22
  <script lang="ts">
23
  import { autofocus } from "$lib/actions/autofocus.js";
24
-
25
  import { clickOutside } from "$lib/actions/click-outside.js";
26
  import { models } from "$lib/state/models.svelte";
27
- import { type Conversation, type CustomModel } from "$lib/types.js";
28
  import type { HTMLFormAttributes } from "svelte/elements";
29
  import { fade, scale } from "svelte/transition";
30
  import IconCross from "~icons/carbon/close";
@@ -34,6 +34,8 @@
34
  import Tooltip from "../tooltip.svelte";
35
  import { createFieldValidation } from "$lib/utils/form.svelte.js";
36
  import { isValidURL } from "$lib/utils/url.js";
 
 
37
 
38
  let dialog: HTMLDialogElement | undefined = $state();
39
  const exists = $derived(!!models.custom.find(m => m._id === model?._id));
@@ -187,7 +189,7 @@
187
  <p class="text-xs text-red-300">{endpointValidation.msg}</p>
188
  </label>
189
  <label class="flex flex-col gap-2">
190
- <p class="block text-sm font-medium text-gray-900 dark:text-white">Access Token</p>
191
  <input
192
  bind:value={model.accessToken}
193
  placeholder="XXXXXXXXXXXXXXXXXXXX"
@@ -197,6 +199,50 @@
197
  <p class="text-sm text-gray-500">Stored locally - not sent to our server</p>
198
  </label>
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  {#if message}
201
  <div
202
  class={[
 
21
 
22
  <script lang="ts">
23
  import { autofocus } from "$lib/actions/autofocus.js";
24
+ import IconCaret from "~icons/carbon/chevron-down";
25
  import { clickOutside } from "$lib/actions/click-outside.js";
26
  import { models } from "$lib/state/models.svelte";
27
+ import { PipelineTag, pipelineTagLabel, type Conversation, type CustomModel } from "$lib/types.js";
28
  import type { HTMLFormAttributes } from "svelte/elements";
29
  import { fade, scale } from "svelte/transition";
30
  import IconCross from "~icons/carbon/close";
 
34
  import Tooltip from "../tooltip.svelte";
35
  import { createFieldValidation } from "$lib/utils/form.svelte.js";
36
  import { isValidURL } from "$lib/utils/url.js";
37
+ import { Select } from "melt/components";
38
+ import { keys } from "$lib/utils/object.js";
39
 
40
  let dialog: HTMLDialogElement | undefined = $state();
41
  const exists = $derived(!!models.custom.find(m => m._id === model?._id));
 
189
  <p class="text-xs text-red-300">{endpointValidation.msg}</p>
190
  </label>
191
  <label class="flex flex-col gap-2">
192
+ <p class="block text-sm font-medium text-gray-900 dark:text-white">Access token</p>
193
  <input
194
  bind:value={model.accessToken}
195
  placeholder="XXXXXXXXXXXXXXXXXXXX"
 
199
  <p class="text-sm text-gray-500">Stored locally - not sent to our server</p>
200
  </label>
201
 
202
+ <div class="flex flex-col gap-2">
203
+ <Select bind:value={model.pipeline_tag}>
204
+ {#snippet children(select)}
205
+ <label for={select.ids.trigger} class="block text-sm font-medium text-gray-900 dark:text-white">
206
+ Supported task
207
+ </label>
208
+
209
+ <button
210
+ {...select.trigger}
211
+ class={[
212
+ "relative flex grow items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm",
213
+ "hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
214
+ ]}
215
+ >
216
+ <div class="flex items-center gap-1 text-sm">
217
+ {pipelineTagLabel[model?.pipeline_tag ?? PipelineTag.TextGeneration]}
218
+ </div>
219
+ <div
220
+ class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
221
+ >
222
+ <IconCaret />
223
+ </div>
224
+ </button>
225
+
226
+ <div {...select.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
227
+ {#each keys(PipelineTag) as key (key)}
228
+ {@const tag = PipelineTag[key]}
229
+ {@const label = pipelineTagLabel[tag]}
230
+ {@const option = select.getOption(tag)}
231
+ <div {...option} class="group block w-full p-1 text-sm dark:text-white">
232
+ <div
233
+ class="rounded-md py-1.5 pr-1 pl-2 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
234
+ >
235
+ <span>
236
+ {label}
237
+ </span>
238
+ </div>
239
+ </div>
240
+ {/each}
241
+ </div>
242
+ {/snippet}
243
+ </Select>
244
+ </div>
245
+
246
  {#if message}
247
  <div
248
  class={[
src/lib/components/inference-playground/message.svelte CHANGED
@@ -8,6 +8,7 @@
8
  import { fade } from "svelte/transition";
9
  import IconImage from "~icons/carbon/image-reference";
10
  import IconMaximize from "~icons/carbon/maximize";
 
11
  import ImgPreview from "./img-preview.svelte";
12
 
13
  type Props = {
@@ -16,9 +17,11 @@
16
  loading?: boolean;
17
  autofocus?: boolean;
18
  onDelete?: () => void;
 
 
19
  };
20
 
21
- let { message = $bindable(), conversation, loading, autofocus, onDelete }: Props = $props();
22
 
23
  let element = $state<HTMLTextAreaElement>();
24
  new TextareaAutosize({
@@ -48,6 +51,11 @@
48
  });
49
 
50
  let previewImg = $state<string>();
 
 
 
 
 
51
  </script>
52
 
53
  <div
@@ -104,6 +112,25 @@
104
  </Tooltip>
105
  {/if}
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  <Tooltip>
108
  {#snippet trigger(tooltip)}
109
  <button
 
8
  import { fade } from "svelte/transition";
9
  import IconImage from "~icons/carbon/image-reference";
10
  import IconMaximize from "~icons/carbon/maximize";
11
+ import IconCustom from "../icon-custom.svelte";
12
  import ImgPreview from "./img-preview.svelte";
13
 
14
  type Props = {
 
17
  loading?: boolean;
18
  autofocus?: boolean;
19
  onDelete?: () => void;
20
+ onRegen?: () => void;
21
+ isLast?: boolean;
22
  };
23
 
24
+ let { message = $bindable(), conversation, loading, autofocus, onDelete, onRegen, isLast }: Props = $props();
25
 
26
  let element = $state<HTMLTextAreaElement>();
27
  new TextareaAutosize({
 
51
  });
52
 
53
  let previewImg = $state<string>();
54
+
55
+ const regenLabel = $derived.by(() => {
56
+ if (message.role === "assistant") return "Regenerate";
57
+ return isLast ? "Generate from here" : "Regenerate from here";
58
+ });
59
  </script>
60
 
61
  <div
 
112
  </Tooltip>
113
  {/if}
114
 
115
+ <Tooltip>
116
+ {#snippet trigger(tooltip)}
117
+ <button
118
+ tabindex="0"
119
+ onclick={onRegen}
120
+ type="button"
121
+ class="mt-1.5 -mr-2 grid size-8 place-items-center rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-900
122
+ group-focus-within/message:visible group-hover/message:visible hover:bg-gray-100
123
+ hover:text-blue-700 focus:z-10 focus:ring-4
124
+ focus:ring-gray-100 focus:outline-hidden sm:invisible dark:border-gray-600 dark:bg-gray-800
125
+ dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
126
+ {...tooltip.trigger}
127
+ >
128
+ <IconCustom icon="regen" />
129
+ </button>
130
+ {/snippet}
131
+ {regenLabel}
132
+ </Tooltip>
133
+
134
  <Tooltip>
135
  {#snippet trigger(tooltip)}
136
  <button
src/lib/components/inference-playground/playground.svelte CHANGED
@@ -1,25 +1,16 @@
1
  <script lang="ts">
2
  import { observe, observed, ObservedElements } from "$lib/actions/observe.svelte.js";
3
- import { AbortManager } from "$lib/spells/abort-manager.svelte.js";
4
  import { models } from "$lib/state/models.svelte.js";
5
  import { session } from "$lib/state/session.svelte.js";
6
  import { token } from "$lib/state/token.svelte.js";
7
- import { type ConversationMessage, type Model, type Project } from "$lib/types.js";
8
- import { isMac } from "$lib/utils/platform.js";
 
9
  import { watch } from "runed";
10
  import typia from "typia";
11
- import IconExternal from "~icons/carbon/arrow-up-right";
12
- import IconCode from "~icons/carbon/code";
13
- import IconCompare from "~icons/carbon/compare";
14
- import IconInfo from "~icons/carbon/information";
15
- import IconSettings from "~icons/carbon/settings";
16
- import IconShare from "~icons/carbon/share";
17
- import IconWaterfall from "~icons/carbon/chart-waterfall";
18
  import { default as IconDelete } from "~icons/carbon/trash-can";
19
- import { showQuotaModal } from "../quota-modal.svelte";
20
  import { showShareModal } from "../share-modal.svelte";
21
  import Toaster from "../toaster.svelte";
22
- import { addToast } from "../toaster.svelte.js";
23
  import Tooltip from "../tooltip.svelte";
24
  import PlaygroundConversationHeader from "./conversation-header.svelte";
25
  import PlaygroundConversation from "./conversation.svelte";
@@ -28,32 +19,39 @@
28
  import ModelSelectorModal from "./model-selector-modal.svelte";
29
  import ModelSelector from "./model-selector.svelte";
30
  import ProjectSelect from "./project-select.svelte";
31
- import { getTokens, handleNonStreamingResponse, handleStreamingResponse, isSystemPromptSupported } from "./utils.js";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
34
 
35
  let viewCode = $state(false);
36
  let viewSettings = $state(false);
37
- let loading = $state(false);
38
 
39
- const abortManager = new AbortManager();
40
  let selectCompareModelOpen = $state(false);
41
 
42
- interface GenerationStatistics {
43
- latency: number;
44
- generatedTokensCount: number;
45
- }
46
- let generationStats = $state(
47
- session.project.conversations.map(_ => ({ latency: 0, generatedTokensCount: 0 })) as
48
- | [GenerationStatistics]
49
- | [GenerationStatistics, GenerationStatistics]
50
- );
51
-
52
  watch(
53
  () => $state.snapshot(session.project),
54
  () => {
55
  session.project.conversations.forEach(async (c, i) => {
56
- generationStats[i] = { latency: 0, ...generationStats[i], generatedTokensCount: await getTokens(c) };
 
 
 
 
57
  });
58
  }
59
  );
@@ -74,99 +72,15 @@
74
  if (typia.is<Project["conversations"]>(c)) session.project.conversations = c;
75
  }
76
 
77
- async function runInference(conversationIdx: number) {
78
- const conversation = session.project.conversations[conversationIdx];
79
- if (!conversation) return;
80
-
81
- const startTime = performance.now();
82
-
83
- if (conversation.streaming) {
84
- let addedMessage = false;
85
- let streamingMessage = $state({ role: "assistant", content: "" });
86
-
87
- await handleStreamingResponse(
88
- conversation,
89
- content => {
90
- if (!streamingMessage) return;
91
- streamingMessage.content = content;
92
- if (!addedMessage) {
93
- conversation.messages = [...conversation.messages, streamingMessage];
94
- addedMessage = true;
95
- }
96
- },
97
- abortManager.createController()
98
- );
99
- } else {
100
- const { message: newMessage, completion_tokens: newTokensCount } = await handleNonStreamingResponse(conversation);
101
- conversation.messages = [...conversation.messages, newMessage];
102
- const c = generationStats[conversationIdx];
103
- if (c) c.generatedTokensCount += newTokensCount;
104
- }
105
-
106
- const endTime = performance.now();
107
- const c = generationStats[conversationIdx];
108
- if (c) c.latency = Math.round(endTime - startTime);
109
- }
110
-
111
- async function submit() {
112
- if (!token.value) {
113
- token.showModal = true;
114
- return;
115
- }
116
-
117
- for (const [idx, conversation] of session.project.conversations.entries()) {
118
- if (conversation.messages.at(-1)?.role !== "assistant") continue;
119
- let prefix = "";
120
- if (session.project.conversations.length === 2) {
121
- prefix = `Error on ${idx === 0 ? "left" : "right"} conversation. `;
122
- }
123
- return addToast({
124
- title: "Failed to run inference",
125
- description: `${prefix}Messages must alternate between user/assistant roles.`,
126
- variant: "error",
127
- });
128
- }
129
-
130
- (document.activeElement as HTMLElement).blur();
131
- loading = true;
132
-
133
- try {
134
- const promises = session.project.conversations.map((_, idx) => runInference(idx));
135
- await Promise.all(promises);
136
- } catch (error) {
137
- for (const conversation of session.project.conversations) {
138
- if (conversation.messages.at(-1)?.role === "assistant" && !conversation.messages.at(-1)?.content?.trim()) {
139
- conversation.messages.pop();
140
- conversation.messages = [...conversation.messages];
141
- }
142
- session.$ = session.$;
143
- }
144
-
145
- if (error instanceof Error) {
146
- const msg = error.message;
147
- if (msg.toLowerCase().includes("montly") || msg.toLowerCase().includes("pro")) {
148
- showQuotaModal();
149
- }
150
-
151
- if (error.message.includes("token seems invalid")) {
152
- token.reset();
153
- }
154
-
155
- if (error.name !== "AbortError") {
156
- addToast({ title: "Error", description: error.message, variant: "error" });
157
- }
158
- } else {
159
- addToast({ title: "Error", description: "An unknown error occurred", variant: "error" });
160
- }
161
- } finally {
162
- loading = false;
163
- abortManager.clear();
164
- }
165
- }
166
-
167
  function onKeydown(event: KeyboardEvent) {
168
  if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
169
- submit();
 
 
 
 
 
 
170
  }
171
  }
172
 
@@ -191,14 +105,14 @@
191
  }
192
  const newConversation = { ...JSON.parse(JSON.stringify(session.project.conversations[0])), model };
193
  session.project.conversations = [...session.project.conversations, newConversation];
194
- generationStats = [generationStats[0], { latency: 0, generatedTokensCount: 0 }];
195
  }
196
 
197
  function removeCompareModal(conversationIdx: number) {
198
  session.project.conversations.splice(conversationIdx, 1)[0];
199
  session.$ = session.$;
200
- generationStats.splice(conversationIdx, 1)[0];
201
- generationStats = generationStats;
202
  }
203
  </script>
204
 
@@ -307,8 +221,7 @@
307
  <div
308
  class="pointer-events-none absolute inset-0 flex flex-1 shrink-0 items-center justify-around gap-x-8 text-center text-sm text-gray-500 max-xl:hidden"
309
  >
310
- {#each generationStats as { latency, generatedTokensCount }, index}
311
- {@const isLast = index === generationStats.length - 1}
312
  {@const baLeft = observed["bottom-actions"].rect.left}
313
  {@const tceRight = observed["token-count-end"].offset.right}
314
  <span
@@ -328,39 +241,115 @@
328
  <IconCode />
329
  {!viewCode ? "View Code" : "Hide Code"}
330
  </button>
331
- <button
332
- onclick={() => {
333
- viewCode = false;
334
- loading ? abortManager.abortAll() : submit();
335
- }}
336
- type="button"
337
- class="flex h-[39px] w-24 items-center justify-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium text-white focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:border-gray-700 dark:focus:ring-gray-700 {loading
338
- ? 'bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700'
339
- : 'bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700'}"
340
- >
341
- {#if loading}
342
- <div class="flex flex-none items-center gap-[3px]">
343
- <span class="mr-2">
344
- {#if session.project.conversations[0]?.streaming || session.project.conversations[1]?.streaming}
345
- Stop
346
- {:else}
347
- Cancel
348
- {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  </span>
350
- {#each { length: 3 } as _, i}
351
- <div
352
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
353
- style="animation-delay: {(i + 1) * 0.25}s;"
354
- ></div>
355
- {/each}
356
- </div>
357
- {:else}
358
- Run <span
359
- class="inline-flex gap-0.5 rounded-sm border border-white/20 bg-white/10 px-0.5 text-xs text-white/70"
360
- >{isMac() ? "⌘" : "Ctrl"}<span class="translate-y-px">↵</span></span
361
  >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  {/if}
363
- </button>
364
  </div>
365
  </div>
366
  </div>
@@ -370,7 +359,7 @@
370
  <div class={[viewSettings && "max-md:fixed max-md:inset-0 max-md:bottom-20 max-md:backdrop-blur-lg"]}>
371
  <div
372
  class={[
373
- "flex h-full flex-col p-3 max-md:absolute max-md:inset-x-0 max-md:bottom-0",
374
  viewSettings ? "max-md:fixed" : "max-md:hidden",
375
  ]}
376
  >
@@ -387,15 +376,17 @@
387
  <IconCompare />
388
  Compare
389
  </button>
390
- <a
391
- href="https://huggingface.co/{session.project.conversations[0]?.model.id}?inference_provider={session
392
- .project.conversations[0]?.provider}"
393
- target="_blank"
394
- class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
395
- >
396
- <IconExternal class="text-2xs" />
397
- Model page
398
- </a>
 
 
399
  </div>
400
  </div>
401
 
 
1
  <script lang="ts">
2
  import { observe, observed, ObservedElements } from "$lib/actions/observe.svelte.js";
 
3
  import { models } from "$lib/state/models.svelte.js";
4
  import { session } from "$lib/state/session.svelte.js";
5
  import { token } from "$lib/state/token.svelte.js";
6
+ import { isConversationWithHFModel, type ConversationMessage, type Model, type Project } from "$lib/types.js";
7
+ import { cmdOrCtrl, optOrAlt } from "$lib/utils/platform.js";
8
+ import { Popover } from "melt/components";
9
  import { watch } from "runed";
10
  import typia from "typia";
 
 
 
 
 
 
 
11
  import { default as IconDelete } from "~icons/carbon/trash-can";
 
12
  import { showShareModal } from "../share-modal.svelte";
13
  import Toaster from "../toaster.svelte";
 
14
  import Tooltip from "../tooltip.svelte";
15
  import PlaygroundConversationHeader from "./conversation-header.svelte";
16
  import PlaygroundConversation from "./conversation.svelte";
 
19
  import ModelSelectorModal from "./model-selector-modal.svelte";
20
  import ModelSelector from "./model-selector.svelte";
21
  import ProjectSelect from "./project-select.svelte";
22
+ import { getTokens, isSystemPromptSupported } from "./utils.js";
23
+
24
+ import { iterate } from "$lib/utils/array.js";
25
+ import IconChatLeft from "~icons/carbon/align-box-bottom-left";
26
+ import IconChatRight from "~icons/carbon/align-box-bottom-right";
27
+ import IconExternal from "~icons/carbon/arrow-up-right";
28
+ import IconWaterfall from "~icons/carbon/chart-waterfall";
29
+ import IconChevronDown from "~icons/carbon/chevron-down";
30
+ import IconCode from "~icons/carbon/code";
31
+ import IconCompare from "~icons/carbon/compare";
32
+ import IconInfo from "~icons/carbon/information";
33
+ import IconSettings from "~icons/carbon/settings";
34
+ import IconShare from "~icons/carbon/share";
35
+
36
+ const multiple = $derived(session.project.conversations.length > 1);
37
 
38
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
39
 
40
  let viewCode = $state(false);
41
  let viewSettings = $state(false);
42
+ const loading = $derived(session.generating);
43
 
 
44
  let selectCompareModelOpen = $state(false);
45
 
 
 
 
 
 
 
 
 
 
 
46
  watch(
47
  () => $state.snapshot(session.project),
48
  () => {
49
  session.project.conversations.forEach(async (c, i) => {
50
+ session.generationStats[i] = {
51
+ latency: 0,
52
+ ...session.generationStats[i],
53
+ generatedTokensCount: await getTokens(c),
54
+ };
55
  });
56
  }
57
  );
 
72
  if (typia.is<Project["conversations"]>(c)) session.project.conversations = c;
73
  }
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  function onKeydown(event: KeyboardEvent) {
76
  if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
77
+ session.run();
78
+ }
79
+ if ((event.ctrlKey || event.metaKey) && event.altKey && event.key === "l") {
80
+ session.run("left");
81
+ }
82
+ if ((event.ctrlKey || event.metaKey) && event.altKey && event.key === "r") {
83
+ session.run("right");
84
  }
85
  }
86
 
 
105
  }
106
  const newConversation = { ...JSON.parse(JSON.stringify(session.project.conversations[0])), model };
107
  session.project.conversations = [...session.project.conversations, newConversation];
108
+ session.generationStats = [session.generationStats[0], { latency: 0, generatedTokensCount: 0 }];
109
  }
110
 
111
  function removeCompareModal(conversationIdx: number) {
112
  session.project.conversations.splice(conversationIdx, 1)[0];
113
  session.$ = session.$;
114
+ session.generationStats.splice(conversationIdx, 1)[0];
115
+ session.generationStats = session.generationStats;
116
  }
117
  </script>
118
 
 
221
  <div
222
  class="pointer-events-none absolute inset-0 flex flex-1 shrink-0 items-center justify-around gap-x-8 text-center text-sm text-gray-500 max-xl:hidden"
223
  >
224
+ {#each iterate(session.generationStats) as [{ latency, generatedTokensCount }, isLast]}
 
225
  {@const baLeft = observed["bottom-actions"].rect.left}
226
  {@const tceRight = observed["token-count-end"].offset.right}
227
  <span
 
241
  <IconCode />
242
  {!viewCode ? "View Code" : "Hide Code"}
243
  </button>
244
+ <div class="flex">
245
+ <button
246
+ onclick={() => {
247
+ viewCode = false;
248
+ session.runOrStop();
249
+ }}
250
+ type="button"
251
+ class={[
252
+ "flex h-[39px] items-center justify-center gap-2 rounded-l-lg px-3.5 py-2.5 text-sm font-medium text-white focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:focus:ring-gray-700",
253
+ multiple ? "rounded-l-lg" : "rounded-lg",
254
+ loading && "bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700",
255
+ !loading && "bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700",
256
+ ]}
257
+ >
258
+ {#if loading}
259
+ <div class="flex flex-none items-center gap-[3px]">
260
+ <span class="mr-2">
261
+ {#if session.project.conversations[0]?.streaming || session.project.conversations[1]?.streaming}
262
+ Stop
263
+ {:else}
264
+ Cancel
265
+ {/if}
266
+ </span>
267
+ {#each { length: 3 } as _, i}
268
+ <div
269
+ class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
270
+ style="animation-delay: {(i + 1) * 0.25}s;"
271
+ ></div>
272
+ {/each}
273
+ </div>
274
+ {:else}
275
+ {multiple ? "Run all" : "Run"}
276
+ <span
277
+ class="inline-flex gap-0.5 rounded-sm border border-white/20 bg-white/10 px-0.5 text-xs text-white/70"
278
+ >
279
+ {cmdOrCtrl}<span class="translate-y-px">↵</span>
280
  </span>
281
+ {/if}
282
+ </button>
283
+ {#if multiple}
284
+ <div class="w-[1px] bg-gray-800" aria-hidden="true"></div>
285
+ <Popover
286
+ open
287
+ floatingConfig={{
288
+ computePosition: {
289
+ placement: "top-end",
290
+ },
291
+ }}
292
  >
293
+ {#snippet children(popover)}
294
+ <button
295
+ class={[
296
+ "flex items-center justify-center gap-2 rounded-r-lg px-1.5 text-sm font-medium text-white",
297
+ "focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:focus:ring-gray-700",
298
+ loading && "bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700",
299
+ !loading && "bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700",
300
+ ]}
301
+ {...popover.trigger}
302
+ disabled={loading}
303
+ >
304
+ <IconChevronDown />
305
+ </button>
306
+ <div
307
+ class={["flex-col rounded-lg bg-white px-2 py-1 shadow dark:bg-gray-800", popover.open && "flex"]}
308
+ {...popover.content}
309
+ >
310
+ <button
311
+ class="group py-1 text-sm"
312
+ onclick={() => {
313
+ viewCode = false;
314
+ session.runOrStop("left");
315
+ popover.open = false;
316
+ }}
317
+ >
318
+ <div
319
+ class="flex items-center gap-2 rounded p-1 group-hover:bg-gray-200 dark:group-hover:bg-gray-700"
320
+ >
321
+ <IconChatLeft />
322
+ <span class="mr-2">Only run left conversation</span>
323
+ <span class="ml-auto rounded-sm border border-white/20 bg-gray-500/10 px-0.5 text-xs">
324
+ {cmdOrCtrl}
325
+ {optOrAlt} L
326
+ </span>
327
+ </div>
328
+ </button>
329
+ <button
330
+ class="group py-1 text-sm"
331
+ onclick={() => {
332
+ viewCode = false;
333
+ session.runOrStop("right");
334
+ popover.open = false;
335
+ }}
336
+ >
337
+ <div
338
+ class="flex items-center gap-2 rounded p-1 group-hover:bg-gray-200 dark:group-hover:bg-gray-700"
339
+ >
340
+ <IconChatRight />
341
+ <span class="mr-2">Only run right conversation</span>
342
+ <span class="ml-auto rounded-sm border border-white/20 bg-gray-500/10 px-0.5 text-xs">
343
+ {cmdOrCtrl}
344
+ {optOrAlt} R
345
+ </span>
346
+ </div>
347
+ </button>
348
+ </div>
349
+ {/snippet}
350
+ </Popover>
351
  {/if}
352
+ </div>
353
  </div>
354
  </div>
355
  </div>
 
359
  <div class={[viewSettings && "max-md:fixed max-md:inset-0 max-md:bottom-20 max-md:backdrop-blur-lg"]}>
360
  <div
361
  class={[
362
+ "flex h-full flex-col p-3 max-md:absolute max-md:inset-x-0 max-md:bottom-0",
363
  viewSettings ? "max-md:fixed" : "max-md:hidden",
364
  ]}
365
  >
 
376
  <IconCompare />
377
  Compare
378
  </button>
379
+ {#if isConversationWithHFModel(session.project.conversations[0])}
380
+ <a
381
+ href="https://huggingface.co/{session.project.conversations[0]?.model.id}?inference_provider={session
382
+ .project.conversations[0]?.provider}"
383
+ target="_blank"
384
+ class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
385
+ >
386
+ <IconExternal class="text-2xs" />
387
+ Model page
388
+ </a>
389
+ {/if}
390
  </div>
391
  </div>
392
 
src/lib/components/inference-playground/utils.ts CHANGED
@@ -48,31 +48,14 @@ type OpenAICompletionMetadata = {
48
 
49
  type CompletionMetadata = HFCompletionMetadata | OpenAICompletionMetadata;
50
 
51
- function parseOpenAIMessages(
52
- messages: ConversationMessage[],
53
- systemMessage?: ConversationMessage
54
- ): OpenAI.ChatCompletionMessageParam[] {
55
- const parsedMessages: OpenAI.ChatCompletionMessageParam[] = [];
56
-
57
- if (systemMessage?.content) {
58
- parsedMessages.push({
59
- role: "system",
60
- content: systemMessage.content,
61
- });
62
- }
63
-
64
- return [
65
- ...parsedMessages,
66
- ...messages.map(msg => ({
67
- role: msg.role === "assistant" ? ("assistant" as const) : ("user" as const),
68
- content: msg.content || "",
69
- })),
70
- ];
71
- }
72
-
73
  function getCompletionMetadata(conversation: Conversation, signal?: AbortSignal): CompletionMetadata {
74
  const { model, systemMessage } = conversation;
75
 
 
 
 
 
 
76
  // Handle OpenAI-compatible models
77
  if (isCustomModel(model)) {
78
  const openai = new OpenAI({
@@ -88,17 +71,14 @@ function getCompletionMetadata(conversation: Conversation, signal?: AbortSignal)
88
  type: "openai",
89
  client: openai,
90
  args: {
91
- messages: parseOpenAIMessages(conversation.messages, systemMessage),
 
92
  model: model.id,
93
  },
94
  };
95
  }
96
 
97
  // Handle HuggingFace models
98
- const messages = [
99
- ...(isSystemPromptSupported(model) && systemMessage.content?.length ? [systemMessage] : []),
100
- ...conversation.messages,
101
- ];
102
 
103
  return {
104
  type: "huggingface",
 
48
 
49
  type CompletionMetadata = HFCompletionMetadata | OpenAICompletionMetadata;
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  function getCompletionMetadata(conversation: Conversation, signal?: AbortSignal): CompletionMetadata {
52
  const { model, systemMessage } = conversation;
53
 
54
+ const messages = [
55
+ ...(isSystemPromptSupported(model) && systemMessage.content?.length ? [systemMessage] : []),
56
+ ...conversation.messages,
57
+ ];
58
+
59
  // Handle OpenAI-compatible models
60
  if (isCustomModel(model)) {
61
  const openai = new OpenAI({
 
71
  type: "openai",
72
  client: openai,
73
  args: {
74
+ messages: messages.map(parseMessage) as OpenAI.ChatCompletionMessageParam[],
75
+ ...conversation.config,
76
  model: model.id,
77
  },
78
  };
79
  }
80
 
81
  // Handle HuggingFace models
 
 
 
 
82
 
83
  return {
84
  type: "huggingface",
src/lib/spells/abort-manager.svelte.ts CHANGED
@@ -1,4 +1,5 @@
1
  import { onDestroy } from "svelte";
 
2
 
3
  /**
4
  * Manages abort controllers, and aborts them when the component unmounts.
@@ -7,9 +8,17 @@ export class AbortManager {
7
  private controllers: AbortController[] = [];
8
 
9
  constructor() {
10
- onDestroy(() => this.abortAll());
11
  }
12
 
 
 
 
 
 
 
 
 
13
  /**
14
  * Creates a new abort controller and adds it to the manager.
15
  */
 
1
  import { onDestroy } from "svelte";
2
+ import { createInit } from "./create-init.svelte";
3
 
4
  /**
5
  * Manages abort controllers, and aborts them when the component unmounts.
 
8
  private controllers: AbortController[] = [];
9
 
10
  constructor() {
11
+ this.init();
12
  }
13
 
14
+ init = createInit(() => {
15
+ try {
16
+ onDestroy(() => this.abortAll());
17
+ } catch {
18
+ // no-op
19
+ }
20
+ });
21
+
22
  /**
23
  * Creates a new abort controller and adds it to the manager.
24
  */
src/lib/spells/create-init.svelte.ts CHANGED
@@ -1,14 +1,18 @@
1
  export function createInit(cb: () => void) {
2
  let called = $state(false);
3
 
4
- return {
5
- fn: () => {
6
- if (called) return;
7
- called = true;
8
- cb();
9
- },
10
- get called() {
11
- return called;
 
 
 
 
12
  },
13
- };
14
  }
 
1
  export function createInit(cb: () => void) {
2
  let called = $state(false);
3
 
4
+ function init() {
5
+ if (called) return;
6
+ called = true;
7
+ cb();
8
+ }
9
+
10
+ return Object.defineProperties(init, {
11
+ called: {
12
+ get() {
13
+ return called;
14
+ },
15
+ enumerable: true,
16
  },
17
+ }) as typeof init & { readonly called: boolean };
18
  }
src/lib/state/session.svelte.ts CHANGED
@@ -1,4 +1,7 @@
1
  import { defaultGenerationConfig } from "$lib/components/inference-playground/generation-config-settings.js";
 
 
 
2
  import { createInit } from "$lib/spells/create-init.svelte.js";
3
  import {
4
  PipelineTag,
@@ -13,9 +16,18 @@ import { safeParse } from "$lib/utils/json.js";
13
  import typia from "typia";
14
  import { models } from "./models.svelte";
15
  import { checkpoints } from "./checkpoints.svelte";
 
 
 
 
16
 
17
  const LOCAL_STORAGE_KEY = "hf_inference_playground_session";
18
 
 
 
 
 
 
19
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
20
  const systemMessage: ConversationMessage = {
21
  role: "system",
@@ -59,6 +71,13 @@ function getDefaults() {
59
  class SessionState {
60
  #value = $state<Session>({} as Session);
61
 
 
 
 
 
 
 
 
62
  // Call once in layout
63
  init = createInit(() => {
64
  const { defaultConversation, defaultProject } = getDefaults();
@@ -111,6 +130,10 @@ class SessionState {
111
  }
112
 
113
  this.$ = savedSession;
 
 
 
 
114
  });
115
 
116
  constructor() {
@@ -190,6 +213,120 @@ class SessionState {
190
  const projects = this.$.projects.map(p => (p.id === np.id ? np : p));
191
  this.#setAnySession({ ...this.$, projects });
192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  }
194
 
195
  export const session = new SessionState();
 
1
  import { defaultGenerationConfig } from "$lib/components/inference-playground/generation-config-settings.js";
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore - Svelte imports are broken in TS files
4
+ import { showQuotaModal } from "$lib/components/quota-modal.svelte";
5
  import { createInit } from "$lib/spells/create-init.svelte.js";
6
  import {
7
  PipelineTag,
 
16
  import typia from "typia";
17
  import { models } from "./models.svelte";
18
  import { checkpoints } from "./checkpoints.svelte";
19
+ import { handleNonStreamingResponse, handleStreamingResponse } from "$lib/components/inference-playground/utils.js";
20
+ import { AbortManager } from "$lib/spells/abort-manager.svelte";
21
+ import { addToast } from "$lib/components/toaster.svelte.js";
22
+ import { token } from "./token.svelte";
23
 
24
  const LOCAL_STORAGE_KEY = "hf_inference_playground_session";
25
 
26
+ interface GenerationStatistics {
27
+ latency: number;
28
+ generatedTokensCount: number;
29
+ }
30
+
31
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
32
  const systemMessage: ConversationMessage = {
33
  role: "system",
 
71
  class SessionState {
72
  #value = $state<Session>({} as Session);
73
 
74
+ generationStats = $state([{ latency: 0, generatedTokensCount: 0 }] as
75
+ | [GenerationStatistics]
76
+ | [GenerationStatistics, GenerationStatistics]);
77
+ generating = $state(false);
78
+
79
+ #abortManager = new AbortManager();
80
+
81
  // Call once in layout
82
  init = createInit(() => {
83
  const { defaultConversation, defaultProject } = getDefaults();
 
130
  }
131
 
132
  this.$ = savedSession;
133
+ session.generationStats = session.project.conversations.map(_ => ({ latency: 0, generatedTokensCount: 0 })) as
134
+ | [GenerationStatistics]
135
+ | [GenerationStatistics, GenerationStatistics];
136
+ this.#abortManager.init();
137
  });
138
 
139
  constructor() {
 
213
  const projects = this.$.projects.map(p => (p.id === np.id ? np : p));
214
  this.#setAnySession({ ...this.$, projects });
215
  }
216
+
217
+ async #runInference(conversation: Conversation) {
218
+ const idx = session.project.conversations.indexOf(conversation);
219
+
220
+ const startTime = performance.now();
221
+
222
+ if (conversation.streaming) {
223
+ let addedMessage = false;
224
+ const streamingMessage = $state({ role: "assistant", content: "" });
225
+
226
+ await handleStreamingResponse(
227
+ conversation,
228
+ content => {
229
+ if (!streamingMessage) return;
230
+ streamingMessage.content = content;
231
+ if (!addedMessage) {
232
+ conversation.messages = [...conversation.messages, streamingMessage];
233
+ addedMessage = true;
234
+ }
235
+ },
236
+ this.#abortManager.createController()
237
+ );
238
+ } else {
239
+ const { message: newMessage, completion_tokens: newTokensCount } = await handleNonStreamingResponse(conversation);
240
+ conversation.messages = [...conversation.messages, newMessage];
241
+ const c = session.generationStats[idx];
242
+ if (c) c.generatedTokensCount += newTokensCount;
243
+ }
244
+
245
+ const endTime = performance.now();
246
+ const c = session.generationStats[idx];
247
+ if (c) c.latency = Math.round(endTime - startTime);
248
+ }
249
+
250
+ async run(conv: "left" | "right" | "both" | Conversation = "both") {
251
+ if (!token.value) {
252
+ token.showModal = true;
253
+ return;
254
+ }
255
+
256
+ const conversations = (() => {
257
+ if (typeof conv === "string") {
258
+ return session.project.conversations.filter((_, idx) => {
259
+ return conv === "both" || (conv === "left" ? idx === 0 : idx === 1);
260
+ });
261
+ }
262
+ return [conv];
263
+ })();
264
+
265
+ for (let idx = 0; idx < conversations.length; idx++) {
266
+ const conversation = conversations[idx];
267
+ if (!conversation || conversation.messages.at(-1)?.role !== "assistant") continue;
268
+
269
+ let prefix = "";
270
+ if (session.project.conversations.length === 2) {
271
+ prefix = `Error on ${idx === 0 ? "left" : "right"} conversation. `;
272
+ }
273
+ return addToast({
274
+ title: "Failed to run inference",
275
+ description: `${prefix}Messages must alternate between user/assistant roles.`,
276
+ variant: "error",
277
+ });
278
+ }
279
+
280
+ (document.activeElement as HTMLElement).blur();
281
+ session.generating = true;
282
+
283
+ try {
284
+ const promises = conversations.map(c => this.#runInference(c));
285
+ await Promise.all(promises);
286
+ } catch (error) {
287
+ for (const conversation of conversations) {
288
+ if (conversation.messages.at(-1)?.role === "assistant" && !conversation.messages.at(-1)?.content?.trim()) {
289
+ conversation.messages.pop();
290
+ conversation.messages = [...conversation.messages];
291
+ }
292
+ // eslint-disable-next-line no-self-assign
293
+ session.$ = session.$;
294
+ }
295
+
296
+ if (error instanceof Error) {
297
+ const msg = error.message;
298
+ if (msg.toLowerCase().includes("montly") || msg.toLowerCase().includes("pro")) {
299
+ showQuotaModal();
300
+ }
301
+
302
+ if (error.message.includes("token seems invalid")) {
303
+ token.reset();
304
+ }
305
+
306
+ if (error.name !== "AbortError") {
307
+ addToast({ title: "Error", description: error.message, variant: "error" });
308
+ }
309
+ } else {
310
+ addToast({ title: "Error", description: "An unknown error occurred", variant: "error" });
311
+ }
312
+ } finally {
313
+ session.generating = false;
314
+ this.#abortManager.clear();
315
+ }
316
+ }
317
+
318
+ stopGenerating = () => {
319
+ this.#abortManager.abortAll();
320
+ session.generating = false;
321
+ };
322
+
323
+ runOrStop = (c?: Parameters<typeof this.run>[0]) => {
324
+ if (session.generating) {
325
+ this.stopGenerating();
326
+ } else {
327
+ this.run(c);
328
+ }
329
+ };
330
  }
331
 
332
  export const session = new SessionState();
src/lib/types.ts CHANGED
@@ -71,6 +71,8 @@ export type CustomModel = {
71
  _id: string;
72
  endpointUrl: string;
73
  accessToken?: string;
 
 
74
  };
75
 
76
  export type Config = {
@@ -196,6 +198,11 @@ export enum PipelineTag {
196
  ImageTextToText = "image-text-to-text",
197
  }
198
 
 
 
 
 
 
199
  export type MaybeGetter<T> = T | (() => T);
200
 
201
  export type ValueOf<T> = T[keyof T];
 
71
  _id: string;
72
  endpointUrl: string;
73
  accessToken?: string;
74
+ /** @default "text-generation" */
75
+ pipeline_tag?: PipelineTag;
76
  };
77
 
78
  export type Config = {
 
198
  ImageTextToText = "image-text-to-text",
199
  }
200
 
201
+ export const pipelineTagLabel: Record<PipelineTag, string> = {
202
+ [PipelineTag.TextGeneration]: "Text→Text",
203
+ [PipelineTag.ImageTextToText]: "Image+Text→Text",
204
+ };
205
+
206
  export type MaybeGetter<T> = T | (() => T);
207
 
208
  export type ValueOf<T> = T[keyof T];
src/lib/utils/array.ts CHANGED
@@ -10,3 +10,37 @@ export function randomPick<T>(arr: T[]): T | undefined {
10
  export function edit<T>(arr: T[], index: number, newValue: T): T[] {
11
  return arr.map((value, i) => (i === index ? newValue : value));
12
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  export function edit<T>(arr: T[], index: number, newValue: T): T[] {
11
  return arr.map((value, i) => (i === index ? newValue : value));
12
  }
13
+
14
+ type IterateReturn<T> = [
15
+ T,
16
+ {
17
+ isFirst: boolean;
18
+ isLast: boolean;
19
+ array: T[];
20
+ index: number;
21
+ length: number;
22
+ },
23
+ ];
24
+
25
+ /**
26
+ * Returns an an iterator that iterates over the given array.
27
+ * Each returned item contains helpful properties, such as
28
+ * `isFirst`, `isLast`, `array`, `index`, and `length`
29
+ *
30
+ * @param array The array to iterate over.
31
+ * @returns An iterator that iterates over the given array.
32
+ */
33
+ export function* iterate<T>(array: T[]): Generator<IterateReturn<T>> {
34
+ for (let i = 0; i < array.length; i++) {
35
+ yield [
36
+ array[i]!,
37
+ {
38
+ isFirst: i === 0,
39
+ isLast: i === array.length - 1,
40
+ array,
41
+ index: i,
42
+ length: array.length,
43
+ },
44
+ ];
45
+ }
46
+ }
src/lib/utils/platform.ts CHANGED
@@ -1,3 +1,6 @@
1
  export function isMac() {
2
  return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
3
  }
 
 
 
 
1
  export function isMac() {
2
  return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
3
  }
4
+
5
+ export const cmdOrCtrl = isMac() ? "⌘" : "Ctrl";
6
+ export const optOrAlt = isMac() ? "⌥" : "Alt";
src/routes/+layout.svelte CHANGED
@@ -12,7 +12,7 @@
12
  }
13
 
14
  let { children }: Props = $props();
15
- session.init.fn();
16
  </script>
17
 
18
  {@render children?.()}
 
12
  }
13
 
14
  let { children }: Props = $props();
15
+ session.init();
16
  </script>
17
 
18
  {@render children?.()}