Skip to content

Commit

Permalink
feat: Session: Use existing knowledge when submitting a prompt (#227)
Browse files Browse the repository at this point in the history
Co-authored-by: Fernando Maclen <fmaclen@gmail.com>
  • Loading branch information
GregoMac1 and fmaclen authored Nov 13, 2024
1 parent 07162ca commit 362da02
Show file tree
Hide file tree
Showing 20 changed files with 541 additions and 130 deletions.
1 change: 1 addition & 0 deletions src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const en = {
settings: 'Settings',
somethingWentWrong: 'Sorry, something went wrong',
stop: 'Stop sequence',
stopCompletion: 'Stop completion',
stopResponse: 'Stop response',
success: 'Success',
system: 'System',
Expand Down
8 changes: 8 additions & 0 deletions src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ type RootTranslation = {
* S​t​o​p​ ​s​e​q​u​e​n​c​e
*/
stop: string
/**
* S​t​o​p​ ​c​o​m​p​l​e​t​i​o​n
*/
stopCompletion: string
/**
* S​t​o​p​ ​r​e​s​p​o​n​s​e
*/
Expand Down Expand Up @@ -1043,6 +1047,10 @@ The completion in progress will stop
* Stop sequence
*/
stop: () => LocalizedString
/**
* Stop completion
*/
stopCompletion: () => LocalizedString
/**
* Stop response
*/
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ButtonSubmit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
export let hasMetaKey: boolean = false;
</script>

<Button class="w-full text-left" on:click={handleSubmit} {disabled}>
<Button class="text-left" on:click={handleSubmit} {disabled}>
<slot />

{#if browser}
Expand Down
6 changes: 4 additions & 2 deletions src/lib/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export function formatSessionMetadata(session: Session) {
}

export function getSessionTitle(session: Session) {
const hasKnowledge = session.messages[0]?.knowledge;
return hasKnowledge ? session.messages[1]?.content : session.messages[0]?.content;
const firstUserMessage = session.messages.find(
(m) => m.role === 'user' && m.content && !m.knowledge
);
return firstUserMessage?.content || '';
}
6 changes: 3 additions & 3 deletions src/routes/motd/motd.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
`2024-10-25`
`2024-11-12`

### Message of the day

# Welcome to Hollama: a simple web interface for [Ollama](https://ollama.ai)

#### What's new?

**OpenAI models** ✨ are now _(optionally)_ available in Sessions.
Set your own API key in [Settings](/settings)
- **[Knowledge](/knowledge)** can now be used as context at any point in a Session.
- **OpenAI models** ✨ are now _(optionally)_ available in Sessions. Set your own API key in [Settings](/settings)

#### Previously, in Hollama

Expand Down
10 changes: 2 additions & 8 deletions src/routes/sessions/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import type { PageData } from './$types';
import Controls from './Controls.svelte';
import Messages from './Messages.svelte';
import PromptEditor from './Prompt.svelte';
import Prompt from './Prompt.svelte';
const shouldConfirmDeletion = writable(false);
Expand Down Expand Up @@ -221,13 +221,7 @@
</div>
{/if}

<PromptEditor
bind:model={$session.model}
{editor}
{handleSubmit}
{stopCompletion}
{scrollToBottom}
/>
<Prompt bind:session {editor} {handleSubmit} {stopCompletion} {scrollToBottom} />
</div>

<style lang="postcss">
Expand Down
148 changes: 96 additions & 52 deletions src/routes/sessions/[id]/Article.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Brain, Pencil, RefreshCw } from 'lucide-svelte';
import { BrainIcon, Pencil, RefreshCw, Trash2 } from 'lucide-svelte';
import LL from '$i18n/i18n-svelte';
import Badge from '$lib/components/Badge.svelte';
Expand All @@ -14,62 +14,83 @@
export let retryIndex: number | undefined = undefined;
export let handleRetry: ((index: number) => void) | undefined = undefined;
export let handleEditMessage: ((message: Message) => void) | undefined = undefined;
export let handleDeleteAttachment: ((message: Message) => void) | undefined = undefined;
const isUserRole = message.role === 'user';
let isKnowledgeAttachment: boolean | undefined;
let isUserRole: boolean | undefined;
$: if (message) {
isKnowledgeAttachment = message.knowledge?.name !== undefined;
isUserRole = message.role === 'user' && !isKnowledgeAttachment;
}
</script>

<article class="article article--{message.role}">
<nav class="article__nav">
<div data-testid="session-role" class="article__role">
<Badge>
{#if isUserRole}
{$LL.you()}
{:else if message.role === 'assistant'}
{$LL.assistant()}
{:else}
{$LL.system()}
{/if}
</Badge>
</div>
<div class="article__interactive">
{#if retryIndex}
<Button
title={$LL.retry()}
variant="icon"
id="retry-index-{retryIndex}"
on:click={() => handleRetry && handleRetry(retryIndex)}
>
<RefreshCw class="base-icon" />
</Button>
{/if}
{#if isUserRole}
<Button
title={$LL.edit()}
variant="icon"
on:click={() => handleEditMessage && handleEditMessage(message)}
>
<Pencil class="base-icon" />
{#if isKnowledgeAttachment}
<article class="attachment">
<div class="attachment__content">
<div class="attachment__icon">
<BrainIcon class="base-icon" />
</div>
<div class="attachment__name">
<Button variant="link" href={generateNewUrl(Sitemap.KNOWLEDGE, message.knowledge?.id)}>
{message.knowledge?.name}
</Button>
{/if}
<ButtonCopy content={message.content} />
</div>
</div>
</nav>

<div class="markdown">
{#if message.knowledge}
<div class="attachment__interactive">
<Button
variant="outline"
href={generateNewUrl(Sitemap.KNOWLEDGE, message.knowledge.id)}
aria-label={$LL.goToKnowledge()}
variant="icon"
on:click={() => handleDeleteAttachment && handleDeleteAttachment(message)}
>
{message.knowledge.name}
<Brain class="base-icon -mr-1 ml-2" />
<Trash2 class="base-icon" />
</Button>
{:else if message.content}
<Markdown markdown={message.content} />
{/if}
</div>
</article>
</div>
</article>
{:else}
<article class="article article--{message.role}">
<nav class="article__nav">
<div data-testid="session-role" class="article__role">
<Badge>
{#if isUserRole}
{$LL.you()}
{:else if message.role === 'assistant'}
{$LL.assistant()}
{:else}
{$LL.system()}
{/if}
</Badge>
</div>
<div class="article__interactive">
{#if retryIndex}
<Button
title={$LL.retry()}
variant="icon"
id="retry-index-{retryIndex}"
on:click={() => handleRetry && handleRetry(retryIndex)}
>
<RefreshCw class="base-icon" />
</Button>
{/if}
{#if isUserRole}
<Button
title={$LL.edit()}
variant="icon"
on:click={() => handleEditMessage && handleEditMessage(message)}
>
<Pencil class="base-icon" />
</Button>
{/if}
<ButtonCopy content={message.content} />
</div>
</nav>

<div class="markdown">
{#if message.content}
<Markdown markdown={message.content} />
{/if}
</div>
</article>
{/if}

<style lang="postcss">
.article {
Expand All @@ -83,16 +104,20 @@
@apply border-transparent bg-shade-0;
}
.article__interactive {
@apply opacity-100;
.article__interactive,
.attachment__interactive {
@apply -mr-2 opacity-100;
@apply md:-mr-3;
@media (hover: hover) {
/* The interactive elements should be visible by default on mobile
and hidden by default on desktop. */
@apply opacity-0;
}
}
.article:hover .article__interactive {
.article:hover .article__interactive,
.attachment:hover .attachment__interactive {
@apply opacity-100;
}
Expand All @@ -104,4 +129,23 @@
.article__role {
@apply text-center text-xs font-bold uppercase leading-7;
}
.attachment {
@apply mx-auto mb-2 flex w-full max-w-[80ch] gap-2 rounded-md border border-shade-3;
@apply flex items-center justify-between px-3 py-1;
@apply md:px-4;
@apply lg:px-6;
}
.attachment__icon {
@apply text-muted;
}
.attachment__name {
@apply text-sm;
}
.attachment__content {
@apply flex items-center gap-2;
}
</style>
27 changes: 3 additions & 24 deletions src/routes/sessions/[id]/Controls.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
<script lang="ts">
import { Brain } from 'lucide-svelte';
import type { Writable } from 'svelte/store';
import LL from '$i18n/i18n-svelte';
import Button from '$lib/components/Button.svelte';
import { generateNewUrl } from '$lib/components/ButtonNew';
import FieldCheckbox from '$lib/components/FieldCheckbox.svelte';
import FieldInput from '$lib/components/FieldInput.svelte';
import FieldSelect from '$lib/components/FieldSelect.svelte';
import Fieldset from '$lib/components/Fieldset.svelte';
import P from '$lib/components/P.svelte';
import { loadKnowledge } from '$lib/knowledge';
import { knowledgeStore } from '$lib/localStorage';
import type { Session } from '$lib/sessions';
import { Sitemap } from '$lib/sitemap';
import KnowledgeSelect from './KnowledgeSelect.svelte';
const DEFAULT_MIROSTAT = '0';
const DEFAULT_MIROSTAT_ETA = '0.1';
Expand Down Expand Up @@ -68,25 +65,7 @@
<div class="controls">
<Fieldset>
<P><strong>{$LL.systemPrompt()}</strong></P>
<FieldSelect
label={$LL.knowledge()}
name="knowledge"
disabled={!$knowledgeStore.length}
placeholder={!$knowledgeStore.length ? $LL.emptyKnowledge() : ''}
options={$knowledgeStore?.map((k) => ({ value: k.id, label: k.name }))}
bind:value={knowledgeId}
>
<svelte:fragment slot="nav">
<Button
aria-label={$LL.newKnowledge()}
variant="outline"
href={generateNewUrl(Sitemap.KNOWLEDGE)}
class="h-full text-muted"
>
<Brain class="base-icon" />
</Button>
</svelte:fragment>
</FieldSelect>
<KnowledgeSelect bind:value={knowledgeId} bind:options={$knowledgeStore} showNav={true} />
</Fieldset>

<Fieldset>
Expand Down
43 changes: 43 additions & 0 deletions src/routes/sessions/[id]/KnowledgeSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import { Brain } from 'lucide-svelte';
import LL from '$i18n/i18n-svelte';
import Button from '$lib/components/Button.svelte';
import { generateNewUrl } from '$lib/components/ButtonNew';
import FieldSelect from '$lib/components/FieldSelect.svelte';
import { type Knowledge } from '$lib/knowledge';
import { Sitemap } from '$lib/sitemap';
export let value: string | undefined = undefined;
export let options: Knowledge[] = [];
export let showNav: boolean = false;
export let showLabel: boolean = true;
export let allowClear: boolean = true;
export let onChange: ((knowledgeId: string) => void) | undefined = undefined;
export let fieldId: string = 'knowledge';
</script>

<FieldSelect
bind:value
label={$LL.knowledge()}
isLabelVisible={showLabel}
name={fieldId}
disabled={!options.length}
placeholder={!options.length ? $LL.emptyKnowledge() : !showLabel ? $LL.knowledge() : ''}
options={options?.map((k) => ({ value: k.id, label: k.name }))}
onChange={(option) => onChange?.(option.value)}
{allowClear}
>
<svelte:fragment slot="nav">
{#if showNav}
<Button
aria-label={$LL.newKnowledge()}
variant="outline"
href={generateNewUrl(Sitemap.KNOWLEDGE)}
class="h-full text-muted"
>
<Brain class="base-icon" />
</Button>
{/if}
</svelte:fragment>
</FieldSelect>
8 changes: 7 additions & 1 deletion src/routes/sessions/[id]/Messages.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import LL from '$i18n/i18n-svelte';
import EmptyMessage from '$lib/components/EmptyMessage.svelte';
import type { Editor, Message, Session } from '$lib/sessions';
import { saveSession, type Editor, type Message, type Session } from '$lib/sessions';
import Article from './Article.svelte';
Expand All @@ -17,6 +17,11 @@
$editor.prompt = message.content;
$editor.promptTextarea?.focus();
}
function handleDeleteAttachment(message: Message) {
$session.messages = $session.messages.filter((m) => m !== message);
saveSession($session);
}
</script>

{#if $editor.isNewSession}
Expand All @@ -30,6 +35,7 @@
retryIndex={['assistant', 'system'].includes(message.role) ? i : undefined}
{handleRetry}
handleEditMessage={() => handleEditMessage(message)}
handleDeleteAttachment={() => handleDeleteAttachment(message)}
/>
{/key}
{/each}
Expand Down
Loading

0 comments on commit 362da02

Please sign in to comment.