extonlawrence commited on
Commit
029d885
·
1 Parent(s): 2e04298

Reroute model and persona pages to global settings modal.

Browse files
src/lib/components/NavMenu.svelte CHANGED
@@ -75,6 +75,18 @@
75
 
76
  const nModels: number = page.data.models.filter((el: Model) => !el.unlisted).length;
77
  let nPersonas = $derived($settings.personas.length);
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  async function handleVisible() {
80
  p++;
@@ -180,7 +192,7 @@
180
  </a>
181
  {/if} -->
182
  <a
183
- href="{base}/models"
184
  class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
185
  >
186
  Models
@@ -191,7 +203,7 @@
191
  </a>
192
 
193
  <a
194
- href="{base}/personas"
195
  class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
196
  >
197
  Personas
 
75
 
76
  const nModels: number = page.data.models.filter((el: Model) => !el.unlisted).length;
77
  let nPersonas = $derived($settings.personas.length);
78
+
79
+ // Route to first active persona or first persona
80
+ let personasRoute = $derived.by(() => {
81
+ const targetId = $settings.activePersonas[0] || $settings.personas[0]?.id || '';
82
+ return `${base}/settings/personas/${targetId}`;
83
+ });
84
+
85
+ // Route to active model or first model
86
+ let modelsRoute = $derived.by(() => {
87
+ const targetId = $settings.activeModel || page.data.models[0]?.id || '';
88
+ return `${base}/settings/models/${targetId}`;
89
+ });
90
 
91
  async function handleVisible() {
92
  p++;
 
192
  </a>
193
  {/if} -->
194
  <a
195
+ href={modelsRoute}
196
  class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
197
  >
198
  Models
 
203
  </a>
204
 
205
  <a
206
+ href={personasRoute}
207
  class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
208
  >
209
  Personas
src/lib/components/OverloadedModal.svelte CHANGED
@@ -26,7 +26,7 @@
26
 
27
  <div class="flex w-full flex-col items-center gap-4 pt-4">
28
  <button
29
- onclick={() => (onClose(), goto(`${base}/models`))}
30
  class="flex w-full flex-wrap items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
31
  >
32
  See all available models
 
26
 
27
  <div class="flex w-full flex-col items-center gap-4 pt-4">
28
  <button
29
+ onclick={() => (onClose(), goto(`${base}/settings/models`))}
30
  class="flex w-full flex-wrap items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
31
  >
32
  See all available models
src/routes/models/+page.svelte DELETED
@@ -1,147 +0,0 @@
1
- <script lang="ts">
2
- import type { PageData } from "./$types";
3
- import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
4
-
5
- import { base } from "$app/paths";
6
- import { page } from "$app/state";
7
-
8
- import CarbonHelpFilled from "~icons/carbon/help-filled";
9
- import CarbonView from "~icons/carbon/view";
10
- import { useSettingsStore } from "$lib/stores/settings";
11
- interface Props {
12
- data: PageData;
13
- }
14
-
15
- let { data }: Props = $props();
16
-
17
- const settings = useSettingsStore();
18
-
19
- const publicConfig = usePublicConfig();
20
-
21
- // Local filter state for model search (hyphen/space insensitive)
22
- let modelFilter = $state("");
23
- const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ");
24
- let queryTokens = $derived(normalize(modelFilter).trim().split(/\s+/).filter(Boolean));
25
- </script>
26
-
27
- <svelte:head>
28
- {#if publicConfig.isHuggingChat}
29
- <title>HuggingChat - Models</title>
30
- <meta property="og:title" content="HuggingChat - Models" />
31
- <meta property="og:type" content="link" />
32
- <meta property="og:description" content="Browse HuggingChat available models" />
33
- <meta property="og:url" content={page.url.href} />
34
- {/if}
35
- </svelte:head>
36
-
37
- <div class="scrollbar-custom h-full overflow-y-auto py-12 max-sm:pt-8 md:py-24">
38
- <div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]">
39
- <div class="flex items-center">
40
- <h1 class="text-2xl font-bold">Models</h1>
41
- {#if publicConfig.isHuggingChat}
42
- <a
43
- href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions/372"
44
- class="ml-auto dark:text-gray-400 dark:hover:text-gray-300"
45
- target="_blank"
46
- aria-label="Hub discussion about models"
47
- >
48
- <CarbonHelpFilled />
49
- </a>
50
- {/if}
51
- </div>
52
- <h2 class="text-gray-500">All models available on {publicConfig.PUBLIC_APP_NAME}</h2>
53
-
54
- <!-- Filter input -->
55
- <input
56
- type="search"
57
- bind:value={modelFilter}
58
- placeholder="Search by name"
59
- aria-label="Search models by name or id"
60
- class="mt-4 w-full rounded-3xl border border-gray-300 bg-white px-5 py-2 text-[15px]
61
- placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-300
62
- dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-gray-700"
63
- />
64
- <div class="mt-6 grid grid-cols-1 gap-3 sm:gap-5 xl:grid-cols-2">
65
- {#each data.models
66
- .filter((el) => !el.unlisted)
67
- .filter((el) => {
68
- const haystack = normalize(`${el.id} ${el.name ?? ""} ${el.displayName ?? ""}`);
69
- return queryTokens.every((q) => haystack.includes(q));
70
- }) as model, index (model.id)}
71
- <a
72
- href="{base}/models/{model.id}"
73
- aria-label="Model card"
74
- class="relative flex flex-col gap-2 overflow-hidden rounded-xl border bg-gray-50/50 px-6 py-5 shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
75
- class:active-model={model.id === $settings.activeModel}
76
- >
77
- <div class="flex items-center justify-between gap-1">
78
- {#if model.logoUrl}
79
- <img
80
- class="aspect-square size-6 rounded border bg-white dark:border-gray-700"
81
- src={model.logoUrl}
82
- alt="{model.displayName} logo"
83
- />
84
- {:else}
85
- <div
86
- class="size-6 rounded border border-transparent bg-gray-300 dark:bg-gray-800"
87
- aria-hidden="true"
88
- ></div>
89
- {/if}
90
- <div class="flex items-center gap-1">
91
- {#if $settings.multimodalOverrides?.[model.id] ?? model.multimodal}
92
- <span
93
- title="This model is multimodal and supports image inputs natively."
94
- class="ml-auto flex size-[21px] items-center justify-center rounded-lg border border-blue-700 dark:border-blue-500"
95
- aria-label="Model is multimodal"
96
- role="img"
97
- >
98
- <CarbonView class="text-xxs text-blue-700 dark:text-blue-500" />
99
- </span>
100
- {/if}
101
- {#if model.reasoning}
102
- <span
103
- title="This model supports reasoning."
104
- class="ml-auto grid size-[21px] place-items-center rounded-lg border border-purple-300 dark:border-purple-700"
105
- aria-label="Model supports reasoning"
106
- role="img"
107
- >
108
- <svg
109
- xmlns="http://www.w3.org/2000/svg"
110
- width="14"
111
- height="14"
112
- viewBox="0 0 32 32"
113
- >
114
- <path
115
- class="stroke-purple-700"
116
- style="stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 50;"
117
- d="M16 6v3.33M16 6c0-2.65 3.25-4.3 5.4-2.62 1.2.95 1.6 2.65.95 4.04a3.63 3.63 0 0 1 4.61.16 3.45 3.45 0 0 1 .46 4.37 5.32 5.32 0 0 1 1.87 4.75c-.22 1.66-1.39 3.6-3.07 4.14M16 6c0-2.65-3.25-4.3-5.4-2.62a3.37 3.37 0 0 0-.95 4.04 3.65 3.65 0 0 0-4.6.16 3.37 3.37 0 0 0-.49 4.27 5.57 5.57 0 0 0-1.85 4.85 5.3 5.3 0 0 0 3.07 4.15M16 9.33v17.34m0-17.34c0 2.18 1.82 4 4 4m6.22 7.5c.67 1.3.56 2.91-.27 4.11a4.05 4.05 0 0 1-4.62 1.5c0 1.53-1.05 2.9-2.66 2.9A2.7 2.7 0 0 1 16 26.66m10.22-5.83a4.05 4.05 0 0 0-3.55-2.17m-16.9 2.18a4.05 4.05 0 0 0 .28 4.1c1 1.44 2.92 2.09 4.59 1.5 0 1.52 1.12 2.88 2.7 2.88A2.7 2.7 0 0 0 16 26.67M5.78 20.85a4.04 4.04 0 0 1 3.55-2.18"
118
- />
119
- </svg>
120
- </span>
121
- {/if}
122
- {#if model.id === $settings.activeModel}
123
- <span
124
- class="rounded-full bg-black px-2 py-0.5 text-xs text-white dark:bg-white dark:text-black"
125
- >
126
- Active
127
- </span>
128
- {:else if index === 0 && model.id === "omni"}
129
- <span
130
- class="rounded-full border border-gray-300 px-2 py-0.5 text-xs text-gray-500 dark:border-gray-500 dark:text-gray-400"
131
- >
132
- Default
133
- </span>
134
- {/if}
135
- </div>
136
- </div>
137
- <span class="flex items-center gap-2 font-semibold">
138
- {model.displayName}
139
- </span>
140
- <span class="line-clamp-4 whitespace-pre-wrap text-sm text-gray-500 dark:text-gray-400">
141
- {model.description || "-"}
142
- </span>
143
- </a>
144
- {/each}
145
- </div>
146
- </div>
147
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/routes/personas/+page.svelte DELETED
@@ -1,514 +0,0 @@
1
- <script lang="ts">
2
- import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
3
- import { useSettingsStore } from "$lib/stores/settings";
4
- import type { Persona } from "$lib/types/Persona";
5
- import { page } from "$app/state";
6
- import Modal from "$lib/components/Modal.svelte";
7
- import CarbonEdit from "~icons/carbon/edit";
8
-
9
- const publicConfig = usePublicConfig();
10
- const settings = useSettingsStore();
11
-
12
- let clickTimeout: number | null = null;
13
- let showCloseConfirm = $state(false);
14
-
15
- // Local filter state for persona search (hyphen/space insensitive)
16
- let personaFilter = $state("");
17
- const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ");
18
- let queryTokens = $derived(normalize(personaFilter).trim().split(/\s+/).filter(Boolean));
19
- let filtered = $derived(
20
- $settings.personas.filter((p: Persona) => {
21
- const haystack = normalize(`${p.name} ${p.age ?? ""} ${p.gender ?? ""} ${p.jobSector ?? ""} ${p.stance ?? ""}`);
22
- return queryTokens.every((q) => haystack.includes(q));
23
- })
24
- );
25
-
26
- function togglePersona(personaId: string) {
27
- const isActive = $settings.activePersonas.includes(personaId);
28
- if (isActive) {
29
- // Prevent deactivating the last active persona
30
- if ($settings.activePersonas.length === 1) {
31
- alert("At least one persona must be active.");
32
- return;
33
- }
34
- // Deactivate: remove from array
35
- settings.instantSet({ activePersonas: $settings.activePersonas.filter(id => id !== personaId) });
36
- } else {
37
- // Activate: add to array
38
- settings.instantSet({ activePersonas: [...$settings.activePersonas, personaId] });
39
- }
40
- }
41
-
42
- function handleCardClick(personaId: string) {
43
- if (clickTimeout) return; // waiting for dblclick
44
- clickTimeout = window.setTimeout(() => {
45
- clickTimeout = null;
46
- openEdit(personaId);
47
- }, 220);
48
- }
49
-
50
- function handleCardDblClick(personaId: string) {
51
- if (clickTimeout) {
52
- clearTimeout(clickTimeout);
53
- clickTimeout = null;
54
- }
55
- togglePersona(personaId);
56
- }
57
-
58
- // Edit modal state
59
- let editingPersonaId = $state<string | null>(null);
60
- let editingPersona = $derived(
61
- $settings.personas.find((p) => p.id === editingPersonaId) ?? null
62
- );
63
- let editableName = $state("");
64
- let editableAge = $state("");
65
- let editableGender = $state("");
66
- let editableJobSector = $state("");
67
- let editableStance = $state("");
68
- let editableCommunicationStyle = $state("");
69
- let editableGoalInDebate = $state("");
70
- let editableIncomeBracket = $state("");
71
- let editablePoliticalLeanings = $state("");
72
- let editableGeographicContext = $state("");
73
-
74
- $effect(() => {
75
- if (editingPersona) {
76
- editableName = editingPersona.name;
77
- editableAge = editingPersona.age;
78
- editableGender = editingPersona.gender;
79
- editableJobSector = editingPersona.jobSector || "";
80
- editableStance = editingPersona.stance || "";
81
- editableCommunicationStyle = editingPersona.communicationStyle || "";
82
- editableGoalInDebate = editingPersona.goalInDebate || "";
83
- editableIncomeBracket = editingPersona.incomeBracket || "";
84
- editablePoliticalLeanings = editingPersona.politicalLeanings || "";
85
- editableGeographicContext = editingPersona.geographicContext || "";
86
- }
87
- });
88
-
89
- function openEdit(personaId: string) {
90
- editingPersonaId = personaId;
91
- }
92
-
93
- function closeEdit() {
94
- editingPersonaId = null;
95
- }
96
-
97
- function saveEdit() {
98
- if (!editingPersona) return;
99
- $settings.personas = $settings.personas.map((p) =>
100
- p.id === editingPersona.id
101
- ? {
102
- ...p,
103
- name: editableName,
104
- age: editableAge,
105
- gender: editableGender,
106
- jobSector: editableJobSector,
107
- stance: editableStance,
108
- communicationStyle: editableCommunicationStyle,
109
- goalInDebate: editableGoalInDebate,
110
- incomeBracket: editableIncomeBracket,
111
- politicalLeanings: editablePoliticalLeanings,
112
- geographicContext: editableGeographicContext,
113
- updatedAt: new Date(),
114
- }
115
- : p
116
- );
117
- closeEdit();
118
- }
119
-
120
- function toggleEditingPersona() {
121
- if (!editingPersona) return;
122
- const id = editingPersona.id;
123
- saveEdit();
124
- togglePersona(id);
125
- }
126
-
127
- function deleteEditingPersona() {
128
- if (!editingPersona) return;
129
- if ($settings.personas.length === 1) return alert("Cannot delete the last persona.");
130
- if ($settings.activePersonas.includes(editingPersona.id))
131
- return alert("Cannot delete an active persona. Deactivate it first.");
132
- if (confirm(`Delete "${editingPersona.name}"?`)) {
133
- $settings.personas = $settings.personas.filter((p) => p.id !== editingPersona!.id);
134
- closeEdit();
135
- }
136
- }
137
-
138
- // Function to show datalist on focus
139
- function showDatalist(event: FocusEvent) {
140
- const input = event.target as HTMLInputElement;
141
- // Dispatch a synthetic input event to trigger the datalist dropdown
142
- input.dispatchEvent(new Event('input', { bubbles: true }));
143
- }
144
- </script>
145
-
146
- <svelte:head>
147
- <title>{publicConfig.PUBLIC_APP_NAME} - Personas</title>
148
- <meta property="og:title" content="Personas" />
149
- <meta property="og:url" content={page.url.href} />
150
- </svelte:head>
151
-
152
- <div class="scrollbar-custom h-full overflow-y-auto py-12 max-sm:pt-8 md:py-24">
153
- <div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]">
154
- <div class="flex items-center">
155
- <h1 class="text-2xl font-bold">Personas</h1>
156
- </div>
157
- <h2 class="text-gray-500">All personas available on {publicConfig.PUBLIC_APP_NAME}</h2>
158
-
159
- <!-- Filter input -->
160
- <input
161
- type="search"
162
- bind:value={personaFilter}
163
- placeholder="Search by name, age, gender, job sector, or stance"
164
- aria-label="Search personas"
165
- class="mt-4 w-full rounded-3xl border border-gray-300 bg-white px-5 py-2 text-[15px]
166
- placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-300
167
- dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-gray-700"
168
- />
169
-
170
- <div class="mt-6 grid grid-cols-1 gap-3 sm:gap-5 xl:grid-cols-2">
171
- {#each filtered as persona (persona.id)}
172
- <div
173
- role="button"
174
- tabindex="0"
175
- onclick={() => handleCardClick(persona.id)}
176
- ondblclick={() => handleCardDblClick(persona.id)}
177
- onkeydown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleCardClick(persona.id); } }}
178
- aria-label={`Open persona ${persona.name}`}
179
- class="group relative flex min-h-[112px] flex-col gap-2 overflow-hidden rounded-xl bg-gray-50/50 px-6 py-5 text-left shadow hover:bg-gray-50 hover:shadow-inner dark:bg-gray-950/20 dark:hover:bg-gray-950/40 {$settings.activePersonas.includes(persona.id) ? 'border-2 border-black dark:border-white' : 'border border-gray-800/70'}"
180
- >
181
- <div class="flex items-center justify-between gap-1">
182
- <span class="flex items-center gap-2 font-semibold">{persona.name}</span>
183
- {#if $settings.activePersonas.includes(persona.id)}
184
- <div class="size-2.5 rounded-full bg-black dark:bg-white" title="Active persona"></div>
185
- {/if}
186
- </div>
187
- <div class="text-sm text-gray-600 dark:text-gray-300">
188
- {#if persona.age || persona.gender}
189
- <div class="mb-1">
190
- {#if persona.age}<span>{persona.age}</span>{/if}
191
- {#if persona.age && persona.gender}<span class="mx-1 text-gray-400">•</span>{/if}
192
- {#if persona.gender}<span>{persona.gender}</span>{/if}
193
- </div>
194
- {/if}
195
- {#if persona.jobSector || persona.stance}
196
- <div>
197
- {#if persona.jobSector}<span class="font-medium">{persona.jobSector}</span>{/if}
198
- {#if persona.jobSector && persona.stance}<span class="mx-1 text-gray-400">•</span>{/if}
199
- {#if persona.stance}<span class="italic">{persona.stance}</span>{/if}
200
- </div>
201
- {/if}
202
- </div>
203
- </div>
204
- {/each}
205
- </div>
206
- </div>
207
- </div>
208
-
209
- {#if editingPersona}
210
- <Modal onclose={() => {
211
- const dirty = editingPersona && (
212
- editableName !== editingPersona.name ||
213
- editableAge !== editingPersona.age ||
214
- editableGender !== editingPersona.gender ||
215
- editableJobSector !== (editingPersona.jobSector || "") ||
216
- editableStance !== (editingPersona.stance || "") ||
217
- editableCommunicationStyle !== (editingPersona.communicationStyle || "") ||
218
- editableGoalInDebate !== (editingPersona.goalInDebate || "") ||
219
- editableIncomeBracket !== (editingPersona.incomeBracket || "") ||
220
- editablePoliticalLeanings !== (editingPersona.politicalLeanings || "") ||
221
- editableGeographicContext !== (editingPersona.geographicContext || "")
222
- );
223
- if (!dirty) return closeEdit();
224
- showCloseConfirm = true;
225
- }} width="w-full !max-w-4xl">
226
- <div class="scrollbar-custom flex h-full max-h-[85vh] w-full flex-col gap-5 overflow-y-auto p-6">
227
- <div class="text-xl font-semibold text-gray-800 dark:text-gray-200">Edit Persona</div>
228
-
229
- <!-- Group 1: Core Identity -->
230
- <div>
231
- <h3 class="mb-3 text-sm font-semibold text-gray-800 dark:text-gray-200">Core Identity</h3>
232
- <div class="grid grid-cols-1 gap-4 md:grid-cols-3 md:gap-6">
233
- <div class="flex flex-col gap-2">
234
- <label for="edit-name" class="text-sm font-medium text-gray-700 dark:text-gray-300">
235
- Name <span class="text-red-500">*</span>
236
- </label>
237
- <input
238
- id="edit-name"
239
- type="text"
240
- bind:value={editableName}
241
- required
242
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
243
- maxlength="100"
244
- />
245
- </div>
246
-
247
- <div class="flex flex-col gap-2">
248
- <label for="edit-age" class="text-sm font-medium text-gray-700 dark:text-gray-300">
249
- Age <span class="text-red-500">*</span>
250
- </label>
251
- <input
252
- id="edit-age"
253
- type="text"
254
- list="edit-age-options"
255
- bind:value={editableAge}
256
- onfocus={showDatalist}
257
- required
258
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
259
- maxlength="50"
260
- />
261
- <datalist id="edit-age-options">
262
- <option value="18-25">18-25</option>
263
- <option value="26-35">26-35</option>
264
- <option value="36-45">36-45</option>
265
- <option value="46-55">46-55</option>
266
- <option value="56-65">56-65</option>
267
- <option value="66+">66+</option>
268
- </datalist>
269
- </div>
270
-
271
- <div class="flex flex-col gap-2">
272
- <label for="edit-gender" class="text-sm font-medium text-gray-700 dark:text-gray-300">
273
- Gender <span class="text-red-500">*</span>
274
- </label>
275
- <input
276
- id="edit-gender"
277
- type="text"
278
- list="edit-gender-options"
279
- bind:value={editableGender}
280
- onfocus={showDatalist}
281
- required
282
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
283
- maxlength="50"
284
- />
285
- <datalist id="edit-gender-options">
286
- <option value="Male">Male</option>
287
- <option value="Female">Female</option>
288
- <option value="Prefer not to say">Prefer not to say</option>
289
- </datalist>
290
- </div>
291
- </div>
292
- </div>
293
-
294
- <!-- Group 2: Professional & Stance -->
295
- <div>
296
- <h3 class="mb-3 text-sm font-semibold text-gray-800 dark:text-gray-200">Professional & Stance</h3>
297
- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
298
- <div class="flex flex-col gap-2">
299
- <label for="edit-job-sector" class="text-sm font-medium text-gray-700 dark:text-gray-300">
300
- Job Sector
301
- </label>
302
- <input
303
- id="edit-job-sector"
304
- type="text"
305
- list="edit-job-sector-options"
306
- bind:value={editableJobSector}
307
- onfocus={showDatalist}
308
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
309
- maxlength="200"
310
- />
311
- <datalist id="edit-job-sector-options">
312
- <option value="Healthcare provider">Healthcare provider</option>
313
- <option value="Small business owner">Small business owner</option>
314
- <option value="Tech worker">Tech worker</option>
315
- <option value="Teacher">Teacher</option>
316
- <option value="Unemployed/Retired">Unemployed/Retired</option>
317
- <option value="Government worker">Government worker</option>
318
- <option value="Student">Student</option>
319
- </datalist>
320
- </div>
321
-
322
- <div class="flex flex-col gap-2">
323
- <label for="edit-stance" class="text-sm font-medium text-gray-700 dark:text-gray-300">
324
- Stance
325
- </label>
326
- <input
327
- id="edit-stance"
328
- type="text"
329
- list="edit-stance-options"
330
- bind:value={editableStance}
331
- onfocus={showDatalist}
332
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
333
- maxlength="200"
334
- />
335
- <datalist id="edit-stance-options">
336
- <option value="In Favor of Medicare for All">In Favor of Medicare for All</option>
337
- <option value="Hardline Insurance Advocate">Hardline Insurance Advocate</option>
338
- <option value="Improvement of Current System">Improvement of Current System</option>
339
- <option value="Public Option Supporter">Public Option Supporter</option>
340
- <option value="Status Quo">Status Quo</option>
341
- </datalist>
342
- </div>
343
- </div>
344
- </div>
345
-
346
- <!-- Group 3: Communication & Goals -->
347
- <div>
348
- <h3 class="mb-3 text-sm font-semibold text-gray-800 dark:text-gray-200">Communication & Goals</h3>
349
- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
350
- <div class="flex flex-col gap-2">
351
- <label for="edit-communication-style" class="text-sm font-medium text-gray-700 dark:text-gray-300">
352
- Communication Style
353
- </label>
354
- <input
355
- id="edit-communication-style"
356
- type="text"
357
- list="edit-communication-style-options"
358
- bind:value={editableCommunicationStyle}
359
- onfocus={showDatalist}
360
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
361
- maxlength="200"
362
- />
363
- <datalist id="edit-communication-style-options">
364
- <option value="Direct">Direct</option>
365
- <option value="Technical/Jargon use">Technical/Jargon use</option>
366
- <option value="Informal">Informal</option>
367
- <option value="Philosophical">Philosophical</option>
368
- <option value="Pragmatic">Pragmatic</option>
369
- <option value="Conversational">Conversational</option>
370
- </datalist>
371
- </div>
372
-
373
- <div class="flex flex-col gap-2">
374
- <label for="edit-goal-in-debate" class="text-sm font-medium text-gray-700 dark:text-gray-300">
375
- Goal in the Debate
376
- </label>
377
- <input
378
- id="edit-goal-in-debate"
379
- type="text"
380
- list="edit-goal-in-debate-options"
381
- bind:value={editableGoalInDebate}
382
- onfocus={showDatalist}
383
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
384
- maxlength="300"
385
- />
386
- <datalist id="edit-goal-in-debate-options">
387
- <option value="Keep discussion grounded">Keep discussion grounded</option>
388
- <option value="Explain complexity">Explain complexity</option>
389
- <option value="Advocate for change">Advocate for change</option>
390
- <option value="Defend current system">Defend current system</option>
391
- <option value="Find compromise">Find compromise</option>
392
- </datalist>
393
- </div>
394
- </div>
395
- </div>
396
-
397
- <!-- Group 4: Demographics -->
398
- <div>
399
- <h3 class="mb-3 text-sm font-semibold text-gray-800 dark:text-gray-200">Demographics</h3>
400
- <div class="grid grid-cols-1 gap-4 md:grid-cols-3 md:gap-6">
401
- <div class="flex flex-col gap-2">
402
- <label for="edit-income-bracket" class="text-sm font-medium text-gray-700 dark:text-gray-300">
403
- Income Bracket
404
- </label>
405
- <input
406
- id="edit-income-bracket"
407
- type="text"
408
- list="edit-income-bracket-options"
409
- bind:value={editableIncomeBracket}
410
- onfocus={showDatalist}
411
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
412
- maxlength="100"
413
- />
414
- <datalist id="edit-income-bracket-options">
415
- <option value="Low">Low</option>
416
- <option value="Middle">Middle</option>
417
- <option value="High">High</option>
418
- <option value="Comfortable">Comfortable</option>
419
- <option value="Struggling">Struggling</option>
420
- </datalist>
421
- </div>
422
-
423
- <div class="flex flex-col gap-2">
424
- <label for="edit-political-leanings" class="text-sm font-medium text-gray-700 dark:text-gray-300">
425
- Political Leanings
426
- </label>
427
- <input
428
- id="edit-political-leanings"
429
- type="text"
430
- list="edit-political-leanings-options"
431
- bind:value={editablePoliticalLeanings}
432
- onfocus={showDatalist}
433
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
434
- maxlength="100"
435
- />
436
- <datalist id="edit-political-leanings-options">
437
- <option value="Liberal">Liberal</option>
438
- <option value="Conservative">Conservative</option>
439
- <option value="Moderate">Moderate</option>
440
- <option value="Libertarian">Libertarian</option>
441
- <option value="Non-affiliated">Non-affiliated</option>
442
- <option value="Progressive">Progressive</option>
443
- </datalist>
444
- </div>
445
-
446
- <div class="flex flex-col gap-2">
447
- <label for="edit-geographic-context" class="text-sm font-medium text-gray-700 dark:text-gray-300">
448
- Geographic Context
449
- </label>
450
- <input
451
- id="edit-geographic-context"
452
- type="text"
453
- list="edit-geographic-context-options"
454
- bind:value={editableGeographicContext}
455
- onfocus={showDatalist}
456
- class="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm transition-colors focus:bg-white focus:outline-none dark:border-gray-600 dark:focus:bg-gray-900"
457
- maxlength="100"
458
- />
459
- <datalist id="edit-geographic-context-options">
460
- <option value="Rural">Rural</option>
461
- <option value="Urban">Urban</option>
462
- <option value="Suburban">Suburban</option>
463
- </datalist>
464
- </div>
465
- </div>
466
- </div>
467
-
468
- <div class="flex flex-wrap gap-2 pt-2">
469
- <button
470
- class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
471
- onclick={toggleEditingPersona}
472
- >
473
- {$settings.activePersonas.includes(editingPersona.id) ? "Deactivate" : "Activate"}
474
- </button>
475
- <button
476
- class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
477
- onclick={saveEdit}
478
- >
479
- Save
480
- </button>
481
- <button
482
- class="ml-auto flex items-center gap-2 rounded-lg border border-red-300 bg-white px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50 dark:border-red-700 dark:bg-gray-800 dark:hover:bg-red-900/20"
483
- onclick={deleteEditingPersona}
484
- >
485
- Delete
486
- </button>
487
- </div>
488
- </div>
489
- </Modal>
490
- {/if}
491
-
492
- {#if editingPersona && showCloseConfirm}
493
- <Modal onclose={() => (showCloseConfirm = false)} width="w-full !max-w-sm">
494
- <div class="flex w-full flex-col gap-4 p-4">
495
- <div class="text-base font-semibold text-gray-800 dark:text-gray-200">Unsaved changes</div>
496
- <p class="text-sm text-gray-600 dark:text-gray-300">Save your changes before closing?</p>
497
- <div class="mt-2 flex gap-2">
498
- <button class="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300" onclick={() => { showCloseConfirm = false; saveEdit(); }}>
499
- Save
500
- </button>
501
- <button class="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300" onclick={() => { showCloseConfirm = false; closeEdit(); }}>
502
- Discard
503
- </button>
504
- <button class="ml-auto rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300" onclick={() => { showCloseConfirm = false; }}>
505
- Cancel
506
- </button>
507
- </div>
508
- </div>
509
- </Modal>
510
- {/if}
511
-
512
- <!-- merged into top script -->
513
-
514
-