Andrew commited on
Commit
5510827
·
1 Parent(s): 6900c11

Add rotating INIT_PROMPTS placeholder in chat input

Browse files
.env CHANGED
@@ -34,6 +34,9 @@ MONGO_STORAGE_PATH= # where is the db folder stored
34
 
35
  REASONING_SUMMARY=false # Change this to false to disable reasoning summary
36
 
 
 
 
37
  ## Models overrides
38
  MODELS=
39
 
 
34
 
35
  REASONING_SUMMARY=false # Change this to false to disable reasoning summary
36
 
37
+ # Comma-separated list of placeholder prompts that rotate in the chat input when empty
38
+ INIT_PROMPTS=Should the government subsidize healthcare for all citizens?,What are the trade-offs of a single-payer healthcare system?,Is the Affordable Care Act helping or hurting middle-class families?,Should prescription drug prices be regulated by the government?,Is universal healthcare a right or a privilege?,How should we balance healthcare access with fiscal responsibility?,What role should private insurance play alongside public options?,Should preventive care be free for everyone regardless of income?
39
+
40
  ## Models overrides
41
  MODELS=
42
 
src/lib/components/chat/ChatInput.svelte CHANGED
@@ -1,5 +1,6 @@
1
  <script lang="ts">
2
  import { onMount, tick } from "svelte";
 
3
 
4
  import HoverTooltip from "$lib/components/HoverTooltip.svelte";
5
  import IconPaperclip from "$lib/components/icons/IconPaperclip.svelte";
@@ -12,9 +13,9 @@
12
  mimeTypes?: string[];
13
  value?: string;
14
  placeholder?: string;
 
15
  loading?: boolean;
16
  disabled?: boolean;
17
- // tools removed
18
  modelIsMultimodal?: boolean;
19
  children?: import("svelte").Snippet;
20
  onPaste?: (e: ClipboardEvent) => void;
@@ -27,6 +28,7 @@
27
  mimeTypes = [],
28
  value = $bindable(""),
29
  placeholder = "",
 
30
  loading = false,
31
  disabled = false,
32
 
@@ -37,6 +39,8 @@
37
  onsubmit,
38
  }: Props = $props();
39
 
 
 
40
  const onFileChange = async (e: Event) => {
41
  if (!e.target) return;
42
  const target = e.target as HTMLInputElement;
@@ -95,29 +99,48 @@
95
  </script>
96
 
97
  <div class="flex min-h-full flex-1 flex-col" onpaste={onPaste}>
98
- <textarea
99
- rows="1"
100
- tabindex="0"
101
- inputmode="text"
102
- class="scrollbar-custom max-h-[4lh] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent px-2.5 py-2.5 outline-none placeholder:text-gray-400 focus:ring-0 focus-visible:ring-0 dark:placeholder:text-gray-500 sm:px-3 md:max-h-[8lh]"
103
- class:text-gray-400={disabled}
104
- bind:value
105
- bind:this={textareaElement}
106
- onkeydown={handleKeydown}
107
- oncompositionstart={() => (isCompositionOn = true)}
108
- oncompositionend={() => (isCompositionOn = false)}
109
- oninput={adjustTextareaHeight}
110
- onbeforeinput={(ev) => {
111
- if (page.data.loginRequired) {
112
- ev.preventDefault();
113
- $loginModalOpen = true;
114
- }
115
- }}
116
- {placeholder}
117
- {disabled}
118
- onfocus={() => (focused = true)}
119
- onblur={() => (focused = false)}
120
- ></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  {#if !showNoTools}
123
  <div
@@ -171,4 +194,14 @@
171
  line-height: 1.5;
172
  font-size: 16px;
173
  }
 
 
 
 
 
 
 
 
 
 
174
  </style>
 
1
  <script lang="ts">
2
  import { onMount, tick } from "svelte";
3
+ import { fly, fade } from "svelte/transition";
4
 
5
  import HoverTooltip from "$lib/components/HoverTooltip.svelte";
6
  import IconPaperclip from "$lib/components/icons/IconPaperclip.svelte";
 
13
  mimeTypes?: string[];
14
  value?: string;
15
  placeholder?: string;
16
+ animatePlaceholder?: boolean;
17
  loading?: boolean;
18
  disabled?: boolean;
 
19
  modelIsMultimodal?: boolean;
20
  children?: import("svelte").Snippet;
21
  onPaste?: (e: ClipboardEvent) => void;
 
28
  mimeTypes = [],
29
  value = $bindable(""),
30
  placeholder = "",
31
+ animatePlaceholder = false,
32
  loading = false,
33
  disabled = false,
34
 
 
39
  onsubmit,
40
  }: Props = $props();
