Thomas G. Lopes victor HF Staff commited on
Commit
a88830a
·
unverified ·
1 Parent(s): 038941d

New input layout (#92)

Browse files

Co-authored-by: Victor Muštar <[email protected]>

e2e/home.test.ts CHANGED
@@ -30,7 +30,7 @@ test.describe.serial("Token Handling and Subsequent Tests", () => {
30
  });
31
 
32
  // Nested describe for tests that use the saved state
33
- test.describe("Tests requiring persisted token", () => {
34
  test.use({ storageState: STORAGE_STATE_FILE });
35
 
36
  test("can create a conversation with persisted token", async ({ page }) => {
@@ -67,7 +67,7 @@ test.describe.serial("Token Handling and Subsequent Tests", () => {
67
  const assistantMsg = "assistant message: hey";
68
 
69
  // Fill user message
70
- await page.getByRole("textbox", { name: "Enter user message" }).click();
71
  await page.getByRole("textbox", { name: "Enter user message" }).fill(userMsg);
72
  // Blur
73
  await page.locator(".relative > div:nth-child(2) > div").first().click();
 
30
  });
31
 
32
  // Nested describe for tests that use the saved state
33
+ test.describe.skip("Tests requiring persisted token", () => {
34
  test.use({ storageState: STORAGE_STATE_FILE });
35
 
36
  test("can create a conversation with persisted token", async ({ page }) => {
 
67
  const assistantMsg = "assistant message: hey";
68
 
69
  // Fill user message
70
+ await page.getByRole("textbox", { name: "Enter message" }).click();
71
  await page.getByRole("textbox", { name: "Enter user message" }).fill(userMsg);
72
  // Blur
73
  await page.locator(".relative > div:nth-child(2) > div").first().click();
package.json CHANGED
@@ -51,7 +51,7 @@
51
  "highlight.js": "^11.10.0",
52
  "jiti": "^2.4.2",
53
  "jsdom": "^26.0.0",
54
- "melt": "^0.30.1",
55
  "openai": "^4.90.0",
56
  "playwright": "^1.52.0",
57
  "postcss": "^8.4.38",
@@ -60,7 +60,7 @@
60
  "prettier-plugin-tailwindcss": "^0.6.11",
61
  "runed": "^0.25.0",
62
  "shiki": "^3.4.0",
63
- "svelte": "^5.34.3",
64
  "svelte-check": "^4.0.0",
65
  "tailwind-merge": "^3.0.2",
66
  "tailwindcss": "^4.0.9",
 
51
  "highlight.js": "^11.10.0",
52
  "jiti": "^2.4.2",
53
  "jsdom": "^26.0.0",
54
+ "melt": "^0.36.0",
55
  "openai": "^4.90.0",
56
  "playwright": "^1.52.0",
57
  "postcss": "^8.4.38",
 
60
  "prettier-plugin-tailwindcss": "^0.6.11",
61
  "runed": "^0.25.0",
62
  "shiki": "^3.4.0",
63
+ "svelte": "^5.34.8",
64
  "svelte-check": "^4.0.0",
65
  "tailwind-merge": "^3.0.2",
66
  "tailwindcss": "^4.0.9",
pnpm-lock.yaml CHANGED
@@ -13,7 +13,7 @@ importers:
13
  version: 2.0.3
14
  eslint-plugin-svelte:
15
  specifier: ^3.6.0
16
17
  remult:
18
  specifier: ^3.0.2
19
  version: 3.0.2
@@ -62,16 +62,16 @@ importers:
62
  version: 3.0.0
63
  '@sveltejs/adapter-auto':
64
  specifier: ^3.2.2
65
66
  '@sveltejs/adapter-node':
67
  specifier: ^5.2.0
68
69
  '@sveltejs/kit':
70
  specifier: ^2.5.27
71
72
  '@sveltejs/vite-plugin-svelte':
73
  specifier: ^4.0.0
74
75
  '@tailwindcss/container-queries':
76
  specifier: ^0.1.1
77
  version: 0.1.1([email protected])
@@ -83,7 +83,7 @@ importers:
83
  version: 6.6.3
84
  '@testing-library/svelte':
85
  specifier: ^5.2.4
86
87
  '@types/node':
88
  specifier: ^22.14.1
89
  version: 22.14.1
@@ -121,8 +121,8 @@ importers:
121
  specifier: ^26.0.0
122
  version: 26.1.0
123
  melt:
124
- specifier: ^0.30.1
125
- version: 0.30.1(@floating-ui/[email protected])([email protected].3)
126
  openai:
127
  specifier: ^4.90.0
128
  version: 4.90.0([email protected])
@@ -137,22 +137,22 @@ importers:
137
  version: 3.5.3
138
  prettier-plugin-svelte:
139
  specifier: ^3.4.0
140
- version: 3.4.0([email protected])([email protected].3)
141
  prettier-plugin-tailwindcss:
142
  specifier: ^0.6.11
143
144
  runed:
145
  specifier: ^0.25.0
146
- version: 0.25.0([email protected].3)
147
  shiki:
148
  specifier: ^3.4.0
149
  version: 3.4.0
150
  svelte:
151
- specifier: ^5.34.3
152
- version: 5.34.3
153
  svelte-check:
154
  specifier: ^4.0.0
155
156
  tailwind-merge:
157
  specifier: ^3.0.2
158
  version: 3.0.2
@@ -173,7 +173,7 @@ importers:
173
174
  unplugin-icons:
175
  specifier: ^22.1.0
176
- version: 22.1.0([email protected].3)
177
  vite:
178
  specifier: ^5.4.4
179
  version: 5.4.14(@types/[email protected])([email protected])
@@ -182,7 +182,7 @@ importers:
182
183
  vitest-browser-svelte:
184
  specifier: ^0.1.0
185
186
 
187
  packages:
188
 
@@ -2242,11 +2242,11 @@ packages:
2242
2243
  resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
2244
 
2245
- melt@0.30.1:
2246
- resolution: {integrity: sha512-Z3X3IMknWSbXFlzQA6On18kdGf1a+Kgqu/TxxvchjGGiS3RINd96PrlLU2Bl/SOxF+UWLLYmH1fohwiMz9UsQQ==}
2247
  peerDependencies:
2248
  '@floating-ui/dom': ^1.6.0
2249
- svelte: ^5.0.0
2250
 
2251
2252
  resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
@@ -2842,8 +2842,8 @@ packages:
2842
  svelte:
2843
  optional: true
2844
 
2845
2846
- resolution: {integrity: sha512-Y0QKP2rfWD+ARKe91c4JgZgc/nXa2BfOnVBUjYUMB819m7VyPszihkjdzXPIV0qlGRZYEukpgNq7hgbzTbopJw==}
2847
  engines: {node: '>=18'}
2848
 
2849
@@ -3922,22 +3922,22 @@ snapshots:
3922
  dependencies:
3923
  acorn: 8.14.0
3924
 
3925
3926
  dependencies:
3927
3928
  import-meta-resolve: 4.1.0
3929
 
3930
3931
  dependencies:
3932
  '@rollup/plugin-commonjs': 28.0.2([email protected])
3933
  '@rollup/plugin-json': 6.1.0([email protected])
3934
  '@rollup/plugin-node-resolve': 16.0.0([email protected])
3935
3936
  rollup: 4.34.9
3937
 
3938
3939
  dependencies:
3940
- '@sveltejs/vite-plugin-svelte': 4.0.4([email protected].3)([email protected](@types/[email protected])([email protected]))
3941
  '@types/cookie': 0.6.0
3942
  cookie: 0.6.0
3943
  devalue: 5.1.1
@@ -3949,26 +3949,26 @@ snapshots:
3949
  sade: 1.8.1
3950
  set-cookie-parser: 2.7.1
3951
  sirv: 3.0.1
3952
- svelte: 5.34.3
3953
  vite: 5.4.14(@types/[email protected])([email protected])
3954
 
3955
3956
  dependencies:
3957
- '@sveltejs/vite-plugin-svelte': 4.0.4([email protected].3)([email protected](@types/[email protected])([email protected]))
3958
  debug: 4.4.0
3959
- svelte: 5.34.3
3960
  vite: 5.4.14(@types/[email protected])([email protected])
3961
  transitivePeerDependencies:
3962
  - supports-color
3963
 
3964
3965
  dependencies:
3966
3967
  debug: 4.4.0
3968
  deepmerge: 4.3.1
3969
  kleur: 4.1.5
3970
  magic-string: 0.30.17
3971
- svelte: 5.34.3
3972
  vite: 5.4.14(@types/[email protected])([email protected])
3973
3974
  transitivePeerDependencies:
@@ -4061,10 +4061,10 @@ snapshots:
4061
  lodash: 4.17.21
4062
  redent: 3.0.0
4063
 
4064
4065
  dependencies:
4066
  '@testing-library/dom': 10.4.0
4067
- svelte: 5.34.3
4068
  optionalDependencies:
4069
  vite: 5.4.14(@types/[email protected])([email protected])
4070
@@ -4613,7 +4613,7 @@ snapshots:
4613
  optionalDependencies:
4614
  eslint-config-prettier: 10.1.1([email protected]([email protected]))
4615
 
4616
4617
  dependencies:
4618
  '@eslint-community/eslint-utils': 4.4.1([email protected]([email protected]))
4619
  '@jridgewell/sourcemap-codec': 1.5.0
@@ -4624,9 +4624,9 @@ snapshots:
4624
  postcss-load-config: 3.1.4([email protected])
4625
  postcss-safe-parser: 7.0.1([email protected])
4626
  semver: 7.7.1
4627
- svelte-eslint-parser: 1.2.0([email protected].3)
4628
  optionalDependencies:
4629
- svelte: 5.34.3
4630
  transitivePeerDependencies:
4631
  - ts-node
4632
 
@@ -5201,14 +5201,14 @@ snapshots:
5201
  unist-util-visit: 5.0.0
5202
  vfile: 6.0.3
5203
 
5204
- melt@0.30.1(@floating-ui/[email protected])([email protected].3):
5205
  dependencies:
5206
  '@floating-ui/dom': 1.6.13
5207
  dequal: 2.0.3
5208
  jest-axe: 9.0.0
5209
  nanoid: 5.1.5
5210
- runed: 0.23.4([email protected].3)
5211
- svelte: 5.34.3
5212
 
5213
5214
 
@@ -5467,16 +5467,16 @@ snapshots:
5467
  dependencies:
5468
  fast-diff: 1.3.0
5469
 
5470
5471
  dependencies:
5472
  prettier: 3.5.3
5473
- svelte: 5.34.3
5474
 
5475
5476
  dependencies:
5477
  prettier: 3.5.3
5478
  optionalDependencies:
5479
- prettier-plugin-svelte: 3.4.0([email protected])([email protected].3)
5480
 
5481
5482
 
@@ -5617,15 +5617,15 @@ snapshots:
5617
  dependencies:
5618
  queue-microtask: 1.2.3
5619
 
5620
5621
  dependencies:
5622
  esm-env: 1.2.2
5623
- svelte: 5.34.3
5624
 
5625
5626
  dependencies:
5627
  esm-env: 1.2.2
5628
- svelte: 5.34.3
5629
 
5630
5631
  dependencies:
@@ -5755,19 +5755,19 @@ snapshots:
5755
 
5756
5757
 
5758
5759
  dependencies:
5760
  '@jridgewell/trace-mapping': 0.3.25
5761
  chokidar: 4.0.3
5762
  fdir: 6.4.3([email protected])
5763
  picocolors: 1.1.1
5764
  sade: 1.8.1
5765
- svelte: 5.34.3
5766
  typescript: 5.8.2
5767
  transitivePeerDependencies:
5768
  - picomatch
5769
 
5770
5771
  dependencies:
5772
  eslint-scope: 8.3.0
5773
  eslint-visitor-keys: 4.2.0
@@ -5776,9 +5776,9 @@ snapshots:
5776
  postcss-scss: 4.0.9([email protected])
5777
  postcss-selector-parser: 7.1.0
5778
  optionalDependencies:
5779
- svelte: 5.34.3
5780
 
5781
5782
  dependencies:
5783
  '@ampproject/remapping': 2.3.0
5784
  '@jridgewell/sourcemap-codec': 1.5.0
@@ -5950,7 +5950,7 @@ snapshots:
5950
  unist-util-is: 6.0.0
5951
  unist-util-visit-parents: 6.0.1
5952
 
5953
5954
  dependencies:
5955
  '@antfu/install-pkg': 1.0.0
5956
  '@iconify/utils': 2.3.0
@@ -5958,7 +5958,7 @@ snapshots:
5958
  local-pkg: 1.1.1
5959
  unplugin: 2.2.0
5960
  optionalDependencies:
5961
- svelte: 5.34.3
5962
  transitivePeerDependencies:
5963
  - supports-color
5964
 
@@ -6034,10 +6034,10 @@ snapshots:
6034
  optionalDependencies:
6035
  vite: 5.4.14(@types/[email protected])([email protected])
6036
 
6037
6038
  dependencies:
6039
6040
- svelte: 5.34.3
6041
6042
 
6043
 
13
  version: 2.0.3
14
  eslint-plugin-svelte:
15
  specifier: ^3.6.0
16
17
  remult:
18
  specifier: ^3.0.2
19
  version: 3.0.2
 
62
  version: 3.0.0
63
  '@sveltejs/adapter-auto':
64
  specifier: ^3.2.2
65
66
  '@sveltejs/adapter-node':
67
  specifier: ^5.2.0
68
69
  '@sveltejs/kit':
70
  specifier: ^2.5.27
71
72
  '@sveltejs/vite-plugin-svelte':
73
  specifier: ^4.0.0
74
75
  '@tailwindcss/container-queries':
76
  specifier: ^0.1.1
77
  version: 0.1.1([email protected])
 
83
  version: 6.6.3
84
  '@testing-library/svelte':
85
  specifier: ^5.2.4
86
87
  '@types/node':
88
  specifier: ^22.14.1
89
  version: 22.14.1
 
121
  specifier: ^26.0.0
122
  version: 26.1.0
123
  melt:
124
+ specifier: ^0.36.0
125
+ version: 0.36.0(@floating-ui/[email protected])([email protected].8)
126
  openai:
127
  specifier: ^4.90.0
128
  version: 4.90.0([email protected])
 
137
  version: 3.5.3
138
  prettier-plugin-svelte:
139
  specifier: ^3.4.0
140
+ version: 3.4.0([email protected])([email protected].8)
141
  prettier-plugin-tailwindcss:
142
  specifier: ^0.6.11
143
144
  runed:
145
  specifier: ^0.25.0
146
+ version: 0.25.0([email protected].8)
147
  shiki:
148
  specifier: ^3.4.0
149
  version: 3.4.0
150
  svelte:
151
+ specifier: ^5.34.8
152
+ version: 5.34.8
153
  svelte-check:
154
  specifier: ^4.0.0
155
156
  tailwind-merge:
157
  specifier: ^3.0.2
158
  version: 3.0.2
 
173
174
  unplugin-icons:
175
  specifier: ^22.1.0
176
+ version: 22.1.0([email protected].8)
177
  vite:
178
  specifier: ^5.4.4
179
  version: 5.4.14(@types/[email protected])([email protected])
 
182
183
  vitest-browser-svelte:
184
  specifier: ^0.1.0
185
186
 
187
  packages:
188
 
 
2242
2243
  resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
2244
 
2245
+ melt@0.36.0:
2246
+ resolution: {integrity: sha512-lJdUuPvsCZs7zpcL2iSvxerHxv3QuM91FoTbdsliOQ2+J3fR4ADqUN878J4kkQSzzHlWqyedQmEBDP6U3iEWgA==}
2247
  peerDependencies:
2248
  '@floating-ui/dom': ^1.6.0
2249
+ svelte: ^5.30.1
2250
 
2251
2252
  resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
 
2842
  svelte:
2843
  optional: true
2844
 
2845
2846
+ resolution: {integrity: sha512-TF+8irl7rpj3+fpaLuPRX5BqReTAqckp0Fumxa/mCeK3fo0/MnBb9W/Z2bLwtqj3C3r5Lm6NKIAw7YrgIv1Fwg==}
2847
  engines: {node: '>=18'}
2848
 
2849
 
3922
  dependencies:
3923
  acorn: 8.14.0
3924
 
3925
3926
  dependencies:
3927
3928
  import-meta-resolve: 4.1.0
3929
 
3930
3931
  dependencies:
3932
  '@rollup/plugin-commonjs': 28.0.2([email protected])
3933
  '@rollup/plugin-json': 6.1.0([email protected])
3934
  '@rollup/plugin-node-resolve': 16.0.0([email protected])
3935
3936
  rollup: 4.34.9
3937
 
3938
3939
  dependencies:
3940
+ '@sveltejs/vite-plugin-svelte': 4.0.4([email protected].8)([email protected](@types/[email protected])([email protected]))
3941
  '@types/cookie': 0.6.0
3942
  cookie: 0.6.0
3943
  devalue: 5.1.1
 
3949
  sade: 1.8.1
3950
  set-cookie-parser: 2.7.1
3951
  sirv: 3.0.1
3952
+ svelte: 5.34.8
3953
  vite: 5.4.14(@types/[email protected])([email protected])
3954
 
3955
3956
  dependencies:
3957
+ '@sveltejs/vite-plugin-svelte': 4.0.4([email protected].8)([email protected](@types/[email protected])([email protected]))
3958
  debug: 4.4.0
3959
+ svelte: 5.34.8
3960
  vite: 5.4.14(@types/[email protected])([email protected])
3961
  transitivePeerDependencies:
3962
  - supports-color
3963
 
3964
3965
  dependencies:
3966
3967
  debug: 4.4.0
3968
  deepmerge: 4.3.1
3969
  kleur: 4.1.5
3970
  magic-string: 0.30.17
3971
+ svelte: 5.34.8
3972
  vite: 5.4.14(@types/[email protected])([email protected])
3973
3974
  transitivePeerDependencies:
 
4061
  lodash: 4.17.21
4062
  redent: 3.0.0
4063
 
4064
4065
  dependencies:
4066
  '@testing-library/dom': 10.4.0
4067
+ svelte: 5.34.8
4068
  optionalDependencies:
4069
  vite: 5.4.14(@types/[email protected])([email protected])
4070
 
4613
  optionalDependencies:
4614
  eslint-config-prettier: 10.1.1([email protected]([email protected]))
4615
 
4616
4617
  dependencies:
4618
  '@eslint-community/eslint-utils': 4.4.1([email protected]([email protected]))
4619
  '@jridgewell/sourcemap-codec': 1.5.0
 
4624
  postcss-load-config: 3.1.4([email protected])
4625
  postcss-safe-parser: 7.0.1([email protected])
4626
  semver: 7.7.1
4627
+ svelte-eslint-parser: 1.2.0([email protected].8)
4628
  optionalDependencies:
4629
+ svelte: 5.34.8
4630
  transitivePeerDependencies:
4631
  - ts-node
4632
 
 
5201
  unist-util-visit: 5.0.0
5202
  vfile: 6.0.3
5203
 
5204
+ melt@0.36.0(@floating-ui/[email protected])([email protected].8):
5205
  dependencies:
5206
  '@floating-ui/dom': 1.6.13
5207
  dequal: 2.0.3
5208
  jest-axe: 9.0.0
5209
  nanoid: 5.1.5
5210
+ runed: 0.23.4([email protected].8)
5211
+ svelte: 5.34.8
5212
 
5213
5214
 
 
5467
  dependencies:
5468
  fast-diff: 1.3.0
5469
 
5470
5471
  dependencies:
5472
  prettier: 3.5.3
5473
+ svelte: 5.34.8
5474
 
5475
5476
  dependencies:
5477
  prettier: 3.5.3
5478
  optionalDependencies:
5479
+ prettier-plugin-svelte: 3.4.0([email protected])([email protected].8)
5480
 
5481
5482
 
 
5617
  dependencies:
5618
  queue-microtask: 1.2.3
5619
 
5620
5621
  dependencies:
5622
  esm-env: 1.2.2
5623
+ svelte: 5.34.8
5624
 
5625
5626
  dependencies:
5627
  esm-env: 1.2.2
5628
+ svelte: 5.34.8
5629
 
5630
5631
  dependencies:
 
5755
 
5756
5757
 
5758
5759
  dependencies:
5760
  '@jridgewell/trace-mapping': 0.3.25
5761
  chokidar: 4.0.3
5762
  fdir: 6.4.3([email protected])
5763
  picocolors: 1.1.1
5764
  sade: 1.8.1
5765
+ svelte: 5.34.8
5766
  typescript: 5.8.2
5767
  transitivePeerDependencies:
5768
  - picomatch
5769
 
5770
5771
  dependencies:
5772
  eslint-scope: 8.3.0
5773
  eslint-visitor-keys: 4.2.0
 
5776
  postcss-scss: 4.0.9([email protected])
5777
  postcss-selector-parser: 7.1.0
5778
  optionalDependencies:
5779
+ svelte: 5.34.8
5780
 
5781
5782
  dependencies:
5783
  '@ampproject/remapping': 2.3.0
5784
  '@jridgewell/sourcemap-codec': 1.5.0
 
5950
  unist-util-is: 6.0.0
5951
  unist-util-visit-parents: 6.0.1
5952
 
5953
5954
  dependencies:
5955
  '@antfu/install-pkg': 1.0.0
5956
  '@iconify/utils': 2.3.0
 
5958
  local-pkg: 1.1.1
5959
  unplugin: 2.2.0
5960
  optionalDependencies:
5961
+ svelte: 5.34.8
5962
  transitivePeerDependencies:
5963
  - supports-color
5964
 
 
6034
  optionalDependencies:
6035
  vite: 5.4.14(@types/[email protected])([email protected])
6036
 
6037
6038
  dependencies:
6039
6040
+ svelte: 5.34.8
6041
6042
 
6043
src/lib/builders/local-toasts.svelte.ts ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { extract } from "$lib/spells/extract.svelte";
2
+ import { autoUpdate, computePosition, flip, type Placement } from "@floating-ui/dom";
3
+ import type { MaybeGetter } from "melt";
4
+ import { Toaster, type ToasterProps } from "melt/builders";
5
+ import { createAttachmentKey } from "svelte/attachments";
6
+ import type { HTMLAttributes, HTMLButtonAttributes } from "svelte/elements";
7
+
8
+ type ToastData = {
9
+ content: string;
10
+ variant: "info" | "danger";
11
+ };
12
+
13
+ const classMap: Record<ToastData["variant"], string> = {
14
+ info: "border border-blue-400 bg-gradient-to-b from-blue-500 to-blue-600",
15
+
16
+ danger: "border border-red-400 bg-gradient-to-b from-red-500 to-red-600",
17
+ };
18
+
19
+ type LocalToastsProps = ToasterProps & {
20
+ placement?: MaybeGetter<Placement>;
21
+ };
22
+
23
+ export class LocalToasts {
24
+ placement: Placement;
25
+
26
+ #triggerEl: HTMLButtonElement | null = null;
27
+ toaster: Toaster<ToastData>;
28
+
29
+ constructor(props: LocalToastsProps = {}) {
30
+ this.toaster = new Toaster<ToastData>(props);
31
+ this.placement = $derived(extract(props.placement, "top"));
32
+ }
33
+
34
+ get addToast() {
35
+ return this.toaster.addToast;
36
+ }
37
+
38
+ #triggerAttachment = {
39
+ [createAttachmentKey()]: (node: HTMLButtonElement) => {
40
+ this.#triggerEl = node;
41
+ return () => {
42
+ this.#triggerEl = null;
43
+ };
44
+ },
45
+ };
46
+
47
+ get trigger() {
48
+ return {
49
+ "data-local-toast-trigger": "",
50
+ ...this.#triggerAttachment,
51
+ } as const satisfies HTMLButtonAttributes;
52
+ }
53
+
54
+ get toasts() {
55
+ const original = this.toaster?.toasts;
56
+
57
+ return original.map(toast => {
58
+ const attrs = {
59
+ "data-local-toast": "",
60
+ "data-variant": toast.data.variant,
61
+ [createAttachmentKey()]: node => {
62
+ let placement: Placement = $state(this.placement);
63
+
64
+ const triggerEl = this.#triggerEl;
65
+ if (!triggerEl) return;
66
+
67
+ const compute = () =>
68
+ computePosition(triggerEl, node, {
69
+ strategy: "absolute",
70
+ placement: this.placement,
71
+ middleware: [flip()],
72
+ }).then(({ x, y, placement: _placement }) => {
73
+ placement = _placement;
74
+ Object.assign(node.style, {
75
+ left: placement === "top" ? `${x}px` : `${x - 4}px`,
76
+ top: placement === "top" ? `${y - 6}px` : `${y}px`,
77
+ });
78
+
79
+ // Animate
80
+ // Cancel any ongoing animations
81
+ node.getAnimations().forEach(anim => anim.cancel());
82
+
83
+ // Determine animation direction based on placement
84
+ let keyframes: Keyframe[] = [];
85
+ switch (placement) {
86
+ case "top":
87
+ keyframes = [
88
+ { opacity: 0, transform: "translateY(8px)", scale: "0.8" },
89
+ { opacity: 1, transform: "translateY(0)", scale: "1" },
90
+ ];
91
+ break;
92
+ case "left":
93
+ keyframes = [
94
+ { opacity: 0, transform: "translateX(8px)", scale: "0.8" },
95
+ { opacity: 1, transform: "translateX(0)", scale: "1" },
96
+ ];
97
+ break;
98
+ }
99
+
100
+ node.animate(keyframes, {
101
+ duration: 500,
102
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
103
+ fill: "forwards",
104
+ });
105
+ });
106
+
107
+ const reference = node.cloneNode(true) as HTMLElement;
108
+ node.before(reference);
109
+ reference.style.visibility = "hidden";
110
+
111
+ const destroyers = [
112
+ autoUpdate(triggerEl, node, compute),
113
+ async () => {
114
+ // clone node
115
+ const cloned = node.cloneNode(true) as HTMLElement;
116
+ reference.before(cloned);
117
+ reference.remove();
118
+ cloned.getAnimations().forEach(anim => anim.cancel());
119
+
120
+ // Animate out
121
+ // Cancel any ongoing animations
122
+ cloned.getAnimations().forEach(anim => anim.cancel());
123
+
124
+ // Determine animation direction based on placement
125
+ let keyframes: Keyframe[] = [];
126
+ switch (placement) {
127
+ case "top":
128
+ keyframes = [
129
+ { opacity: 1, transform: "translateY(0)" },
130
+ { opacity: 0, transform: "translateY(-8px)" },
131
+ ];
132
+ break;
133
+ case "left":
134
+ keyframes = [
135
+ { opacity: 1, transform: "translateX(0)" },
136
+ { opacity: 0, transform: "translateX(-8px)" },
137
+ ];
138
+ break;
139
+ }
140
+
141
+ await cloned.animate(keyframes, {
142
+ duration: 400,
143
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
144
+ fill: "forwards",
145
+ }).finished;
146
+
147
+ cloned.remove();
148
+ },
149
+ ];
150
+
151
+ return () => destroyers.forEach(d => d());
152
+ },
153
+ "style": `
154
+ /* Float on top of the UI */
155
+ position: absolute;
156
+ z-index: 100;
157
+
158
+ /* Avoid layout interference */
159
+ width: max-content;
160
+ top: 0;
161
+ left: 0;
162
+ `,
163
+ } as const satisfies HTMLAttributes<HTMLElement>;
164
+
165
+ return Object.assign(toast, {
166
+ class: `${classMap[toast.data.variant]} rounded-full px-2 py-1 text-xs`,
167
+ attrs,
168
+ });
169
+ });
170
+ }
171
+ }
src/lib/components/inference-playground/conversation.svelte CHANGED
@@ -3,7 +3,6 @@
3
  import { type ConversationClass } from "$lib/state/conversations.svelte";
4
  import { watch } from "runed";
5
  import { tick } from "svelte";
6
- import IconPlus from "~icons/carbon/add";
7
  import CodeSnippets from "./code-snippets.svelte";
8
  import Message from "./message.svelte";
9
 
@@ -14,6 +13,7 @@
14
  }
15
 
16
  const { conversation, viewCode, onCloseCode }: Props = $props();
 
17
  let messageContainer: HTMLDivElement | null = $state(null);
18
  const scrollState = new ScrollState({
19
  element: () => messageContainer,
@@ -36,20 +36,6 @@
36
  }
37
  );
38
 
39
- function addMessage() {
40
- const msgs = conversation.data.messages?.slice() || [];
41
- conversation.update({
42
- ...conversation.data,
43
- messages: [
44
- ...msgs,
45
- {
46
- role: msgs.at(-1)?.role === "user" ? "assistant" : "user",
47
- content: "",
48
- },
49
- ],
50
- });
51
- }
52
-
53
  async function regenMessage(idx: number) {
54
  // TODO: migrate to new logic
55
  const msg = conversation.data.messages?.[idx];
@@ -66,7 +52,7 @@
66
  </script>
67
 
68
  <div
69
- class="@container flex flex-col overflow-x-hidden overflow-y-auto"
70
  class:animate-pulse={conversation.generating && !conversation.data.streaming}
71
  bind:this={messageContainer}
72
  >
@@ -76,24 +62,10 @@
76
  {message}
77
  {index}
78
  {conversation}
79
- autofocus={index === (conversation.data.messages?.length || 0) - 1}
80
  onDelete={() => conversation.deleteMessage(index)}
81
  onRegen={() => regenMessage(index)}
82
  />
83
  {/each}
84
-
85
- <button
86
- class="flex px-3.5 py-6 hover:bg-gray-50 md:px-6 dark:hover:bg-gray-800/50"
87
- onclick={addMessage}
88
- disabled={conversation.generating}
89
- >
90
- <div class="flex items-center gap-2 p-0! text-sm font-semibold">
91
- <div class="text-lg">
92
- <IconPlus />
93
- </div>
94
- Add message
95
- </div>
96
- </button>
97
  {:else}
98
  <CodeSnippets {conversation} {onCloseCode} />
99
  {/if}
 
3
  import { type ConversationClass } from "$lib/state/conversations.svelte";
4
  import { watch } from "runed";
5
  import { tick } from "svelte";
 
6
  import CodeSnippets from "./code-snippets.svelte";
7
  import Message from "./message.svelte";
8
 
 
13
  }
14
 
15
  const { conversation, viewCode, onCloseCode }: Props = $props();
16
+
17
  let messageContainer: HTMLDivElement | null = $state(null);
18
  const scrollState = new ScrollState({
19
  element: () => messageContainer,
 
36
  }
37
  );
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  async function regenMessage(idx: number) {
40
  // TODO: migrate to new logic
41
  const msg = conversation.data.messages?.[idx];
 
52
  </script>
53
 
54
  <div
55
+ class="@container flex h-full flex-col overflow-x-hidden overflow-y-auto"
56
  class:animate-pulse={conversation.generating && !conversation.data.streaming}
57
  bind:this={messageContainer}
58
  >
 
62
  {message}
63
  {index}
64
  {conversation}
 
65
  onDelete={() => conversation.deleteMessage(index)}
66
  onRegen={() => regenMessage(index)}
67
  />
68
  {/each}
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  {:else}
70
  <CodeSnippets {conversation} {onCloseCode} />
71
  {/if}
src/lib/components/inference-playground/message-textarea.svelte ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { autofocus } from "$lib/attachments/autofocus.js";
3
+ import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte.js";
4
+ import { conversations } from "$lib/state/conversations.svelte";
5
+ import { cmdOrCtrl } from "$lib/utils/platform.js";
6
+ import { addToast } from "../toaster.svelte.js";
7
+
8
+ const multiple = $derived(conversations.active.length > 1);
9
+ const loading = $derived(conversations.generating);
10
+
11
+ let input = $state("");
12
+
13
+ async function onKeydown(event: KeyboardEvent) {
14
+ if (loading) return;
15
+ const ctrlOrMeta = event.ctrlKey || event.metaKey;
16
+
17
+ if (ctrlOrMeta && event.key === "Enter") {
18
+ const c = conversations.active;
19
+ const isValid = c.every(c => c.data.messages?.at(-1)?.role !== "user");
20
+
21
+ if (!isValid) {
22
+ addToast({
23
+ title: "Cannot add message",
24
+ description: "Cannot have multiple user messages in a row",
25
+
26
+ variant: "error",
27
+ });
28
+ } else {
29
+ await Promise.all(c.map(c => c.addMessage({ role: "user", content: input })));
30
+ c.forEach(c => c.genNextMessage());
31
+ input = "";
32
+ }
33
+ }
34
+ }
35
+
36
+ const autosized = new TextareaAutosize();
37
+ </script>
38
+
39
+ <svelte:window onkeydown={onKeydown} />
40
+
41
+ <div class="mt-auto p-2">
42
+ <label
43
+ class="flex w-full items-end rounded-[32px] bg-gray-200 p-2 pl-8 outline-offset-2 outline-blue-500 focus-within:outline-2 dark:bg-gray-800"
44
+ >
45
+ <textarea
46
+ placeholder="Enter your message"
47
+ class="max-h-100 flex-1 resize-none self-center outline-none"
48
+ bind:value={input}
49
+ {@attach autosized.attachment}
50
+ {@attach autofocus()}
51
+ ></textarea>
52
+ <button
53
+ onclick={() => {
54
+ conversations.genOrStop();
55
+ }}
56
+ type="button"
57
+ class={[
58
+ "flex items-center justify-center gap-2 rounded-full 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",
59
+ loading && "bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700",
60
+ !loading && "bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700",
61
+ ]}
62
+ >
63
+ {#if loading}
64
+ <div class="flex flex-none items-center gap-[3px]">
65
+ <span class="mr-2">
66
+ {#if conversations.active.some(c => c.data.streaming)}
67
+ Stop
68
+ {:else}
69
+ Cancel
70
+ {/if}
71
+ </span>
72
+ {#each { length: 3 } as _, i}
73
+ <div
74
+ class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-200 dark:bg-gray-100"
75
+ style="animation-delay: {(i + 1) * 0.25}s;"
76
+ ></div>
77
+ {/each}
78
+ </div>
79
+ {:else}
80
+ {multiple ? "Run all" : "Run"}
81
+ <span class="inline-flex gap-0.5 rounded-sm border border-white/20 bg-white/10 px-0.5 text-xs text-white/70">
82
+ {cmdOrCtrl}<span class="translate-y-px">↵</span>
83
+ </span>
84
+ {/if}
85
+ </button>
86
+ </label>
87
+ </div>
src/lib/components/inference-playground/message.svelte CHANGED
@@ -1,5 +1,4 @@
1
  <script lang="ts">
2
- import { autofocus as autofocusAction } from "$lib/attachments/autofocus.js";
3
  import Tooltip from "$lib/components/tooltip.svelte";
4
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte.js";
5
  import { type ConversationClass } from "$lib/state/conversations.svelte.js";
@@ -22,12 +21,11 @@
22
  conversation: ConversationClass;
23
  message: ConversationMessage;
24
  index: number;
25
- autofocus?: boolean;
26
  onDelete?: () => void;
27
  onRegen?: () => void;
28
  };
29
 
30
- const { index, conversation, message, autofocus, onDelete, onRegen }: Props = $props();
31
  const isLast = $derived(index === (conversation.data.messages?.length || 0) - 1);
32
 
33
  const autosized = new TextareaAutosize();
@@ -116,7 +114,6 @@
116
  rows="1"
117
  data-message
118
  data-test-id={TEST_IDS.message}
119
- {@attach autofocusAction(autofocus)}
120
  {@attach autosized.attachment}
121
  ></textarea>
122
 
 
1
  <script lang="ts">
 
2
  import Tooltip from "$lib/components/tooltip.svelte";
3
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte.js";
4
  import { type ConversationClass } from "$lib/state/conversations.svelte.js";
 
21
  conversation: ConversationClass;
22
  message: ConversationMessage;
23
  index: number;
 
24
  onDelete?: () => void;
25
  onRegen?: () => void;
26
  };
27
 
28
+ const { index, conversation, message, onDelete, onRegen }: Props = $props();
29
  const isLast = $derived(index === (conversation.data.messages?.length || 0) - 1);
30
 
31
  const autosized = new TextareaAutosize();
 
114
  rows="1"
115
  data-message
116
  data-test-id={TEST_IDS.message}
 
117
  {@attach autosized.attachment}
118
  ></textarea>
119
 
src/lib/components/inference-playground/model-selector-modal.svelte CHANGED
@@ -26,7 +26,7 @@
26
 
27
  let { onModelSelect, onClose, conversation }: Props = $props();
28
 
29
- const combobox = new Combobox({
30
  onOpenChange(o) {
31
  if (!o) onClose?.();
32
  },
@@ -182,7 +182,7 @@
182
  {/if}
183
  <div
184
  class="flex w-full cursor-pointer items-center gap-2 px-2 py-1.5 text-sm text-gray-500 data-[highlighted]:bg-blue-500/15 data-[highlighted]:text-blue-600 dark:text-gray-400 dark:data-[highlighted]:text-blue-300"
185
- {...combobox.getOption("__custom__", () => {
186
  onClose?.();
187
  openCustomModelConfig({
188
  onSubmit: model => {
 
26
 
27
  let { onModelSelect, onClose, conversation }: Props = $props();
28
 
29
+ const combobox = new Combobox<string | undefined>({
30
  onOpenChange(o) {
31
  if (!o) onClose?.();
32
  },
 
182
  {/if}
183
  <div
184
  class="flex w-full cursor-pointer items-center gap-2 px-2 py-1.5 text-sm text-gray-500 data-[highlighted]:bg-blue-500/15 data-[highlighted]:text-blue-600 dark:text-gray-400 dark:data-[highlighted]:text-blue-300"
185
+ {...combobox.getOption("__custom__", "custom", () => {
186
  onClose?.();
187
  openCustomModelConfig({
188
  onSubmit: model => {
src/lib/components/inference-playground/playground.svelte CHANGED
@@ -6,13 +6,8 @@
6
  import { isHFModel } from "$lib/types.js";
7
  import { iterate } from "$lib/utils/array.js";
8
  import { isSystemPromptSupported } from "$lib/utils/business.svelte.js";
9
- import { cmdOrCtrl, optOrAlt } from "$lib/utils/platform.js";
10
- import { Popover } from "melt/components";
11
- import IconChatLeft from "~icons/carbon/align-box-bottom-left";
12
- import IconChatRight from "~icons/carbon/align-box-bottom-right";
13
  import IconExternal from "~icons/carbon/arrow-up-right";
14
  import IconWaterfall from "~icons/carbon/chart-waterfall";
15
- import IconChevronDown from "~icons/carbon/chevron-down";
16
  import IconCode from "~icons/carbon/code";
17
  import IconCompare from "~icons/carbon/compare";
18
  import IconInfo from "~icons/carbon/information";
@@ -30,30 +25,16 @@
30
  import ModelSelector from "./model-selector.svelte";
31
  import ProjectSelect from "./project-select.svelte";
32
  import { TEST_IDS } from "$lib/constants.js";
33
-
34
- const multiple = $derived(conversations.active.length > 1);
35
 
36
  let viewCode = $state(false);
37
  let viewSettings = $state(false);
38
- const loading = $derived(conversations.generating);
39
 
40
  let selectCompareModelOpen = $state(false);
41
 
42
  const systemPromptSupported = $derived(conversations.active.some(c => isSystemPromptSupported(c.model)));
43
  const compareActive = $derived(conversations.active.length === 2);
44
 
45
- function onKeydown(event: KeyboardEvent) {
46
- if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
47
- conversations.genNextMessages();
48
- }
49
- if ((event.ctrlKey || event.metaKey) && event.altKey && event.key === "l") {
50
- conversations.genNextMessages("left");
51
- }
52
- if ((event.ctrlKey || event.metaKey) && event.altKey && event.key === "r") {
53
- conversations.genNextMessages("right");
54
- }
55
- }
56
-
57
  function handleTokenSubmit(e: Event) {
58
  const form = e.target as HTMLFormElement;
59
  const formData = new FormData(form);
@@ -77,7 +58,6 @@
77
  />
78
  {/if}
79
 
80
- <!-- svelte-ignore a11y_no_static_element_interactions -->
81
  <div
82
  class={[
83
  "motion-safe:animate-fade-in grid h-dvh divide-gray-200 overflow-hidden bg-gray-100/50",
@@ -116,7 +96,7 @@
116
  </div>
117
 
118
  <!-- Center column -->
119
- <div class="relative flex h-full flex-col overflow-hidden" onkeydown={onKeydown}>
120
  <Toaster />
121
  <div
122
  class="flex flex-1 divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:pt-3 dark:divide-gray-800"
@@ -135,6 +115,8 @@
135
  {/each}
136
  </div>
137
 
 
 
138
  <!-- Bottom bar -->
139
  <div
140
  class="relative mt-auto flex h-20 shrink-0 items-center justify-center gap-2 overflow-hidden border-t border-gray-200 px-3 whitespace-nowrap dark:border-gray-800"
@@ -194,114 +176,6 @@
194
  <IconCode />
195
  {!viewCode ? "View Code" : "Hide Code"}
196
  </button>
197
- <div class="flex">
198
- <button
199
- onclick={() => {
200
- viewCode = false;
201
- conversations.genOrStop();
202
- }}
203
- type="button"
204
- class={[
205
- "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",
206
- multiple ? "rounded-l-lg" : "rounded-lg",
207
- loading && "bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700",
208
- !loading && "bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700",
209
- ]}
210
- >
211
- {#if loading}
212
- <div class="flex flex-none items-center gap-[3px]">
213
- <span class="mr-2">
214
- {#if conversations.active.some(c => c.data.streaming)}
215
- Stop
216
- {:else}
217
- Cancel
218
- {/if}
219
- </span>
220
- {#each { length: 3 } as _, i}
221
- <div
222
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
223
- style="animation-delay: {(i + 1) * 0.25}s;"
224
- ></div>
225
- {/each}
226
- </div>
227
- {:else}
228
- {multiple ? "Run all" : "Run"}
229
- <span
230
- class="inline-flex gap-0.5 rounded-sm border border-white/20 bg-white/10 px-0.5 text-xs text-white/70"
231
- >
232
- {cmdOrCtrl}<span class="translate-y-px">↵</span>
233
- </span>
234
- {/if}
235
- </button>
236
- {#if multiple}
237
- <div class="w-[1px] bg-gray-800" aria-hidden="true"></div>
238
- <Popover
239
- floatingConfig={{
240
- computePosition: {
241
- placement: "top-end",
242
- },
243
- }}
244
- >
245
- {#snippet children(popover)}
246
- <button
247
- class={[
248
- "flex items-center justify-center gap-2 rounded-r-lg px-1.5 text-sm font-medium text-white",
249
- "focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:focus:ring-gray-700",
250
- loading && "bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700",
251
- !loading && "bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700",
252
- ]}
253
- {...popover.trigger}
254
- disabled={loading}
255
- >
256
- <IconChevronDown />
257
- </button>
258
- <div
259
- class={["flex-col rounded-lg bg-white px-2 py-1 shadow dark:bg-gray-800", popover.open && "flex"]}
260
- {...popover.content}
261
- >
262
- <button
263
- class="group py-1 text-sm"
264
- onclick={() => {
265
- viewCode = false;
266
- conversations.genOrStop("left");
267
- popover.open = false;
268
- }}
269
- >
270
- <div
271
- class="flex items-center gap-2 rounded p-1 group-hover:bg-gray-200 dark:group-hover:bg-gray-700"
272
- >
273
- <IconChatLeft />
274
- <span class="mr-2">Only run left conversation</span>
275
- <span class="ml-auto rounded-sm border border-white/20 bg-gray-500/10 px-0.5 text-xs">
276
- {cmdOrCtrl}
277
- {optOrAlt} L
278
- </span>
279
- </div>
280
- </button>
281
- <button
282
- class="group py-1 text-sm"
283
- onclick={() => {
284
- viewCode = false;
285
- conversations.genOrStop("right");
286
- popover.open = false;
287
- }}
288
- >
289
- <div
290
- class="flex items-center gap-2 rounded p-1 group-hover:bg-gray-200 dark:group-hover:bg-gray-700"
291
- >
292
- <IconChatRight />
293
- <span class="mr-2">Only run right conversation</span>
294
- <span class="ml-auto rounded-sm border border-white/20 bg-gray-500/10 px-0.5 text-xs">
295
- {cmdOrCtrl}
296
- {optOrAlt} R
297
- </span>
298
- </div>
299
- </button>
300
- </div>
301
- {/snippet}
302
- </Popover>
303
- {/if}
304
- </div>
305
  </div>
306
  </div>
307
  </div>
 
6
  import { isHFModel } from "$lib/types.js";
7
  import { iterate } from "$lib/utils/array.js";
8
  import { isSystemPromptSupported } from "$lib/utils/business.svelte.js";
 
 
 
 
9
  import IconExternal from "~icons/carbon/arrow-up-right";
10
  import IconWaterfall from "~icons/carbon/chart-waterfall";
 
11
  import IconCode from "~icons/carbon/code";
12
  import IconCompare from "~icons/carbon/compare";
13
  import IconInfo from "~icons/carbon/information";
 
25
  import ModelSelector from "./model-selector.svelte";
26
  import ProjectSelect from "./project-select.svelte";
27
  import { TEST_IDS } from "$lib/constants.js";
28
+ import MessageTextarea from "./message-textarea.svelte";
 
29
 
30
  let viewCode = $state(false);
31
  let viewSettings = $state(false);
 
32
 
33
  let selectCompareModelOpen = $state(false);
34
 
35
  const systemPromptSupported = $derived(conversations.active.some(c => isSystemPromptSupported(c.model)));
36
  const compareActive = $derived(conversations.active.length === 2);
37
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  function handleTokenSubmit(e: Event) {
39
  const form = e.target as HTMLFormElement;
40
  const formData = new FormData(form);
 
58
  />
59
  {/if}
60
 
 
61
  <div
62
  class={[
63
  "motion-safe:animate-fade-in grid h-dvh divide-gray-200 overflow-hidden bg-gray-100/50",
 
96
  </div>
97
 
98
  <!-- Center column -->
99
+ <div class="relative flex h-full flex-col overflow-hidden">
100
  <Toaster />
101
  <div
102
  class="flex flex-1 divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:pt-3 dark:divide-gray-800"
 
115
  {/each}
116
  </div>
117
 
118
+ <MessageTextarea />
119
+
120
  <!-- Bottom bar -->
121
  <div
122
  class="relative mt-auto flex h-20 shrink-0 items-center justify-center gap-2 overflow-hidden border-t border-gray-200 px-3 whitespace-nowrap dark:border-gray-800"
 
176
  <IconCode />
177
  {!viewCode ? "View Code" : "Hide Code"}
178
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </div>
180
  </div>
181
  </div>
src/lib/components/share-modal.svelte CHANGED
@@ -132,9 +132,9 @@
132
  </div>
133
 
134
  <div class="my-4 flex items-center gap-2">
135
- <div class="h-px grow bg-neutral-500" aria-hidden="true"></div>
136
- <span class="text-xs text-neutral-400">or</span>
137
- <div class="h-px grow bg-neutral-500" aria-hidden="true"></div>
138
  </div>
139
 
140
  <h3 class="text-lg font-semibold">Save a copied project</h3>
 
132
  </div>
133
 
134
  <div class="my-4 flex items-center gap-2">
135
+ <div class="h-px grow bg-gray-500" aria-hidden="true"></div>
136
+ <span class="text-xs text-gray-400">or</span>
137
+ <div class="h-px grow bg-gray-500" aria-hidden="true"></div>
138
  </div>
139
 
140
  <h3 class="text-lg font-semibold">Save a copied project</h3>
src/lib/components/toaster.svelte CHANGED
@@ -46,7 +46,7 @@
46
 
47
  <div
48
  {...omit(toaster.root, "popover")}
49
- class={["absolute right-2 bottom-23 flex w-[300px] flex-col ", !isComparing && "md:right-0"]}
50
  style:--toasts={toaster.toasts.length}
51
  style={getRootStyle()}
52
  >
 
46
 
47
  <div
48
  {...omit(toaster.root, "popover")}
49
+ class={["absolute right-2 bottom-40 flex w-[300px] flex-col ", !isComparing && "md:right-0"]}
50
  style:--toasts={toaster.toasts.length}
51
  style={getRootStyle()}
52
  >
src/lib/data/context_length.json CHANGED
@@ -107,11 +107,7 @@
107
  "google/gemma-3-1b-it": 32768,
108
  "qwen/qwen3-8b-fp8": 128000,
109
  "qwen/qwen3-4b-fp8": 128000,
110
- "thudm/glm-4-9b-0414": 32000,
111
- "thudm/glm-z1-9b-0414": 32000,
112
- "thudm/glm-z1-32b-0414": 32000,
113
  "thudm/glm-4-32b-0414": 32000,
114
- "thudm/glm-z1-rumination-32b-0414": 32000,
115
  "qwen/qwen2.5-7b-instruct": 32000,
116
  "meta-llama/llama-3.2-1b-instruct": 131000,
117
  "meta-llama/llama-3.2-3b-instruct": 32768,
@@ -171,52 +167,44 @@
171
  "command": 4096
172
  },
173
  "together": {
 
174
  "togethercomputer/m2-bert-80M-32k-retrieval": 32768,
175
  "cartesia/sonic": 0,
 
176
  "intfloat/multilingual-e5-large-instruct": 514,
177
  "Alibaba-NLP/gte-modernbert-base": 8192,
 
178
  "meta-llama/LlamaGuard-2-8b": 8192,
 
179
  "cartesia/sonic-2": 0,
180
- "meta-llama/Llama-3.3-70B-Instruct-Turbo": 131072,
181
- "Qwen/Qwen3-235B-A22B-fp8-tput": 40960,
182
  "togethercomputer/MoA-1": 32768,
183
  "meta-llama/Meta-Llama-3-70B-Instruct-Turbo": 8192,
 
184
  "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": 131072,
 
185
  "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": 131072,
186
  "meta-llama/Meta-Llama-Guard-3-8B": 8192,
 
 
187
  "arcee-ai/AFM-4.5B-Preview": 65536,
 
188
  "deepseek-ai/DeepSeek-V3": 131072,
189
  "lgai/exaone-3-5-32b-instruct": 32768,
190
  "deepseek-ai/DeepSeek-R1-0528-tput": 163840,
191
  "mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
192
- "meta-llama/Llama-Vision-Free": 131072,
193
  "meta-llama/Llama-3-8b-chat-hf": 8192,
194
  "mistralai/Mistral-7B-Instruct-v0.1": 32768,
195
- "meta-llama/Llama-4-Scout-17B-16E-Instruct": 1048576,
196
- "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": 131072,
197
- "arcee_ai/arcee-spotlight": 131072,
198
- "Salesforce/Llama-Rank-V1": 8192,
199
- "mistralai/Mistral-Small-24B-Instruct-2501": 32768,
200
- "Qwen/Qwen2.5-72B-Instruct-Turbo": 131072,
201
- "Qwen/Qwen2.5-7B-Instruct-Turbo": 32768,
202
- "Qwen/QwQ-32B": 131072,
203
  "meta-llama/Llama-2-70b-hf": 4096,
204
  "togethercomputer/MoA-1-Turbo": 32768,
205
- "Qwen/Qwen2.5-Coder-32B-Instruct": 16384,
206
- "meta-llama/Meta-Llama-3-8B-Instruct-Lite": 8192,
207
  "black-forest-labs/FLUX.1-kontext-max": 0,
208
  "perplexity-ai/r1-1776": 163840,
209
- "meta-llama/Llama-3-70b-chat-hf": 8192,
210
  "mistralai/Mistral-7B-Instruct-v0.2": 32768,
211
  "deepseek-ai/DeepSeek-V3-p-dp": 131072,
212
  "Qwen/Qwen2-72B-Instruct": 32768,
213
  "mistralai/Mistral-7B-Instruct-v0.3": 32768,
214
  "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO": 32768,
215
- "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo": 131072,
216
  "meta-llama/Llama-Guard-3-11B-Vision-Turbo": 131072,
217
- "google/gemma-2-27b-it": 8192,
218
  "Qwen/Qwen2-VL-72B-Instruct": 32768,
219
- "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo": 131072,
220
  "scb10x/scb10x-llama3-1-typhoon2-70b-instruct": 8192,
221
  "arcee-ai/maestro-reasoning": 131072,
222
  "meta-llama/Llama-3.2-3B-Instruct-Turbo": 131072,
@@ -233,15 +221,26 @@
233
  "togethercomputer/Refuel-Llm-V2-Small": 8192,
234
  "togethercomputer/Refuel-Llm-V2": 16384,
235
  "Qwen/Qwen2.5-VL-72B-Instruct": 32768,
236
- "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": 130815,
237
- "deepseek-ai/DeepSeek-R1": 163840,
238
  "scb10x/scb10x-typhoon-2-1-gemma3-12b": 131072,
239
  "arcee-ai/caller": 32768,
240
  "lgai/exaone-deep-32b": 32768,
241
  "black-forest-labs/FLUX.1-kontext-pro": 0,
242
  "google/gemma-3n-E4B-it": 32768,
 
 
 
 
243
  "arcee-ai/arcee-blitz": 32768,
244
- "marin-community/marin-8b-instruct": 4096
 
 
 
 
 
 
 
 
 
245
  },
246
  "fireworks-ai": {
247
  "accounts/perplexity/models/r1-1776": 163840,
@@ -249,22 +248,23 @@
249
  "accounts/fireworks/models/qwen3-30b-a3b": 40000,
250
  "accounts/fireworks/models/llama-guard-3-8b": 131072,
251
  "accounts/fireworks/models/llama4-scout-instruct-basic": 10485760,
252
- "accounts/fireworks/models/qwq-32b": 131072,
253
  "accounts/fireworks/models/llama4-maverick-instruct-basic": 1048576,
254
  "accounts/fireworks/models/llama-v3p1-8b-instruct": 131072,
255
  "accounts/fireworks/models/firesearch-ocr-v6": 8192,
256
- "accounts/fireworks/models/deepseek-r1-basic": 163840,
257
  "accounts/fireworks/models/llama-v3p1-405b-instruct": 131072,
258
- "accounts/fireworks/models/qwen2p5-vl-32b-instruct": 128000,
259
  "accounts/fireworks/models/qwen2-vl-72b-instruct": 32768,
260
  "accounts/fireworks/models/mixtral-8x22b-instruct": 65536,
261
  "accounts/fireworks/models/qwen2p5-72b-instruct": 32768,
 
262
  "accounts/fireworks/models/llama-v3p1-70b-instruct": 131072,
263
  "accounts/fireworks/models/qwen3-235b-a22b": 128000,
264
  "accounts/fireworks/models/llama-v3p3-70b-instruct": 131072,
265
  "accounts/fireworks/models/deepseek-r1": 163840,
266
  "accounts/sentientfoundation/models/dobby-unhinged-llama-3-3-70b-new": 131072,
 
267
  "accounts/fireworks/models/deepseek-v3": 131072,
268
- "accounts/fireworks/models/deepseek-v3-0324": 163840
 
 
269
  }
270
  }
 
107
  "google/gemma-3-1b-it": 32768,
108
  "qwen/qwen3-8b-fp8": 128000,
109
  "qwen/qwen3-4b-fp8": 128000,
 
 
 
110
  "thudm/glm-4-32b-0414": 32000,
 
111
  "qwen/qwen2.5-7b-instruct": 32000,
112
  "meta-llama/llama-3.2-1b-instruct": 131000,
113
  "meta-llama/llama-3.2-3b-instruct": 32768,
 
167
  "command": 4096
168
  },
169
  "together": {
170
+ "meta-llama/Llama-Vision-Free": 131072,
171
  "togethercomputer/m2-bert-80M-32k-retrieval": 32768,
172
  "cartesia/sonic": 0,
173
+ "meta-llama/Meta-Llama-3-8B-Instruct-Lite": 8192,
174
  "intfloat/multilingual-e5-large-instruct": 514,
175
  "Alibaba-NLP/gte-modernbert-base": 8192,
176
+ "Qwen/Qwen3-235B-A22B-fp8-tput": 40960,
177
  "meta-llama/LlamaGuard-2-8b": 8192,
178
+ "Qwen/Qwen2.5-Coder-32B-Instruct": 16384,
179
  "cartesia/sonic-2": 0,
 
 
180
  "togethercomputer/MoA-1": 32768,
181
  "meta-llama/Meta-Llama-3-70B-Instruct-Turbo": 8192,
182
+ "eddie/Qwen3-32B": 32768,
183
  "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": 131072,
184
+ "Qwen/QwQ-32B": 131072,
185
  "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": 131072,
186
  "meta-llama/Meta-Llama-Guard-3-8B": 8192,
187
+ "arcee_ai/arcee-spotlight": 131072,
188
+ "google/gemma-3-27b-it": 65536,
189
  "arcee-ai/AFM-4.5B-Preview": 65536,
190
+ "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": 130815,
191
  "deepseek-ai/DeepSeek-V3": 131072,
192
  "lgai/exaone-3-5-32b-instruct": 32768,
193
  "deepseek-ai/DeepSeek-R1-0528-tput": 163840,
194
  "mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
 
195
  "meta-llama/Llama-3-8b-chat-hf": 8192,
196
  "mistralai/Mistral-7B-Instruct-v0.1": 32768,
 
 
 
 
 
 
 
 
197
  "meta-llama/Llama-2-70b-hf": 4096,
198
  "togethercomputer/MoA-1-Turbo": 32768,
 
 
199
  "black-forest-labs/FLUX.1-kontext-max": 0,
200
  "perplexity-ai/r1-1776": 163840,
 
201
  "mistralai/Mistral-7B-Instruct-v0.2": 32768,
202
  "deepseek-ai/DeepSeek-V3-p-dp": 131072,
203
  "Qwen/Qwen2-72B-Instruct": 32768,
204
  "mistralai/Mistral-7B-Instruct-v0.3": 32768,
205
  "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO": 32768,
 
206
  "meta-llama/Llama-Guard-3-11B-Vision-Turbo": 131072,
 
207
  "Qwen/Qwen2-VL-72B-Instruct": 32768,
 
208
  "scb10x/scb10x-llama3-1-typhoon2-70b-instruct": 8192,
209
  "arcee-ai/maestro-reasoning": 131072,
210
  "meta-llama/Llama-3.2-3B-Instruct-Turbo": 131072,
 
221
  "togethercomputer/Refuel-Llm-V2-Small": 8192,
222
  "togethercomputer/Refuel-Llm-V2": 16384,
223
  "Qwen/Qwen2.5-VL-72B-Instruct": 32768,
 
 
224
  "scb10x/scb10x-typhoon-2-1-gemma3-12b": 131072,
225
  "arcee-ai/caller": 32768,
226
  "lgai/exaone-deep-32b": 32768,
227
  "black-forest-labs/FLUX.1-kontext-pro": 0,
228
  "google/gemma-3n-E4B-it": 32768,
229
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct": 1048576,
230
+ "deepseek-ai/DeepSeek-R1": 163840,
231
+ "Qwen/Qwen2.5-72B-Instruct-Turbo": 131072,
232
+ "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo": 131072,
233
  "arcee-ai/arcee-blitz": 32768,
234
+ "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo": 131072,
235
+ "meta-llama/Llama-3-70b-chat-hf": 8192,
236
+ "google/gemma-2-27b-it": 8192,
237
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": 131072,
238
+ "Qwen/Qwen2.5-7B-Instruct-Turbo": 32768,
239
+ "mistralai/Mistral-Small-24B-Instruct-2501": 32768,
240
+ "Salesforce/Llama-Rank-V1": 8192,
241
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo": 131072,
242
+ "marin-community/marin-8b-instruct": 4096,
243
+ "Qwen/Qwen3-32B-FP8": 0
244
  },
245
  "fireworks-ai": {
246
  "accounts/perplexity/models/r1-1776": 163840,
 
248
  "accounts/fireworks/models/qwen3-30b-a3b": 40000,
249
  "accounts/fireworks/models/llama-guard-3-8b": 131072,
250
  "accounts/fireworks/models/llama4-scout-instruct-basic": 10485760,
 
251
  "accounts/fireworks/models/llama4-maverick-instruct-basic": 1048576,
252
  "accounts/fireworks/models/llama-v3p1-8b-instruct": 131072,
253
  "accounts/fireworks/models/firesearch-ocr-v6": 8192,
 
254
  "accounts/fireworks/models/llama-v3p1-405b-instruct": 131072,
 
255
  "accounts/fireworks/models/qwen2-vl-72b-instruct": 32768,
256
  "accounts/fireworks/models/mixtral-8x22b-instruct": 65536,
257
  "accounts/fireworks/models/qwen2p5-72b-instruct": 32768,
258
+ "accounts/fireworks/models/deepseek-r1-basic": 163840,
259
  "accounts/fireworks/models/llama-v3p1-70b-instruct": 131072,
260
  "accounts/fireworks/models/qwen3-235b-a22b": 128000,
261
  "accounts/fireworks/models/llama-v3p3-70b-instruct": 131072,
262
  "accounts/fireworks/models/deepseek-r1": 163840,
263
  "accounts/sentientfoundation/models/dobby-unhinged-llama-3-3-70b-new": 131072,
264
+ "accounts/sentientfoundation-serverless/models/dobby-mini-unhinged-plus-llama-3-1-8b": 131072,
265
  "accounts/fireworks/models/deepseek-v3": 131072,
266
+ "accounts/fireworks/models/deepseek-v3-0324": 163840,
267
+ "accounts/fireworks/models/qwq-32b": 131072,
268
+ "accounts/fireworks/models/qwen2p5-vl-32b-instruct": 128000
269
  }
270
  }
src/lib/state/conversations.svelte.ts CHANGED
@@ -125,7 +125,7 @@ export class ConversationClass {
125
  };
126
 
127
  addMessage = async (message: ConversationMessage) => {
128
- this.update({
129
  ...this.data,
130
  messages: [...(this.data.messages || []), snapshot(message)],
131
  });
 
125
  };
126
 
127
  addMessage = async (message: ConversationMessage) => {
128
+ await this.update({
129
  ...this.data,
130
  messages: [...(this.data.messages || []), snapshot(message)],
131
  });