Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/app.css",
"baseColor": "gray"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry"
}
5 changes: 5 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@inlang/paraglide-js": "^2.2.0",
"@langchain/core": "^0.3.78",
"@langchain/langgraph-sdk": "^0.1.9",
"@lucide/svelte": "^0.554.0",
"@playwright/test": "^1.49.1",
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.16.0",
Expand All @@ -34,6 +35,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/svelte": "^5.2.4",
"@vitest/coverage-v8": "^3.2.4",
"clsx": "^2.1.1",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
Expand All @@ -49,7 +51,10 @@
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"svelte-exmarkdown": "^5.0.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^3.1.1",
"tailwindcss": "^4.0.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^6.2.6",
Expand Down
6 changes: 6 additions & 0 deletions apps/frontend/src/app.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@custom-variant dark (&:where(.dark, .dark *));

@theme {
--color-primary: #ef562f;
--color-primary-50: #fff5f2;
--color-primary-100: #fff1ee;
--color-primary-200: #ffe4de;
Expand All @@ -19,6 +20,7 @@
--color-primary-800: #cc4522;
--color-primary-900: #a5371b;

--color-secondary: #0284c7;
--color-secondary-50: #f0f9ff;
--color-secondary-100: #e0f2fe;
--color-secondary-200: #bae6fd;
Expand All @@ -29,6 +31,10 @@
--color-secondary-700: #0369a1;
--color-secondary-800: #075985;
--color-secondary-900: #0c4a6e;

/* Semantic foreground colors for text on colored backgrounds */
--color-primary-foreground: #ffffff;
--color-secondary-foreground: #ffffff;
}

@source "../node_modules/flowbite-svelte/dist";
Expand Down
6 changes: 4 additions & 2 deletions apps/frontend/src/lib/auth/components/SignInButton.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<script>
import { SignIn } from '@auth/sveltekit/components';
import { Button } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';
import { m } from '$lib/paraglide/messages.js';
</script>

<SignIn provider="oidc">
<Button slot="submitButton" size="sm" tag="span">{m.auth_sign_in()}</Button>
<Button variant="default" slot="submitButton" size="sm">
{m.auth_sign_in()}
</Button>
</SignIn>
9 changes: 5 additions & 4 deletions apps/frontend/src/lib/components/AIMessageActions.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Button, Tooltip, Clipboard } from 'flowbite-svelte';
import { Tooltip, Clipboard } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';
import { ArrowsRepeatOutline, CheckOutline, ClipboardCleanSolid } from 'flowbite-svelte-icons';
import type { BaseMessage } from '$lib/langgraph/types';
import * as m from '$lib/paraglide/messages.js';
Expand Down Expand Up @@ -29,11 +30,11 @@
<Button
onclick={() => onRegenerate?.(message)}
class="p-1.5!"
color="alternative"
size="xs"
variant="ghost"
size="sm"
title={m.message_regenerate()}
>
<ArrowsRepeatOutline size="xs" />
<ArrowsRepeatOutline />
</Button>
<Tooltip type="auto">{m.coming_soon()}</Tooltip>
<FeedbackButtons {message} {onFeedback} />
Expand Down
7 changes: 4 additions & 3 deletions apps/frontend/src/lib/components/ChatErrorMessage.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Card, Button } from 'flowbite-svelte';
import { Card } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';
import { ExclamationCircleOutline, RefreshOutline } from 'flowbite-svelte-icons';
import { m } from '$lib/paraglide/messages.js';

Expand Down Expand Up @@ -27,8 +28,8 @@
{error.message}
</p>
<div class="flex gap-2 pt-1">
<Button size="sm" color="dark" outline onclick={onRetry}>
<RefreshOutline class="me-2 h-4 w-4" />
<Button variant="outline" size="sm" onclick={() => onRetry()}>
<RefreshOutline />
{m.chat_error_retry()}
</Button>
</div>
Expand Down
20 changes: 11 additions & 9 deletions apps/frontend/src/lib/components/FeedbackButtons.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script lang="ts">
import { Button, Tooltip } from 'flowbite-svelte';
import { Tooltip } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';

