@ims360/svelte-ivory 0.3.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/colorTheme.svelte.d.ts +14 -0
- package/dist/colorTheme.svelte.d.ts.map +1 -0
- package/dist/colorTheme.svelte.js +59 -0
- package/dist/components/ai/index.d.ts +0 -4
- package/dist/components/ai/index.d.ts.map +1 -1
- package/dist/components/ai/index.js +0 -4
- package/dist/components/basic/ThemeController.svelte +49 -0
- package/dist/components/basic/ThemeController.svelte.d.ts +8 -0
- package/dist/components/basic/ThemeController.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/lib/colorTheme.svelte.ts +65 -0
- package/src/lib/components/ai/index.ts +0 -4
- package/src/lib/components/basic/ThemeController.svelte +49 -0
- package/src/lib/index.ts +1 -0
- package/dist/components/ai/AiMessage.svelte +0 -114
- package/dist/components/ai/AiMessage.svelte.d.ts +0 -19
- package/dist/components/ai/AiMessage.svelte.d.ts.map +0 -1
- package/dist/components/ai/AttachedFile.svelte +0 -28
- package/dist/components/ai/AttachedFile.svelte.d.ts +0 -10
- package/dist/components/ai/AttachedFile.svelte.d.ts.map +0 -1
- package/dist/components/ai/Chat.svelte +0 -149
- package/dist/components/ai/Chat.svelte.d.ts +0 -43
- package/dist/components/ai/Chat.svelte.d.ts.map +0 -1
- package/dist/components/ai/UserMessage.svelte +0 -52
- package/dist/components/ai/UserMessage.svelte.d.ts +0 -17
- package/dist/components/ai/UserMessage.svelte.d.ts.map +0 -1
- package/src/lib/components/ai/AiMessage.svelte +0 -114
- package/src/lib/components/ai/AttachedFile.svelte +0 -28
- package/src/lib/components/ai/Chat.svelte +0 -149
- package/src/lib/components/ai/UserMessage.svelte +0 -52
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Handle } from '@sveltejs/kit';
|
|
2
|
+
export type ColorThemePreference = 'system' | 'light' | 'dark';
|
|
3
|
+
declare class ThemeController {
|
|
4
|
+
private currentTheme;
|
|
5
|
+
constructor();
|
|
6
|
+
get theme(): ColorThemePreference;
|
|
7
|
+
get preference(): ColorThemePreference;
|
|
8
|
+
set theme(value: ColorThemePreference);
|
|
9
|
+
/** Add to hook sequence to enable correct prerendering ot the theme */
|
|
10
|
+
handle: Handle;
|
|
11
|
+
}
|
|
12
|
+
export declare const ColorTheme: ThemeController;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=colorTheme.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colorTheme.svelte.d.ts","sourceRoot":"","sources":["../src/lib/colorTheme.svelte.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/D,cAAM,eAAe;IACjB,OAAO,CAAC,YAAY,CAA0C;;IAY9D,IAAI,KAAK,IAQQ,oBAAoB,CANpC;IAED,IAAI,UAAU,yBAEb;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,oBAAoB,EAepC;IAED,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAcZ;CACL;AAED,eAAO,MAAM,UAAU,iBAAwB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { browser } from '$app/environment';
|
|
2
|
+
import { cookie } from '@ims360/svelte-ivory/utils/functions';
|
|
3
|
+
const COOKIE_THEME_KEY = 'theme';
|
|
4
|
+
class ThemeController {
|
|
5
|
+
currentTheme = $state('system');
|
|
6
|
+
constructor() {
|
|
7
|
+
if (!browser)
|
|
8
|
+
return;
|
|
9
|
+
const value = cookie.get(COOKIE_THEME_KEY);
|
|
10
|
+
if (value === 'system' || value === 'dark' || value === 'light') {
|
|
11
|
+
this.theme = value;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
this.theme = 'system';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
get theme() {
|
|
18
|
+
return this.currentTheme;
|
|
19
|
+
}
|
|
20
|
+
get preference() {
|
|
21
|
+
return this.currentTheme;
|
|
22
|
+
}
|
|
23
|
+
set theme(value) {
|
|
24
|
+
if (!browser)
|
|
25
|
+
return;
|
|
26
|
+
this.currentTheme = value;
|
|
27
|
+
cookie.set({
|
|
28
|
+
name: COOKIE_THEME_KEY,
|
|
29
|
+
value
|
|
30
|
+
});
|
|
31
|
+
if (value === 'system') {
|
|
32
|
+
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
33
|
+
document.documentElement.classList.toggle('dark', systemDark);
|
|
34
|
+
}
|
|
35
|
+
else if (value === 'light') {
|
|
36
|
+
document.documentElement.classList.toggle('dark', false);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
document.documentElement.classList.toggle('dark', true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Add to hook sequence to enable correct prerendering ot the theme */
|
|
43
|
+
handle = ({ event, resolve }) => {
|
|
44
|
+
const theme = event.cookies.get(COOKIE_THEME_KEY);
|
|
45
|
+
let isDark = false;
|
|
46
|
+
if (theme !== undefined && theme !== 'system') {
|
|
47
|
+
isDark = theme === 'dark';
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
isDark = event.request.headers.get('sec-ch-prefers-color-scheme') === 'dark';
|
|
51
|
+
}
|
|
52
|
+
if (!isDark)
|
|
53
|
+
return resolve(event);
|
|
54
|
+
return resolve(event, {
|
|
55
|
+
transformPageChunk: ({ html }) => html.replace('class="', 'class="dark ')
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export const ColorTheme = new ThemeController();
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
export { default as AiMessage } from './AiMessage.svelte';
|
|
2
|
-
export { default as AttachedFile } from './AttachedFile.svelte';
|
|
3
|
-
export { default as Chat, type AiChat, type AiChatMessage } from './Chat.svelte';
|
|
4
1
|
export { default as Markdown } from './Markdown.svelte';
|
|
5
|
-
export { default as UserMessage } from './UserMessage.svelte';
|
|
6
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export { default as AiMessage } from './AiMessage.svelte';
|
|
2
|
-
export { default as AttachedFile } from './AttachedFile.svelte';
|
|
3
|
-
export { default as Chat } from './Chat.svelte';
|
|
4
1
|
export { default as Markdown } from './Markdown.svelte';
|
|
5
|
-
export { default as UserMessage } from './UserMessage.svelte';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ColorTheme, type ColorThemePreference } from '../../colorTheme.svelte';
|
|
3
|
+
import { merge } from '@ims360/svelte-ivory/utils/functions';
|
|
4
|
+
import { Icon, Monitor, Moon, Sun } from '@lucide/svelte';
|
|
5
|
+
import type { ClassValue } from 'svelte/elements';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
class: ClassValue;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: clazz }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const options: { icon: typeof Icon; value: ColorThemePreference; ariaLabel: string }[] = [
|
|
14
|
+
{
|
|
15
|
+
value: 'system',
|
|
16
|
+
icon: Monitor,
|
|
17
|
+
ariaLabel: 'System Theme'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'dark',
|
|
21
|
+
icon: Moon,
|
|
22
|
+
ariaLabel: 'Dark Theme'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'light',
|
|
26
|
+
icon: Sun,
|
|
27
|
+
ariaLabel: 'Light Theme'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class={merge('border-surface-300-700 grid grid-cols-3 rounded-full border', clazz)}>
|
|
33
|
+
{#each options as { value, icon: Icon, ariaLabel }}
|
|
34
|
+
{@const selected = value === ColorTheme.theme}
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class={[
|
|
38
|
+
'tranistion-all text-surface-600-400 hover:text-surface-900-100 h-6 w-6 rounded-full p-1 transition-colors',
|
|
39
|
+
selected && 'bg-surface-100-900'
|
|
40
|
+
]}
|
|
41
|
+
onclick={() => {
|
|
42
|
+
ColorTheme.theme = value;
|
|
43
|
+
}}
|
|
44
|
+
aria-label={ariaLabel}
|
|
45
|
+
>
|
|
46
|
+
<Icon class="h-full w-full" />
|
|
47
|
+
</button>
|
|
48
|
+
{/each}
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ClassValue } from 'svelte/elements';
|
|
2
|
+
type Props = {
|
|
3
|
+
class: ClassValue;
|
|
4
|
+
};
|
|
5
|
+
declare const ThemeController: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type ThemeController = ReturnType<typeof ThemeController>;
|
|
7
|
+
export default ThemeController;
|
|
8
|
+
//# sourceMappingURL=ThemeController.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeController.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/basic/ThemeController.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG9C,KAAK,KAAK,GAAG;IACT,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AA6CN,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { ColorTheme, type ColorThemePreference } from './colorTheme.svelte';
|
|
1
2
|
export { theme, type Theme } from './theme.svelte';
|
|
2
3
|
export { type IvoryComponent } from './types';
|
|
3
4
|
export type Variant = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,OAAO,GACb,SAAS,GACT,WAAW,GACX,UAAU,GACV,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,OAAO,GACb,SAAS,GACT,WAAW,GACX,UAAU,GACV,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { browser } from '$app/environment';
|
|
2
|
+
import { cookie } from '@ims360/svelte-ivory/utils/functions';
|
|
3
|
+
import type { Handle } from '@sveltejs/kit';
|
|
4
|
+
|
|
5
|
+
const COOKIE_THEME_KEY = 'theme';
|
|
6
|
+
|
|
7
|
+
export type ColorThemePreference = 'system' | 'light' | 'dark';
|
|
8
|
+
|
|
9
|
+
class ThemeController {
|
|
10
|
+
private currentTheme = $state<ColorThemePreference>('system');
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
if (!browser) return;
|
|
14
|
+
const value = cookie.get(COOKIE_THEME_KEY);
|
|
15
|
+
if (value === 'system' || value === 'dark' || value === 'light') {
|
|
16
|
+
this.theme = value;
|
|
17
|
+
} else {
|
|
18
|
+
this.theme = 'system';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get theme() {
|
|
23
|
+
return this.currentTheme;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get preference() {
|
|
27
|
+
return this.currentTheme;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set theme(value: ColorThemePreference) {
|
|
31
|
+
if (!browser) return;
|
|
32
|
+
this.currentTheme = value;
|
|
33
|
+
cookie.set({
|
|
34
|
+
name: COOKIE_THEME_KEY,
|
|
35
|
+
value
|
|
36
|
+
});
|
|
37
|
+
if (value === 'system') {
|
|
38
|
+
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
39
|
+
document.documentElement.classList.toggle('dark', systemDark);
|
|
40
|
+
} else if (value === 'light') {
|
|
41
|
+
document.documentElement.classList.toggle('dark', false);
|
|
42
|
+
} else {
|
|
43
|
+
document.documentElement.classList.toggle('dark', true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Add to hook sequence to enable correct prerendering ot the theme */
|
|
48
|
+
handle: Handle = ({ event, resolve }) => {
|
|
49
|
+
const theme = event.cookies.get(COOKIE_THEME_KEY);
|
|
50
|
+
let isDark = false;
|
|
51
|
+
if (theme !== undefined && (theme as ColorThemePreference) !== 'system') {
|
|
52
|
+
isDark = theme === 'dark';
|
|
53
|
+
} else {
|
|
54
|
+
isDark = event.request.headers.get('sec-ch-prefers-color-scheme') === 'dark';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isDark) return resolve(event);
|
|
58
|
+
|
|
59
|
+
return resolve(event, {
|
|
60
|
+
transformPageChunk: ({ html }) => html.replace('class="', 'class="dark ')
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const ColorTheme = new ThemeController();
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export { default as AiMessage } from './AiMessage.svelte';
|
|
2
|
-
export { default as AttachedFile } from './AttachedFile.svelte';
|
|
3
|
-
export { default as Chat, type AiChat, type AiChatMessage } from './Chat.svelte';
|
|
4
1
|
export { default as Markdown } from './Markdown.svelte';
|
|
5
|
-
export { default as UserMessage } from './UserMessage.svelte';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ColorTheme, type ColorThemePreference } from '$lib/colorTheme.svelte';
|
|
3
|
+
import { merge } from '@ims360/svelte-ivory/utils/functions';
|
|
4
|
+
import { Icon, Monitor, Moon, Sun } from '@lucide/svelte';
|
|
5
|
+
import type { ClassValue } from 'svelte/elements';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
class: ClassValue;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: clazz }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const options: { icon: typeof Icon; value: ColorThemePreference; ariaLabel: string }[] = [
|
|
14
|
+
{
|
|
15
|
+
value: 'system',
|
|
16
|
+
icon: Monitor,
|
|
17
|
+
ariaLabel: 'System Theme'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'dark',
|
|
21
|
+
icon: Moon,
|
|
22
|
+
ariaLabel: 'Dark Theme'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'light',
|
|
26
|
+
icon: Sun,
|
|
27
|
+
ariaLabel: 'Light Theme'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class={merge('border-surface-300-700 grid grid-cols-3 rounded-full border', clazz)}>
|
|
33
|
+
{#each options as { value, icon: Icon, ariaLabel }}
|
|
34
|
+
{@const selected = value === ColorTheme.theme}
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class={[
|
|
38
|
+
'tranistion-all text-surface-600-400 hover:text-surface-900-100 h-6 w-6 rounded-full p-1 transition-colors',
|
|
39
|
+
selected && 'bg-surface-100-900'
|
|
40
|
+
]}
|
|
41
|
+
onclick={() => {
|
|
42
|
+
ColorTheme.theme = value;
|
|
43
|
+
}}
|
|
44
|
+
aria-label={ariaLabel}
|
|
45
|
+
>
|
|
46
|
+
<Icon class="h-full w-full" />
|
|
47
|
+
</button>
|
|
48
|
+
{/each}
|
|
49
|
+
</div>
|
package/src/lib/index.ts
CHANGED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
The default for AI Messages in the Chat component.
|
|
4
|
-
Can be customized using the `class` and `loading` props.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import { merge } from '../../utils/functions';
|
|
9
|
-
import { ThumbsDown, ThumbsUp } from '@lucide/svelte';
|
|
10
|
-
import type { Snippet } from 'svelte';
|
|
11
|
-
import type { ClassValue } from 'svelte/elements';
|
|
12
|
-
import CopyToClipboardButton from '../buttons/CopyToClipboardButton.svelte';
|
|
13
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
14
|
-
import Markdown from './Markdown.svelte';
|
|
15
|
-
|
|
16
|
-
interface Props {
|
|
17
|
-
b_message: AiChatMessage;
|
|
18
|
-
messageText?: Snippet<[{ message: AiChatMessage }]>;
|
|
19
|
-
class?: ClassValue;
|
|
20
|
-
minHeight?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let {
|
|
24
|
-
b_message = $bindable(),
|
|
25
|
-
class: clazz,
|
|
26
|
-
messageText = defaultMessage,
|
|
27
|
-
minHeight
|
|
28
|
-
}: Props = $props();
|
|
29
|
-
|
|
30
|
-
// const uuidRegex =
|
|
31
|
-
// /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g;
|
|
32
|
-
// const icon = `<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" class="h-[1em] origin-center overflow-visible align-[-0.125rem]" viewBox="0 0 512 512"><g transform="translate(256 256)" transform-origin="128 0"><path d="M336 0c-8.8 0-16 7.2-16 16s7.2 16 16 16l121.4 0L212.7 276.7c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L480 54.6 480 176c0 8.8 7.2 16 16 16s16-7.2 16-16l0-160c0-8.8-7.2-16-16-16L336 0zM64 32C28.7 32 0 60.7 0 96L0 448c0 35.3 28.7 64 64 64l352 0c35.3 0 64-28.7 64-64l0-144c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 144c0 17.7-14.3 32-32 32L64 480c-17.7 0-32-14.3-32-32L32 96c0-17.7 14.3-32 32-32l144 0c8.8 0 16-7.2 16-16s-7.2-16-16-16L64 32z" fill="currentColor" transform="translate(-256 -256)"></path><!----></g></svg>`;
|
|
33
|
-
|
|
34
|
-
// async function replacerFactory(): Promise<(match: string) => string> {
|
|
35
|
-
// const docs = await documents.value;
|
|
36
|
-
// return (match: string) => {
|
|
37
|
-
// const doc = docs.find((d) => d.id === match);
|
|
38
|
-
|
|
39
|
-
// return `<span class="bg-surface-100-900 py-1 px-2 rounded flex flex-row items-center gap-2 hover:shadow-lg w-fit transition-all">
|
|
40
|
-
// ${doc?.title ?? 'Unbekanntes Dokument'}
|
|
41
|
-
// <a href="/documents/${doc?.id}" target="_blank">
|
|
42
|
-
// ${icon}
|
|
43
|
-
// </a>
|
|
44
|
-
// </span>`;
|
|
45
|
-
// };
|
|
46
|
-
// }
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
<div
|
|
50
|
-
class={merge('group flex w-full flex-col items-start', clazz)}
|
|
51
|
-
style={minHeight ? `min-height: ${minHeight}px;` : undefined}
|
|
52
|
-
>
|
|
53
|
-
{@render messageText({
|
|
54
|
-
message: b_message
|
|
55
|
-
})}
|
|
56
|
-
<div
|
|
57
|
-
class={[
|
|
58
|
-
'text-surface-500 flex -translate-x-3 flex-row items-center transition-all group-hover:opacity-100',
|
|
59
|
-
b_message.liked || b_message.disliked ? 'opacity-100' : 'opacity-0'
|
|
60
|
-
]}
|
|
61
|
-
>
|
|
62
|
-
<CopyToClipboardButton
|
|
63
|
-
text={b_message.message}
|
|
64
|
-
toastMessage="Nachricht wurde in die Zwischenablage kopiert"
|
|
65
|
-
/>
|
|
66
|
-
<button
|
|
67
|
-
type="button"
|
|
68
|
-
class="btn-icon"
|
|
69
|
-
onclick={() => {
|
|
70
|
-
b_message.liked = !b_message.liked;
|
|
71
|
-
b_message.disliked = false;
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
74
|
-
<ThumbsUp class={[b_message.liked && 'fill-surface-500/50']} />
|
|
75
|
-
</button>
|
|
76
|
-
<button
|
|
77
|
-
type="button"
|
|
78
|
-
class="btn-icon"
|
|
79
|
-
onclick={() => {
|
|
80
|
-
b_message.liked = false;
|
|
81
|
-
b_message.disliked = !b_message.disliked;
|
|
82
|
-
}}
|
|
83
|
-
>
|
|
84
|
-
<ThumbsDown class={[b_message.disliked && 'fill-surface-500/50']} />
|
|
85
|
-
</button>
|
|
86
|
-
{#if b_message.time}
|
|
87
|
-
<p class="text-surface-400-600 pl-2">
|
|
88
|
-
{b_message.time.toLocaleString('de')}
|
|
89
|
-
</p>
|
|
90
|
-
{/if}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{#snippet defaultMessage({ message }: { message: AiChatMessage })}
|
|
95
|
-
{#if message}
|
|
96
|
-
<Markdown source={message.message} />
|
|
97
|
-
{:else}
|
|
98
|
-
<p class="flex flex-row">
|
|
99
|
-
<span class="h-4 animate-bounce rounded-full pl-1">.</span>
|
|
100
|
-
<span
|
|
101
|
-
class="h-4 animate-bounce rounded-full"
|
|
102
|
-
style="animation-delay: 125ms !important;"
|
|
103
|
-
>
|
|
104
|
-
.
|
|
105
|
-
</span>
|
|
106
|
-
<span
|
|
107
|
-
class="h-4 animate-bounce rounded-full"
|
|
108
|
-
style="animation-delay: 250ms !important;"
|
|
109
|
-
>
|
|
110
|
-
.
|
|
111
|
-
</span>
|
|
112
|
-
</p>
|
|
113
|
-
{/if}
|
|
114
|
-
{/snippet}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { ClassValue } from 'svelte/elements';
|
|
3
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
4
|
-
interface Props {
|
|
5
|
-
b_message: AiChatMessage;
|
|
6
|
-
messageText?: Snippet<[{
|
|
7
|
-
message: AiChatMessage;
|
|
8
|
-
}]>;
|
|
9
|
-
class?: ClassValue;
|
|
10
|
-
minHeight?: number;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The default for AI Messages in the Chat component.
|
|
14
|
-
* Can be customized using the `class` and `loading` props.
|
|
15
|
-
*/
|
|
16
|
-
declare const AiMessage: import("svelte").Component<Props, {}, "b_message">;
|
|
17
|
-
type AiMessage = ReturnType<typeof AiMessage>;
|
|
18
|
-
export default AiMessage;
|
|
19
|
-
//# sourceMappingURL=AiMessage.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AiMessage.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/AiMessage.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI/C,UAAU,KAAK;IACX,SAAS,EAAE,aAAa,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAsFL;;;GAGG;AACH,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { X } from '@lucide/svelte';
|
|
3
|
-
import type { ClassValue } from 'svelte/elements';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
file: File;
|
|
7
|
-
onremove?: (f: File) => void;
|
|
8
|
-
class?: ClassValue;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let { file, onremove, class: clazz }: Props = $props();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
15
|
-
<svelte:element
|
|
16
|
-
this={onremove ? 'button' : 'div'}
|
|
17
|
-
type={onremove ? 'button' : undefined}
|
|
18
|
-
onclick={onremove ? () => onremove(file) : undefined}
|
|
19
|
-
class={[
|
|
20
|
-
'bg-primary-500/25 group flex flex-row items-center gap-1 overflow-hidden rounded-full px-4 py-1 whitespace-nowrap',
|
|
21
|
-
clazz
|
|
22
|
-
]}
|
|
23
|
-
>
|
|
24
|
-
<p>{file.name}</p>
|
|
25
|
-
{#if onremove}
|
|
26
|
-
<X size={16} class="group-hover:text-primary-500" />
|
|
27
|
-
{/if}
|
|
28
|
-
</svelte:element>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ClassValue } from 'svelte/elements';
|
|
2
|
-
interface Props {
|
|
3
|
-
file: File;
|
|
4
|
-
onremove?: (f: File) => void;
|
|
5
|
-
class?: ClassValue;
|
|
6
|
-
}
|
|
7
|
-
declare const AttachedFile: import("svelte").Component<Props, {}, "">;
|
|
8
|
-
type AttachedFile = ReturnType<typeof AttachedFile>;
|
|
9
|
-
export default AttachedFile;
|
|
10
|
-
//# sourceMappingURL=AttachedFile.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AttachedFile.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/AttachedFile.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG9C,UAAU,KAAK;IACX,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,UAAU,CAAC;CACtB;AAsBL,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
An AI chat component that can be used to create a chatbot.
|
|
4
|
-
Comes with default styles for the chat messages, but can be customized with the `userMessage` and `systemMessage` props.
|
|
5
|
-
The input component has to be provided as a child component, and the `submit` function has to be provided as a callback.
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<script lang="ts" module>
|
|
9
|
-
import { merge } from '../../utils/functions';
|
|
10
|
-
import { tick, type Snippet } from 'svelte';
|
|
11
|
-
import type { ClassValue } from 'svelte/elements';
|
|
12
|
-
import AiMessage from './AiMessage.svelte';
|
|
13
|
-
import UserMessage from './UserMessage.svelte';
|
|
14
|
-
|
|
15
|
-
export interface AiChatMessage {
|
|
16
|
-
from: 'user' | 'system';
|
|
17
|
-
message: string;
|
|
18
|
-
time?: Date;
|
|
19
|
-
liked?: boolean;
|
|
20
|
-
disliked?: boolean;
|
|
21
|
-
files?: File[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface AiChat {
|
|
25
|
-
messages: AiChatMessage[];
|
|
26
|
-
loading?: boolean;
|
|
27
|
-
}
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<script lang="ts">
|
|
31
|
-
interface Props {
|
|
32
|
-
class?: ClassValue;
|
|
33
|
-
b_chat: AiChat;
|
|
34
|
-
userMessage?: Snippet<[{ message: AiChatMessage; i: number }]>;
|
|
35
|
-
systemMessage?: Snippet<[{ message: AiChatMessage; i: number; minHeight?: number }]>;
|
|
36
|
-
placeholder?: Snippet;
|
|
37
|
-
children: Snippet<[{ onsubmit: (message: AiChatMessage) => Promise<void> }]>;
|
|
38
|
-
submit: (message: AiChatMessage) => Promise<void>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let {
|
|
42
|
-
class: clazz,
|
|
43
|
-
b_chat = $bindable(),
|
|
44
|
-
userMessage = defaultUserMessage,
|
|
45
|
-
systemMessage = defaultSystemMessage,
|
|
46
|
-
placeholder,
|
|
47
|
-
children,
|
|
48
|
-
submit: externalSubmit
|
|
49
|
-
}: Props = $props();
|
|
50
|
-
|
|
51
|
-
let chatContainer = $state<HTMLDivElement>();
|
|
52
|
-
let lastMessageMinHeight = $state(0);
|
|
53
|
-
|
|
54
|
-
function getLastMessageMinHeight() {
|
|
55
|
-
if (!chatContainer) return 0;
|
|
56
|
-
const secondToLastElement = chatContainer.children[chatContainer.children.length - 2];
|
|
57
|
-
const rect = secondToLastElement?.getBoundingClientRect();
|
|
58
|
-
const remainHeight = chatContainer.clientHeight - rect.height - 16;
|
|
59
|
-
return remainHeight;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function scrollToBottom() {
|
|
63
|
-
if (!chatContainer) return;
|
|
64
|
-
await tick();
|
|
65
|
-
await tick();
|
|
66
|
-
lastMessageMinHeight = getLastMessageMinHeight();
|
|
67
|
-
await tick();
|
|
68
|
-
// ensure we don't scroll if the newly generated message is already in view
|
|
69
|
-
chatContainer.scrollTo({
|
|
70
|
-
top: chatContainer.scrollHeight,
|
|
71
|
-
behavior: 'smooth'
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function submit(message: AiChatMessage) {
|
|
76
|
-
if (b_chat.loading) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
b_chat.messages.push({
|
|
81
|
-
...message,
|
|
82
|
-
from: 'user',
|
|
83
|
-
time: new Date()
|
|
84
|
-
});
|
|
85
|
-
// prevent the user from sending another message while we are loading the ai response
|
|
86
|
-
b_chat.loading = true;
|
|
87
|
-
|
|
88
|
-
// add an empty system message to the chat, this will indicate a loading state
|
|
89
|
-
b_chat.messages.push({
|
|
90
|
-
from: 'system',
|
|
91
|
-
message: '',
|
|
92
|
-
time: new Date()
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
await scrollToBottom();
|
|
96
|
-
|
|
97
|
-
await externalSubmit(message);
|
|
98
|
-
|
|
99
|
-
b_chat.loading = false;
|
|
100
|
-
}
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
<div class={merge('flex grow flex-col gap-2 overflow-hidden', clazz)}>
|
|
104
|
-
<div
|
|
105
|
-
class="flex grow flex-col gap-4 overflow-auto pr-2 [scrollbar-gutter:stable]"
|
|
106
|
-
bind:this={chatContainer}
|
|
107
|
-
>
|
|
108
|
-
{#if b_chat.messages.length === 0 && placeholder}
|
|
109
|
-
{@render placeholder()}
|
|
110
|
-
{/if}
|
|
111
|
-
{#each b_chat.messages as _, i (i)}
|
|
112
|
-
{@const message = b_chat.messages[i]}
|
|
113
|
-
{#if message.from === 'user'}
|
|
114
|
-
{@render userMessage({
|
|
115
|
-
message,
|
|
116
|
-
i
|
|
117
|
-
})}
|
|
118
|
-
{:else}
|
|
119
|
-
{@render systemMessage({
|
|
120
|
-
message,
|
|
121
|
-
i,
|
|
122
|
-
minHeight: i === b_chat.messages.length - 1 ? lastMessageMinHeight : 0
|
|
123
|
-
})}
|
|
124
|
-
{/if}
|
|
125
|
-
{/each}
|
|
126
|
-
</div>
|
|
127
|
-
{@render children({ onsubmit: submit })}
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{#snippet defaultSystemMessage({
|
|
131
|
-
i,
|
|
132
|
-
minHeight
|
|
133
|
-
}: {
|
|
134
|
-
i: number;
|
|
135
|
-
message: AiChatMessage;
|
|
136
|
-
minHeight?: number;
|
|
137
|
-
})}
|
|
138
|
-
<AiMessage bind:b_message={b_chat.messages[i]} {minHeight} />
|
|
139
|
-
{/snippet}
|
|
140
|
-
|
|
141
|
-
{#snippet defaultUserMessage({
|
|
142
|
-
message
|
|
143
|
-
}: {
|
|
144
|
-
i: number;
|
|
145
|
-
message: AiChatMessage;
|
|
146
|
-
minHeight?: number;
|
|
147
|
-
})}
|
|
148
|
-
<UserMessage {message} />
|
|
149
|
-
{/snippet}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { type Snippet } from 'svelte';
|
|
2
|
-
import type { ClassValue } from 'svelte/elements';
|
|
3
|
-
export interface AiChatMessage {
|
|
4
|
-
from: 'user' | 'system';
|
|
5
|
-
message: string;
|
|
6
|
-
time?: Date;
|
|
7
|
-
liked?: boolean;
|
|
8
|
-
disliked?: boolean;
|
|
9
|
-
files?: File[];
|
|
10
|
-
}
|
|
11
|
-
export interface AiChat {
|
|
12
|
-
messages: AiChatMessage[];
|
|
13
|
-
loading?: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface Props {
|
|
16
|
-
class?: ClassValue;
|
|
17
|
-
b_chat: AiChat;
|
|
18
|
-
userMessage?: Snippet<[{
|
|
19
|
-
message: AiChatMessage;
|
|
20
|
-
i: number;
|
|
21
|
-
}]>;
|
|
22
|
-
systemMessage?: Snippet<[{
|
|
23
|
-
message: AiChatMessage;
|
|
24
|
-
i: number;
|
|
25
|
-
minHeight?: number;
|
|
26
|
-
}]>;
|
|
27
|
-
placeholder?: Snippet;
|
|
28
|
-
children: Snippet<[{
|
|
29
|
-
onsubmit: (message: AiChatMessage) => Promise<void>;
|
|
30
|
-
}]>;
|
|
31
|
-
submit: (message: AiChatMessage) => Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* An AI chat component that can be used to create a chatbot.
|
|
35
|
-
* Comes with default styles for the chat messages, but can be customized with the `userMessage` and `systemMessage` props.
|
|
36
|
-
* The input component has to be provided as a child component, and the `submit` function has to be provided as a callback.
|
|
37
|
-
*/
|
|
38
|
-
declare const Chat: import("svelte").Component<Props, {
|
|
39
|
-
scrollToBottom: () => Promise<void>;
|
|
40
|
-
}, "b_chat">;
|
|
41
|
-
type Chat = ReturnType<typeof Chat>;
|
|
42
|
-
export default Chat;
|
|
43
|
-
//# sourceMappingURL=Chat.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/Chat.svelte.ts"],"names":[],"mappings":"AAII,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,MAAM;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,KAAK;IACX,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACrF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAqHL;;;;GAIG;AACH,QAAA,MAAM,IAAI;;YAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { merge } from '../../utils/functions';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
|
-
import type { ClassValue } from 'svelte/elements';
|
|
5
|
-
import AttachedFile from './AttachedFile.svelte';
|
|
6
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
7
|
-
import Markdown from './Markdown.svelte';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
class?: ClassValue;
|
|
11
|
-
message: AiChatMessage;
|
|
12
|
-
/** How attached files should be rendered */
|
|
13
|
-
attachedFile?: Snippet<[file: File]>;
|
|
14
|
-
/** How the message string should be rendered */
|
|
15
|
-
messageText?: Snippet<[{ message: AiChatMessage }]>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let {
|
|
19
|
-
class: clazz,
|
|
20
|
-
message,
|
|
21
|
-
attachedFile = defaultAttachedFile,
|
|
22
|
-
messageText = defaultMessageText
|
|
23
|
-
}: Props = $props();
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<div class={merge('flex w-full flex-col items-end gap-1', clazz)}>
|
|
27
|
-
{@render messageText({ message })}
|
|
28
|
-
{#if message.files}
|
|
29
|
-
<div class="flex flex-row items-center gap-2">
|
|
30
|
-
{#each message.files as file, i (i)}
|
|
31
|
-
{@render attachedFile(file)}
|
|
32
|
-
{/each}
|
|
33
|
-
</div>
|
|
34
|
-
{/if}
|
|
35
|
-
<div class="flex flex-row items-center gap-2">
|
|
36
|
-
{#if message.time}
|
|
37
|
-
<p class="text-surface-400-600 text-sm">
|
|
38
|
-
{message.time.toLocaleString('de')}
|
|
39
|
-
</p>
|
|
40
|
-
{/if}
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
{#snippet defaultAttachedFile(file: File)}
|
|
45
|
-
<AttachedFile {file} />
|
|
46
|
-
{/snippet}
|
|
47
|
-
|
|
48
|
-
{#snippet defaultMessageText({ message }: { message: AiChatMessage })}
|
|
49
|
-
<div class="bg-surface-200-800 text-surface-contrast-200-800 rounded px-2 py-0.5">
|
|
50
|
-
<Markdown source={message.message} />
|
|
51
|
-
</div>
|
|
52
|
-
{/snippet}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { ClassValue } from 'svelte/elements';
|
|
3
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
4
|
-
interface Props {
|
|
5
|
-
class?: ClassValue;
|
|
6
|
-
message: AiChatMessage;
|
|
7
|
-
/** How attached files should be rendered */
|
|
8
|
-
attachedFile?: Snippet<[file: File]>;
|
|
9
|
-
/** How the message string should be rendered */
|
|
10
|
-
messageText?: Snippet<[{
|
|
11
|
-
message: AiChatMessage;
|
|
12
|
-
}]>;
|
|
13
|
-
}
|
|
14
|
-
declare const UserMessage: import("svelte").Component<Props, {}, "">;
|
|
15
|
-
type UserMessage = ReturnType<typeof UserMessage>;
|
|
16
|
-
export default UserMessage;
|
|
17
|
-
//# sourceMappingURL=UserMessage.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UserMessage.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ai/UserMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI/C,UAAU,KAAK;IACX,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;IACvB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACrC,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC,CAAC;CACvD;AAgDL,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
The default for AI Messages in the Chat component.
|
|
4
|
-
Can be customized using the `class` and `loading` props.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import { merge } from '$lib/utils/functions';
|
|
9
|
-
import { ThumbsDown, ThumbsUp } from '@lucide/svelte';
|
|
10
|
-
import type { Snippet } from 'svelte';
|
|
11
|
-
import type { ClassValue } from 'svelte/elements';
|
|
12
|
-
import CopyToClipboardButton from '../buttons/CopyToClipboardButton.svelte';
|
|
13
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
14
|
-
import Markdown from './Markdown.svelte';
|
|
15
|
-
|
|
16
|
-
interface Props {
|
|
17
|
-
b_message: AiChatMessage;
|
|
18
|
-
messageText?: Snippet<[{ message: AiChatMessage }]>;
|
|
19
|
-
class?: ClassValue;
|
|
20
|
-
minHeight?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let {
|
|
24
|
-
b_message = $bindable(),
|
|
25
|
-
class: clazz,
|
|
26
|
-
messageText = defaultMessage,
|
|
27
|
-
minHeight
|
|
28
|
-
}: Props = $props();
|
|
29
|
-
|
|
30
|
-
// const uuidRegex =
|
|
31
|
-
// /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g;
|
|
32
|
-
// const icon = `<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" class="h-[1em] origin-center overflow-visible align-[-0.125rem]" viewBox="0 0 512 512"><g transform="translate(256 256)" transform-origin="128 0"><path d="M336 0c-8.8 0-16 7.2-16 16s7.2 16 16 16l121.4 0L212.7 276.7c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L480 54.6 480 176c0 8.8 7.2 16 16 16s16-7.2 16-16l0-160c0-8.8-7.2-16-16-16L336 0zM64 32C28.7 32 0 60.7 0 96L0 448c0 35.3 28.7 64 64 64l352 0c35.3 0 64-28.7 64-64l0-144c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 144c0 17.7-14.3 32-32 32L64 480c-17.7 0-32-14.3-32-32L32 96c0-17.7 14.3-32 32-32l144 0c8.8 0 16-7.2 16-16s-7.2-16-16-16L64 32z" fill="currentColor" transform="translate(-256 -256)"></path><!----></g></svg>`;
|
|
33
|
-
|
|
34
|
-
// async function replacerFactory(): Promise<(match: string) => string> {
|
|
35
|
-
// const docs = await documents.value;
|
|
36
|
-
// return (match: string) => {
|
|
37
|
-
// const doc = docs.find((d) => d.id === match);
|
|
38
|
-
|
|
39
|
-
// return `<span class="bg-surface-100-900 py-1 px-2 rounded flex flex-row items-center gap-2 hover:shadow-lg w-fit transition-all">
|
|
40
|
-
// ${doc?.title ?? 'Unbekanntes Dokument'}
|
|
41
|
-
// <a href="/documents/${doc?.id}" target="_blank">
|
|
42
|
-
// ${icon}
|
|
43
|
-
// </a>
|
|
44
|
-
// </span>`;
|
|
45
|
-
// };
|
|
46
|
-
// }
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
<div
|
|
50
|
-
class={merge('group flex w-full flex-col items-start', clazz)}
|
|
51
|
-
style={minHeight ? `min-height: ${minHeight}px;` : undefined}
|
|
52
|
-
>
|
|
53
|
-
{@render messageText({
|
|
54
|
-
message: b_message
|
|
55
|
-
})}
|
|
56
|
-
<div
|
|
57
|
-
class={[
|
|
58
|
-
'text-surface-500 flex -translate-x-3 flex-row items-center transition-all group-hover:opacity-100',
|
|
59
|
-
b_message.liked || b_message.disliked ? 'opacity-100' : 'opacity-0'
|
|
60
|
-
]}
|
|
61
|
-
>
|
|
62
|
-
<CopyToClipboardButton
|
|
63
|
-
text={b_message.message}
|
|
64
|
-
toastMessage="Nachricht wurde in die Zwischenablage kopiert"
|
|
65
|
-
/>
|
|
66
|
-
<button
|
|
67
|
-
type="button"
|
|
68
|
-
class="btn-icon"
|
|
69
|
-
onclick={() => {
|
|
70
|
-
b_message.liked = !b_message.liked;
|
|
71
|
-
b_message.disliked = false;
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
74
|
-
<ThumbsUp class={[b_message.liked && 'fill-surface-500/50']} />
|
|
75
|
-
</button>
|
|
76
|
-
<button
|
|
77
|
-
type="button"
|
|
78
|
-
class="btn-icon"
|
|
79
|
-
onclick={() => {
|
|
80
|
-
b_message.liked = false;
|
|
81
|
-
b_message.disliked = !b_message.disliked;
|
|
82
|
-
}}
|
|
83
|
-
>
|
|
84
|
-
<ThumbsDown class={[b_message.disliked && 'fill-surface-500/50']} />
|
|
85
|
-
</button>
|
|
86
|
-
{#if b_message.time}
|
|
87
|
-
<p class="text-surface-400-600 pl-2">
|
|
88
|
-
{b_message.time.toLocaleString('de')}
|
|
89
|
-
</p>
|
|
90
|
-
{/if}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{#snippet defaultMessage({ message }: { message: AiChatMessage })}
|
|
95
|
-
{#if message}
|
|
96
|
-
<Markdown source={message.message} />
|
|
97
|
-
{:else}
|
|
98
|
-
<p class="flex flex-row">
|
|
99
|
-
<span class="h-4 animate-bounce rounded-full pl-1">.</span>
|
|
100
|
-
<span
|
|
101
|
-
class="h-4 animate-bounce rounded-full"
|
|
102
|
-
style="animation-delay: 125ms !important;"
|
|
103
|
-
>
|
|
104
|
-
.
|
|
105
|
-
</span>
|
|
106
|
-
<span
|
|
107
|
-
class="h-4 animate-bounce rounded-full"
|
|
108
|
-
style="animation-delay: 250ms !important;"
|
|
109
|
-
>
|
|
110
|
-
.
|
|
111
|
-
</span>
|
|
112
|
-
</p>
|
|
113
|
-
{/if}
|
|
114
|
-
{/snippet}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { X } from '@lucide/svelte';
|
|
3
|
-
import type { ClassValue } from 'svelte/elements';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
file: File;
|
|
7
|
-
onremove?: (f: File) => void;
|
|
8
|
-
class?: ClassValue;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let { file, onremove, class: clazz }: Props = $props();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
15
|
-
<svelte:element
|
|
16
|
-
this={onremove ? 'button' : 'div'}
|
|
17
|
-
type={onremove ? 'button' : undefined}
|
|
18
|
-
onclick={onremove ? () => onremove(file) : undefined}
|
|
19
|
-
class={[
|
|
20
|
-
'bg-primary-500/25 group flex flex-row items-center gap-1 overflow-hidden rounded-full px-4 py-1 whitespace-nowrap',
|
|
21
|
-
clazz
|
|
22
|
-
]}
|
|
23
|
-
>
|
|
24
|
-
<p>{file.name}</p>
|
|
25
|
-
{#if onremove}
|
|
26
|
-
<X size={16} class="group-hover:text-primary-500" />
|
|
27
|
-
{/if}
|
|
28
|
-
</svelte:element>
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
An AI chat component that can be used to create a chatbot.
|
|
4
|
-
Comes with default styles for the chat messages, but can be customized with the `userMessage` and `systemMessage` props.
|
|
5
|
-
The input component has to be provided as a child component, and the `submit` function has to be provided as a callback.
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<script lang="ts" module>
|
|
9
|
-
import { merge } from '$lib/utils/functions';
|
|
10
|
-
import { tick, type Snippet } from 'svelte';
|
|
11
|
-
import type { ClassValue } from 'svelte/elements';
|
|
12
|
-
import AiMessage from './AiMessage.svelte';
|
|
13
|
-
import UserMessage from './UserMessage.svelte';
|
|
14
|
-
|
|
15
|
-
export interface AiChatMessage {
|
|
16
|
-
from: 'user' | 'system';
|
|
17
|
-
message: string;
|
|
18
|
-
time?: Date;
|
|
19
|
-
liked?: boolean;
|
|
20
|
-
disliked?: boolean;
|
|
21
|
-
files?: File[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface AiChat {
|
|
25
|
-
messages: AiChatMessage[];
|
|
26
|
-
loading?: boolean;
|
|
27
|
-
}
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<script lang="ts">
|
|
31
|
-
interface Props {
|
|
32
|
-
class?: ClassValue;
|
|
33
|
-
b_chat: AiChat;
|
|
34
|
-
userMessage?: Snippet<[{ message: AiChatMessage; i: number }]>;
|
|
35
|
-
systemMessage?: Snippet<[{ message: AiChatMessage; i: number; minHeight?: number }]>;
|
|
36
|
-
placeholder?: Snippet;
|
|
37
|
-
children: Snippet<[{ onsubmit: (message: AiChatMessage) => Promise<void> }]>;
|
|
38
|
-
submit: (message: AiChatMessage) => Promise<void>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let {
|
|
42
|
-
class: clazz,
|
|
43
|
-
b_chat = $bindable(),
|
|
44
|
-
userMessage = defaultUserMessage,
|
|
45
|
-
systemMessage = defaultSystemMessage,
|
|
46
|
-
placeholder,
|
|
47
|
-
children,
|
|
48
|
-
submit: externalSubmit
|
|
49
|
-
}: Props = $props();
|
|
50
|
-
|
|
51
|
-
let chatContainer = $state<HTMLDivElement>();
|
|
52
|
-
let lastMessageMinHeight = $state(0);
|
|
53
|
-
|
|
54
|
-
function getLastMessageMinHeight() {
|
|
55
|
-
if (!chatContainer) return 0;
|
|
56
|
-
const secondToLastElement = chatContainer.children[chatContainer.children.length - 2];
|
|
57
|
-
const rect = secondToLastElement?.getBoundingClientRect();
|
|
58
|
-
const remainHeight = chatContainer.clientHeight - rect.height - 16;
|
|
59
|
-
return remainHeight;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function scrollToBottom() {
|
|
63
|
-
if (!chatContainer) return;
|
|
64
|
-
await tick();
|
|
65
|
-
await tick();
|
|
66
|
-
lastMessageMinHeight = getLastMessageMinHeight();
|
|
67
|
-
await tick();
|
|
68
|
-
// ensure we don't scroll if the newly generated message is already in view
|
|
69
|
-
chatContainer.scrollTo({
|
|
70
|
-
top: chatContainer.scrollHeight,
|
|
71
|
-
behavior: 'smooth'
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function submit(message: AiChatMessage) {
|
|
76
|
-
if (b_chat.loading) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
b_chat.messages.push({
|
|
81
|
-
...message,
|
|
82
|
-
from: 'user',
|
|
83
|
-
time: new Date()
|
|
84
|
-
});
|
|
85
|
-
// prevent the user from sending another message while we are loading the ai response
|
|
86
|
-
b_chat.loading = true;
|
|
87
|
-
|
|
88
|
-
// add an empty system message to the chat, this will indicate a loading state
|
|
89
|
-
b_chat.messages.push({
|
|
90
|
-
from: 'system',
|
|
91
|
-
message: '',
|
|
92
|
-
time: new Date()
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
await scrollToBottom();
|
|
96
|
-
|
|
97
|
-
await externalSubmit(message);
|
|
98
|
-
|
|
99
|
-
b_chat.loading = false;
|
|
100
|
-
}
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
<div class={merge('flex grow flex-col gap-2 overflow-hidden', clazz)}>
|
|
104
|
-
<div
|
|
105
|
-
class="flex grow flex-col gap-4 overflow-auto pr-2 [scrollbar-gutter:stable]"
|
|
106
|
-
bind:this={chatContainer}
|
|
107
|
-
>
|
|
108
|
-
{#if b_chat.messages.length === 0 && placeholder}
|
|
109
|
-
{@render placeholder()}
|
|
110
|
-
{/if}
|
|
111
|
-
{#each b_chat.messages as _, i (i)}
|
|
112
|
-
{@const message = b_chat.messages[i]}
|
|
113
|
-
{#if message.from === 'user'}
|
|
114
|
-
{@render userMessage({
|
|
115
|
-
message,
|
|
116
|
-
i
|
|
117
|
-
})}
|
|
118
|
-
{:else}
|
|
119
|
-
{@render systemMessage({
|
|
120
|
-
message,
|
|
121
|
-
i,
|
|
122
|
-
minHeight: i === b_chat.messages.length - 1 ? lastMessageMinHeight : 0
|
|
123
|
-
})}
|
|
124
|
-
{/if}
|
|
125
|
-
{/each}
|
|
126
|
-
</div>
|
|
127
|
-
{@render children({ onsubmit: submit })}
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{#snippet defaultSystemMessage({
|
|
131
|
-
i,
|
|
132
|
-
minHeight
|
|
133
|
-
}: {
|
|
134
|
-
i: number;
|
|
135
|
-
message: AiChatMessage;
|
|
136
|
-
minHeight?: number;
|
|
137
|
-
})}
|
|
138
|
-
<AiMessage bind:b_message={b_chat.messages[i]} {minHeight} />
|
|
139
|
-
{/snippet}
|
|
140
|
-
|
|
141
|
-
{#snippet defaultUserMessage({
|
|
142
|
-
message
|
|
143
|
-
}: {
|
|
144
|
-
i: number;
|
|
145
|
-
message: AiChatMessage;
|
|
146
|
-
minHeight?: number;
|
|
147
|
-
})}
|
|
148
|
-
<UserMessage {message} />
|
|
149
|
-
{/snippet}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { merge } from '$lib/utils/functions';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
|
-
import type { ClassValue } from 'svelte/elements';
|
|
5
|
-
import AttachedFile from './AttachedFile.svelte';
|
|
6
|
-
import type { AiChatMessage } from './Chat.svelte';
|
|
7
|
-
import Markdown from './Markdown.svelte';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
class?: ClassValue;
|
|
11
|
-
message: AiChatMessage;
|
|
12
|
-
/** How attached files should be rendered */
|
|
13
|
-
attachedFile?: Snippet<[file: File]>;
|
|
14
|
-
/** How the message string should be rendered */
|
|
15
|
-
messageText?: Snippet<[{ message: AiChatMessage }]>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let {
|
|
19
|
-
class: clazz,
|
|
20
|
-
message,
|
|
21
|
-
attachedFile = defaultAttachedFile,
|
|
22
|
-
messageText = defaultMessageText
|
|
23
|
-
}: Props = $props();
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<div class={merge('flex w-full flex-col items-end gap-1', clazz)}>
|
|
27
|
-
{@render messageText({ message })}
|
|
28
|
-
{#if message.files}
|
|
29
|
-
<div class="flex flex-row items-center gap-2">
|
|
30
|
-
{#each message.files as file, i (i)}
|
|
31
|
-
{@render attachedFile(file)}
|
|
32
|
-
{/each}
|
|
33
|
-
</div>
|
|
34
|
-
{/if}
|
|
35
|
-
<div class="flex flex-row items-center gap-2">
|
|
36
|
-
{#if message.time}
|
|
37
|
-
<p class="text-surface-400-600 text-sm">
|
|
38
|
-
{message.time.toLocaleString('de')}
|
|
39
|
-
</p>
|
|
40
|
-
{/if}
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
{#snippet defaultAttachedFile(file: File)}
|
|
45
|
-
<AttachedFile {file} />
|
|
46
|
-
{/snippet}
|
|
47
|
-
|
|
48
|
-
{#snippet defaultMessageText({ message }: { message: AiChatMessage })}
|
|
49
|
-
<div class="bg-surface-200-800 text-surface-contrast-200-800 rounded px-2 py-0.5">
|
|
50
|
-
<Markdown source={message.message} />
|
|
51
|
-
</div>
|
|
52
|
-
{/snippet}
|