41
 
42
+ let showAnimatedPlaceholder = $derived(animatePlaceholder && value === "");
43
+
44
  const onFileChange = async (e: Event) => {
45
  if (!e.target) return;
46
  const target = e.target as HTMLInputElement;
 
99
  </script>
100
 
101
  <div class="flex min-h-full flex-1 flex-col" onpaste={onPaste}>
102
+ <div class="relative w-full">
103
+ <textarea
104
+ rows="1"
105
+ tabindex="0"
106
+ inputmode="text"
107
+ class="scrollbar-custom max-h-[4lh] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent px-2.5 py-2.5 outline-none placeholder:text-gray-400 focus:ring-0 focus-visible:ring-0 dark:placeholder:text-gray-500 sm:px-3 md:max-h-[8lh]"
108
+ class:text-gray-400={disabled}
109
+ bind:value
110
+ bind:this={textareaElement}
111
+ onkeydown={handleKeydown}
112
+ oncompositionstart={() => (isCompositionOn = true)}
113
+ oncompositionend={() => (isCompositionOn = false)}
114
+ oninput={adjustTextareaHeight}
115
+ onbeforeinput={(ev) => {
116
+ if (page.data.loginRequired) {
117
+ ev.preventDefault();
118
+ $loginModalOpen = true;
119
+ }
120
+ }}
121
+ placeholder={showAnimatedPlaceholder ? "" : placeholder}
122
+ {disabled}
123
+ onfocus={() => (focused = true)}
124
+ onblur={() => (focused = false)}
125
+ ></textarea>
126
+
127
+ {#if showAnimatedPlaceholder}
128
+ <div
129
+ class="pointer-events-none absolute inset-0 overflow-hidden px-2.5 py-2.5 sm:px-3"
130
+ aria-hidden="true"
131
+ >
132
+ {#key placeholder}
133
+ <span
134
+ class="animated-placeholder absolute inset-0 px-2.5 py-2.5 text-gray-400 sm:px-3 dark:text-gray-500"
135
+ in:fly={{ y: 8, duration: 350, delay: 200 }}
136
+ out:fade={{ duration: 180 }}
137
+ >
138
+ {placeholder}
139
+ </span>
140
+ {/key}
141
+ </div>
142
+ {/if}
143
+ </div>
144
 
145
  {#if !showNoTools}
146
  <div
 
194
  line-height: 1.5;
195
  font-size: 16px;
196
  }
197
+
198
+ .animated-placeholder {
199
+ font-family: inherit;
200
+ font-size: 16px;
201
+ line-height: 1.5;
202
+ white-space: nowrap;
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
205
+ max-width: 100%;
206
+ }
207
  </style>
src/lib/components/chat/ChatWindow.svelte CHANGED
@@ -58,6 +58,7 @@
58
  personaName: string;
59
  } | null;
60
  files?: File[];
 
61
  metacognitiveConfig?: MetacognitiveConfig;
62
  metacognitiveState?: {
63
  targetFrequency?: number;
@@ -85,6 +86,7 @@
85
  lockedPersonaId,
86
  branchState,
87
  files = $bindable([]),
 
88
  metacognitiveConfig,
89
  metacognitiveState,
90
  onmessage,
@@ -142,6 +144,25 @@
142
  let editMsdgId: Message["id"] | null = $state(null);
143
  let pastedLongContent = $state(false);
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  const handleSubmit = () => {
146
  if (loading) return;
147
  onmessage?.(message);
@@ -609,22 +630,21 @@
609
  {#if lastIsError}
610
  <ChatInput value="Sorry, something went wrong. Please try again." disabled={true} />
611
  {:else}
612
- <ChatInput
613
- placeholder={isReadOnly
614
- ? "This conversation is read-only."
615
- : branchState
616
- ? `Branched from ${branchState.personaName}`
617
- : "Ask anything"}
618
- {loading}
619
- bind:value={message}
620
- bind:files
621
- mimeTypes={activeMimeTypes}
622
- onsubmit={handleSubmit}
623
- {onPaste}
624
- disabled={isReadOnly || lastIsError}
625
- {modelIsMultimodal}
626
- bind:focused
627
- />
628
  {/if}
629
 
630
  {#if loading}
 
58
  personaName: string;
59
  } | null;
60
  files?: File[];
61
+ initPrompts?: string[];
62
  metacognitiveConfig?: MetacognitiveConfig;
63
  metacognitiveState?: {
64
  targetFrequency?: number;
 
86
  lockedPersonaId,
87
  branchState,
88
  files = $bindable([]),
89
+ initPrompts = [],
90
  metacognitiveConfig,
91
  metacognitiveState,
92
  onmessage,
 
144
  let editMsdgId: Message["id"] | null = $state(null);
145
  let pastedLongContent = $state(false);
146
 
147
+ let initPromptIndex = $state(0);
148
+
149
+ $effect(() => {
150
+ if (initPrompts.length > 1) {
151
+ const interval = setInterval(() => {
152
+ initPromptIndex = (initPromptIndex + 1) % initPrompts.length;
153
+ }, 4000);
154
+ return () => clearInterval(interval);
155
+ }
156
+ });
157
+
158
+ let rotatingPlaceholder = $derived.by(() => {
159
+ if (messages.length > 0 || branchState) {
160
+ return branchState ? `Branched from ${branchState.personaName}` : "Ask anything";
161
+ }
162
+ if (initPrompts.length === 0) return "Ask anything";
163
+ return initPrompts[initPromptIndex];
164
+ });
165
+
166
  const handleSubmit = () => {
167
  if (loading) return;
168
  onmessage?.(message);
 
630
  {#if lastIsError}
631
  <ChatInput value="Sorry, something went wrong. Please try again." disabled={true} />
632
  {:else}
633
+ <ChatInput
634
+ placeholder={isReadOnly
635
+ ? "This conversation is read-only."
636
+ : rotatingPlaceholder}
637
+ animatePlaceholder={!isReadOnly && initPrompts.length > 1 && messages.length === 0 && !branchState}
638
+ {loading}
639
+ bind:value={message}
640
+ bind:files
641
+ mimeTypes={activeMimeTypes}
642
+ onsubmit={handleSubmit}
643
+ {onPaste}
644
+ disabled={isReadOnly || lastIsError}
645
+ {modelIsMultimodal}
646
+ bind:focused
647
+ />
 
648
  {/if}
649
 
650
  {#if loading}
src/lib/server/api/routes/groups/misc.ts CHANGED
@@ -24,6 +24,16 @@ export type ApiReturnType = Awaited<ReturnType<typeof Client.prototype.view_api>
24
  export const misc = new Elysia()
25
  .use(authPlugin)
26
  .get("/public-config", async () => config.getPublicConfig())
 
 
 
 
 
 
 
 
 
 
27
  .get("/metacognitive-config", async () => {
28
  const metacogConfig = getMetacognitiveConfig();
29
  return {
 
24
  export const misc = new Elysia()
25
  .use(authPlugin)
26
  .get("/public-config", async () => config.getPublicConfig())
27
+ .get("/init-prompts", async () => {
28
+ const raw = config.INIT_PROMPTS;
29
+ if (!raw || raw.trim() === "") return { prompts: [] };
30
+ return {
31
+ prompts: raw
32
+ .split(",")
33
+ .map((s: string) => s.trim())
34
+ .filter(Boolean),
35
+ };
36
+ })
37
  .get("/metacognitive-config", async () => {
38
  const metacogConfig = getMetacognitiveConfig();
39
  return {
src/lib/server/config.ts CHANGED
@@ -158,7 +158,8 @@ type ExtraConfigKeys =
158
  | "ALLOWED_MODELS"
159
  | "METACOGNITIVE_FREQUENCIES"
160
  | "METACOGNITIVE_PROMPTS_COMPREHENSION"
161
- | "METACOGNITIVE_PROMPTS_PERSPECTIVE";
 
162
 
163
  type ConfigProxy = ConfigManager & { [K in ConfigKey | ExtraConfigKeys]: string };
164
 
 
158
  | "ALLOWED_MODELS"
159
  | "METACOGNITIVE_FREQUENCIES"
160
  | "METACOGNITIVE_PROMPTS_COMPREHENSION"
161
+ | "METACOGNITIVE_PROMPTS_PERSPECTIVE"
162
+ | "INIT_PROMPTS";
163
 
164
  type ConfigProxy = ConfigManager & { [K in ConfigKey | ExtraConfigKeys]: string };
165
 
src/routes/+layout.ts CHANGED
@@ -17,6 +17,7 @@ export const load = async ({ depends, fetch, url }) => {
17
  featureFlags,
18
  conversationsData,
19
  metacognitiveConfig,
 
20
  ] = await Promise.all([
21
  client.user.settings.get().then(handleResponse),
22
  client.models.get().then(handleResponse),
@@ -26,6 +27,7 @@ export const load = async ({ depends, fetch, url }) => {
26
  client["feature-flags"].get().then(handleResponse),
27
  client.conversations.get({ query: { p: 0 } }).then(handleResponse),
28
  client["metacognitive-config"].get().then(handleResponse),
 
29
  ]);
30
 
31
  const defaultModel = models[0];
@@ -66,6 +68,7 @@ export const load = async ({ depends, fetch, url }) => {
66
  perspectivePrompts: string[];
67
  enabled: boolean;
68
  },
 
69
  ...featureFlags,
70
  };
71
  };
 
17
  featureFlags,
18
  conversationsData,
19
  metacognitiveConfig,
20
+ initPromptsData,
21
  ] = await Promise.all([
22
  client.user.settings.get().then(handleResponse),
23
  client.models.get().then(handleResponse),
 
27
  client["feature-flags"].get().then(handleResponse),
28
  client.conversations.get({ query: { p: 0 } }).then(handleResponse),
29
  client["metacognitive-config"].get().then(handleResponse),
30
+ client["init-prompts"].get().then(handleResponse),
31
  ]);
32
 
33
  const defaultModel = models[0];
 
68
  perspectivePrompts: string[];
69
  enabled: boolean;
70
  },
71
+ initPrompts: (initPromptsData as { prompts: string[] }).prompts,
72
  ...featureFlags,
73
  };
74
  };
src/routes/+page.svelte CHANGED
@@ -97,6 +97,7 @@ const publicConfig = usePublicConfig();
97
  {loading}
98
  {currentModel}
99
  models={data.models}
 
100
  bind:files
101
  />
102
  {:else}
 
97
  {loading}
98
  {currentModel}
99
  models={data.models}
100
+ initPrompts={data.initPrompts}
101
  bind:files
102
  />
103
  {:else}
src/routes/conversation/[id]/+page.svelte CHANGED
@@ -726,6 +726,7 @@ let branchSyncTimeout: ReturnType<typeof setTimeout> | null = null;
726
  preprompt={data.preprompt}
727
  personaId={(data as any).personaId}
728
  branchState={activeBranch}
 
729
  metacognitiveConfig={data.metacognitiveConfig}
730
  metacognitiveState={(data as any).metacognitiveState}
731
  bind:files
 
726
  preprompt={data.preprompt}
727
  personaId={(data as any).personaId}
728
  branchState={activeBranch}
729
+ initPrompts={data.initPrompts}
730
  metacognitiveConfig={data.metacognitiveConfig}
731
  metacognitiveState={(data as any).metacognitiveState}
732
  bind:files
src/routes/models/[...model]/+page.svelte CHANGED
@@ -90,5 +90,6 @@
90
  {loading}
91
  currentModel={findCurrentModel(data.models, data.oldModels, modelId)}
92
  models={data.models}
 
93
  bind:files
94
  />
 
90
  {loading}
91
  currentModel={findCurrentModel(data.models, data.oldModels, modelId)}
92
  models={data.models}
93
+ initPrompts={data.initPrompts}
94
  bind:files
95
  />