import { ThumbsUpOutline, ThumbsDownOutline } from 'flowbite-svelte-icons';
import type { BaseMessage } from '$lib/langgraph/types';
import * as m from '$lib/paraglide/messages.js';
Expand All @@ -22,22 +24,22 @@
<div class="ml-2 flex gap-1 border-l border-gray-300 pl-2 dark:border-gray-600">
<Button
onclick={() => handleFeedback('up')}
class="p-1.5! {feedbackGiven === 'up' ? 'bg-gray-200 dark:bg-gray-700' : ''}"
color="alternative"
size="xs"
variant="ghost"
size="icon-sm"
class="h-6 w-6 p-1.5 {feedbackGiven === 'up' ? 'bg-gray-200 dark:bg-gray-700' : ''}"
title={m.message_feedback_good()}
>
<ThumbsUpOutline size="xs" />
<ThumbsUpOutline />
</Button>
<Tooltip type="auto">Coming Soon !</Tooltip>
<Button
onclick={() => handleFeedback('down')}
class="p-1.5! {feedbackGiven === 'down' ? 'bg-gray-200 dark:bg-gray-700' : ''}"
color="alternative"
size="xs"
variant="ghost"
size="icon-sm"
class="h-6 w-6 p-1.5 {feedbackGiven === 'down' ? 'bg-gray-200 dark:bg-gray-700' : ''}"
title={m.message_feedback_bad()}
>
<ThumbsDownOutline size="xs" />
<ThumbsDownOutline />
</Button>
<Tooltip type="auto">Coming Soon !</Tooltip>
</div>
6 changes: 4 additions & 2 deletions apps/frontend/src/lib/components/LanguageSwitcher.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script lang="ts">
import { getLocale, setLocale, locales, type Locale } from '$lib/paraglide/runtime.js';
import { Button, Dropdown, DropdownItem } from 'flowbite-svelte';
import { Dropdown, DropdownItem } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';
import { GlobeOutline } from 'flowbite-svelte-icons';
import { m } from '$lib/paraglide/messages.js';
import { cn } from '$lib/utils.js';

let { class: className = '' } = $props();

Expand All @@ -16,7 +18,7 @@
}
</script>

<Button class={className} color="light" outline={false}>
<Button class={cn('', className)} variant="outline" size="sm">
<GlobeOutline />
</Button>
<Dropdown simple>
Expand Down
16 changes: 10 additions & 6 deletions apps/frontend/src/lib/components/LoginModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
</script>

<Modal title={m.login_modal_title()} size="xs" bind:open onclose={handleClose}>
<div class="text-center">
<ExclamationCircleOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-200" />
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
{m.login_modal_message()}
</h3>
<SignInButton />
<div>
<div class="text-center">
<ExclamationCircleOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-200" />
<h3 class="mb-5 text-lg font-normal text-gray-600 dark:text-gray-400">
{m.login_modal_message()}
</h3>
</div>
<div class="flex justify-center">
<SignInButton />
</div>
</div>
</Modal>
6 changes: 4 additions & 2 deletions apps/frontend/src/lib/components/SubmitButton.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { Button, Spinner } from 'flowbite-svelte';
import { Spinner } from 'flowbite-svelte';
import { PaperPlaneSolid } from 'flowbite-svelte-icons';
import { Button } from '$lib/components/ui/button';

interface Props {
isStreaming: boolean;
Expand All @@ -13,7 +14,8 @@
<Button
type="submit"
{disabled}
size="sm"
size="icon-sm"
variant="default"
class="bg-primary-600 hover:bg-primary-700 flex h-8 w-8 shrink-0 items-center justify-center rounded-full p-0 text-white shadow-sm disabled:cursor-not-allowed disabled:bg-gray-300"
>
{#if isStreaming}
Expand Down
13 changes: 4 additions & 9 deletions apps/frontend/src/lib/components/UserMessageActions.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Button, Tooltip } from 'flowbite-svelte';
import { Tooltip } from 'flowbite-svelte';
import { Button } from '$lib/components/ui/button';
import { PenOutline } from 'flowbite-svelte-icons';
import type { BaseMessage } from '$lib/langgraph/types';
import * as m from '$lib/paraglide/messages.js';
Expand All @@ -17,14 +18,8 @@
class="absolute right-0 bottom-2 flex items-center gap-2 transition-all duration-300 ease-in-out"
style="opacity: {isHovered ? '1' : '0'}; transform: translateY({isHovered ? '0' : '-4px'});"
>
<Button
onclick={() => onEdit?.(message)}
class="p-1.5!"
color="alternative"
size="xs"
title={m.message_edit()}
>
<PenOutline size="xs" />
<Button onclick={() => onEdit?.(message)} variant="ghost" size="icon-sm" title={m.message_edit()}>
<PenOutline />
</Button>
<Tooltip type="auto">{m.coming_soon()}</Tooltip>
</div>
82 changes: 82 additions & 0 deletions apps/frontend/src/lib/components/ui/button/button.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';

export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
destructive:
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
outline:
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});

export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];

export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>

<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
children,
...restProps
}: ButtonProps = $props();
</script>

{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
{/if}
17 changes: 17 additions & 0 deletions apps/frontend/src/lib/components/ui/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';

export {
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
};
13 changes: 13 additions & 0 deletions apps/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
Loading