Spaces:
Runtime error
Runtime error
Andrew commited on
Commit ·
5510827
1
Parent(s): 6900c11
Add rotating INIT_PROMPTS placeholder in chat input
Browse files- .env +3 -0
- src/lib/components/chat/ChatInput.svelte +57 -24
- src/lib/components/chat/ChatWindow.svelte +36 -16
- src/lib/server/api/routes/groups/misc.ts +10 -0
- src/lib/server/config.ts +2 -1
- src/routes/+layout.ts +3 -0
- src/routes/+page.svelte +1 -0
- src/routes/conversation/[id]/+page.svelte +1 -0
- src/routes/models/[...model]/+page.svelte +1 -0
.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 |
-
<
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 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 |
/>
|