@makolabs/ripple 0.4.0 → 0.5.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/README.md +165 -205
- package/dist/adapters/ai/OpenAIAdapter.d.ts +115 -0
- package/dist/adapters/ai/OpenAIAdapter.js +568 -0
- package/dist/adapters/ai/index.d.ts +3 -0
- package/dist/adapters/ai/index.js +3 -0
- package/dist/adapters/ai/types.d.ts +108 -0
- package/dist/adapters/ai/types.js +31 -0
- package/dist/adapters/storage/BaseAdapter.js +31 -31
- package/dist/ai/AIChatInterface.svelte +435 -0
- package/dist/ai/AIChatInterface.svelte.d.ts +18 -0
- package/dist/ai/ChatInput.svelte +211 -0
- package/dist/ai/ChatInput.svelte.d.ts +18 -0
- package/dist/ai/CodeRenderer.svelte +174 -0
- package/dist/ai/CodeRenderer.svelte.d.ts +8 -0
- package/dist/ai/ComposeDropdown.svelte +171 -0
- package/dist/ai/ComposeDropdown.svelte.d.ts +9 -0
- package/dist/ai/MermaidRenderer.svelte +89 -0
- package/dist/ai/MermaidRenderer.svelte.d.ts +7 -0
- package/dist/ai/MessageBox.svelte +403 -0
- package/dist/ai/MessageBox.svelte.d.ts +12 -0
- package/dist/ai/ThinkingDisplay.svelte +275 -0
- package/dist/ai/ThinkingDisplay.svelte.d.ts +9 -0
- package/dist/ai/ai-chat-interface.d.ts +161 -0
- package/dist/ai/ai-chat-interface.js +63 -0
- package/dist/ai/content-detector.d.ts +41 -0
- package/dist/ai/content-detector.js +153 -0
- package/dist/config/ai.d.ts +13 -0
- package/dist/config/ai.js +43 -0
- package/dist/elements/accordion/accordion.js +1 -1
- package/dist/elements/badge/Badge.svelte +14 -3
- package/dist/elements/dropdown/Dropdown.svelte +2 -2
- package/dist/elements/dropdown/Select.svelte +1 -1
- package/dist/elements/progress/Progress.svelte +7 -10
- package/dist/file-browser/FileBrowser.svelte +1 -1
- package/dist/forms/DateRange.svelte +18 -16
- package/dist/forms/NumberInput.svelte +1 -1
- package/dist/forms/RadioInputs.svelte +1 -1
- package/dist/forms/RadioPill.svelte +1 -1
- package/dist/forms/Tags.svelte +2 -2
- package/dist/helper/date.d.ts +1 -0
- package/dist/helper/date.js +6 -0
- package/dist/index.d.ts +67 -3
- package/dist/index.js +11 -0
- package/dist/layout/activity-list/ActivityList.svelte +94 -0
- package/dist/layout/activity-list/ActivityList.svelte.d.ts +4 -0
- package/dist/layout/activity-list/activity-list.d.ts +152 -0
- package/dist/layout/activity-list/activity-list.js +59 -0
- package/dist/layout/card/Card.svelte +1 -5
- package/dist/layout/card/metric-card.d.ts +18 -18
- package/dist/layout/table/Cells.svelte +1 -7
- package/dist/layout/table/Cells.svelte.d.ts +1 -1
- package/dist/modal/Modal.svelte +4 -2
- package/dist/modal/Modal.svelte.d.ts +1 -1
- package/dist/modal/modal.d.ts +19 -18
- package/dist/modal/modal.js +7 -6
- package/dist/sonner/sonner.svelte +1 -7
- package/dist/types/markdown.d.ts +14 -0
- package/dist/utils/Portal.svelte +1 -1
- package/package.json +128 -121
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { browser } from '$app/environment';
|
|
4
|
+
import { tv } from 'tailwind-variants';
|
|
5
|
+
import ComposeDropdown from './ComposeDropdown.svelte';
|
|
6
|
+
import type { VariantColors } from '../index.js';
|
|
7
|
+
|
|
8
|
+
interface ChatInputProps {
|
|
9
|
+
userInput: string;
|
|
10
|
+
textareaRef?: HTMLTextAreaElement;
|
|
11
|
+
placeholder: string;
|
|
12
|
+
isProcessing: boolean;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
isAdapterConfigured: boolean;
|
|
15
|
+
color?: VariantColors;
|
|
16
|
+
hasMessages: boolean;
|
|
17
|
+
thinkingMode?: boolean;
|
|
18
|
+
onSubmit: () => void;
|
|
19
|
+
onKeyDown: (event: KeyboardEvent) => void;
|
|
20
|
+
onThinkingToggle?: (enabled: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
userInput = $bindable(),
|
|
25
|
+
textareaRef = $bindable(),
|
|
26
|
+
placeholder,
|
|
27
|
+
isProcessing,
|
|
28
|
+
disabled,
|
|
29
|
+
isAdapterConfigured,
|
|
30
|
+
color = 'primary',
|
|
31
|
+
hasMessages,
|
|
32
|
+
thinkingMode = $bindable(),
|
|
33
|
+
onSubmit,
|
|
34
|
+
onKeyDown,
|
|
35
|
+
onThinkingToggle
|
|
36
|
+
}: ChatInputProps = $props();
|
|
37
|
+
|
|
38
|
+
const textarea = tv({
|
|
39
|
+
base: 'flex-1 rounded-md text-sm placeholder-gray-400 focus:outline-none transition-all duration-200 bg-gray-200/60 border-0 resize-none',
|
|
40
|
+
variants: {
|
|
41
|
+
layout: {
|
|
42
|
+
empty: 'px-6 py-4 min-h-[52px] max-h-[120px]',
|
|
43
|
+
chat: 'px-6 py-3 min-h-[48px] max-h-[393px] overflow-y-auto'
|
|
44
|
+
},
|
|
45
|
+
state: {
|
|
46
|
+
disabled: 'bg-gray-100 cursor-not-allowed text-gray-500',
|
|
47
|
+
enabled: 'hover:bg-gray-200/80 focus:bg-white focus:ring-2 focus:shadow-md'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
compoundVariants: [
|
|
51
|
+
{
|
|
52
|
+
state: 'enabled',
|
|
53
|
+
class: 'focus:ring-primary-500/20'
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const sendButton = tv({
|
|
59
|
+
base: 'flex h-12 w-12 items-center justify-center rounded-full transition-all duration-200',
|
|
60
|
+
variants: {
|
|
61
|
+
state: {
|
|
62
|
+
disabled: 'bg-gray-200 text-gray-400 cursor-not-allowed',
|
|
63
|
+
enabled: 'text-white bg-primary-500 hover:bg-primary-600 focus:ring-2 focus:ring-primary-500/20'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
const inputArea = tv({
|
|
71
|
+
variants: {
|
|
72
|
+
layout: {
|
|
73
|
+
empty: 'w-full max-w-3xl',
|
|
74
|
+
chat: 'sticky bottom-0 z-10 flex-shrink-0 bg-white/50 px-6 pt-2 pb-0 backdrop-blur-sm'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const inputContainer = tv({
|
|
80
|
+
base: 'flex items-end gap-3',
|
|
81
|
+
variants: {
|
|
82
|
+
layout: {
|
|
83
|
+
empty: '',
|
|
84
|
+
chat: 'mx-auto max-w-4xl'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Computed classes
|
|
90
|
+
const textareaClasses = $derived(
|
|
91
|
+
textarea({
|
|
92
|
+
layout: hasMessages ? 'chat' : 'empty',
|
|
93
|
+
state: disabled || !isAdapterConfigured ? 'disabled' : 'enabled'
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const sendButtonClasses = $derived(
|
|
98
|
+
sendButton({
|
|
99
|
+
state: disabled || !isAdapterConfigured || isProcessing || !userInput.trim() ? 'disabled' : 'enabled'
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
const inputAreaClasses = $derived(
|
|
106
|
+
inputArea({
|
|
107
|
+
layout: hasMessages ? 'chat' : 'empty'
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const inputContainerClasses = $derived(
|
|
112
|
+
inputContainer({
|
|
113
|
+
layout: hasMessages ? 'chat' : 'empty'
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Auto-resize textarea functionality
|
|
118
|
+
function autoResizeTextarea() {
|
|
119
|
+
if (textareaRef) {
|
|
120
|
+
textareaRef.style.height = 'auto';
|
|
121
|
+
textareaRef.style.height = Math.min(textareaRef.scrollHeight, 393) + 'px';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Watch for userInput changes and auto-resize
|
|
126
|
+
$effect(() => {
|
|
127
|
+
if (userInput !== undefined) {
|
|
128
|
+
// Small delay to ensure DOM is updated
|
|
129
|
+
setTimeout(autoResizeTextarea, 0);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Conditional handlers for textarea based on layout
|
|
134
|
+
const textareaHandlers = $derived({
|
|
135
|
+
onkeydown: onKeyDown,
|
|
136
|
+
// Only add auto-resize handlers in chat layout (when messages exist)
|
|
137
|
+
...(hasMessages && {
|
|
138
|
+
oninput: autoResizeTextarea,
|
|
139
|
+
onpaste: () => setTimeout(autoResizeTextarea, 0)
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function handleSubmit() {
|
|
144
|
+
onSubmit();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
onMount(() => {
|
|
148
|
+
// Auto-focus textarea on mount if configured
|
|
149
|
+
if (browser && isAdapterConfigured) {
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
textareaRef?.focus();
|
|
152
|
+
}, 100);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<!-- Single Input Area (used by both layouts) -->
|
|
158
|
+
<div class={inputAreaClasses}>
|
|
159
|
+
<div class={inputContainerClasses}>
|
|
160
|
+
<!-- Compose Dropdown -->
|
|
161
|
+
<ComposeDropdown
|
|
162
|
+
{disabled}
|
|
163
|
+
{isAdapterConfigured}
|
|
164
|
+
bind:thinkingMode
|
|
165
|
+
{onThinkingToggle}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
<textarea
|
|
169
|
+
bind:this={textareaRef}
|
|
170
|
+
bind:value={userInput}
|
|
171
|
+
{...textareaHandlers}
|
|
172
|
+
{placeholder}
|
|
173
|
+
class={textareaClasses}
|
|
174
|
+
rows={1}
|
|
175
|
+
disabled={isProcessing || disabled || !isAdapterConfigured}
|
|
176
|
+
></textarea>
|
|
177
|
+
|
|
178
|
+
<button
|
|
179
|
+
class={sendButtonClasses}
|
|
180
|
+
onclick={handleSubmit}
|
|
181
|
+
disabled={isProcessing || disabled || !userInput.trim() || !isAdapterConfigured}
|
|
182
|
+
aria-label="Send message"
|
|
183
|
+
>
|
|
184
|
+
{#if isProcessing}
|
|
185
|
+
<svg class="h-5 w-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
186
|
+
<path
|
|
187
|
+
stroke-linecap="round"
|
|
188
|
+
stroke-linejoin="round"
|
|
189
|
+
stroke-width="2"
|
|
190
|
+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
|
191
|
+
/>
|
|
192
|
+
</svg>
|
|
193
|
+
{:else}
|
|
194
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
195
|
+
<path
|
|
196
|
+
stroke-linecap="round"
|
|
197
|
+
stroke-linejoin="round"
|
|
198
|
+
stroke-width="2"
|
|
199
|
+
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
|
|
200
|
+
/>
|
|
201
|
+
</svg>
|
|
202
|
+
{/if}
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="mt-2 h-6 px-2">
|
|
207
|
+
{#if userInput.trim() && !isProcessing}
|
|
208
|
+
<p class="text-xs text-gray-500">Press Enter to send • Shift+Enter for new line</p>
|
|
209
|
+
{/if}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { VariantColors } from '../index.js';
|
|
2
|
+
interface ChatInputProps {
|
|
3
|
+
userInput: string;
|
|
4
|
+
textareaRef?: HTMLTextAreaElement;
|
|
5
|
+
placeholder: string;
|
|
6
|
+
isProcessing: boolean;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
isAdapterConfigured: boolean;
|
|
9
|
+
color?: VariantColors;
|
|
10
|
+
hasMessages: boolean;
|
|
11
|
+
thinkingMode?: boolean;
|
|
12
|
+
onSubmit: () => void;
|
|
13
|
+
onKeyDown: (event: KeyboardEvent) => void;
|
|
14
|
+
onThinkingToggle?: (enabled: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const ChatInput: import("svelte").Component<ChatInputProps, {}, "thinkingMode" | "userInput" | "textareaRef">;
|
|
17
|
+
type ChatInput = ReturnType<typeof ChatInput>;
|
|
18
|
+
export default ChatInput;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Highlight from 'svelte-highlight';
|
|
3
|
+
import { HighlightAuto } from 'svelte-highlight';
|
|
4
|
+
|
|
5
|
+
// Import common languages
|
|
6
|
+
import javascript from 'svelte-highlight/languages/javascript';
|
|
7
|
+
import typescript from 'svelte-highlight/languages/typescript';
|
|
8
|
+
import python from 'svelte-highlight/languages/python';
|
|
9
|
+
import java from 'svelte-highlight/languages/java';
|
|
10
|
+
import cpp from 'svelte-highlight/languages/cpp';
|
|
11
|
+
import csharp from 'svelte-highlight/languages/csharp';
|
|
12
|
+
import php from 'svelte-highlight/languages/php';
|
|
13
|
+
import ruby from 'svelte-highlight/languages/ruby';
|
|
14
|
+
import go from 'svelte-highlight/languages/go';
|
|
15
|
+
import rust from 'svelte-highlight/languages/rust';
|
|
16
|
+
import swift from 'svelte-highlight/languages/swift';
|
|
17
|
+
import kotlin from 'svelte-highlight/languages/kotlin';
|
|
18
|
+
import css from 'svelte-highlight/languages/css';
|
|
19
|
+
import html from 'svelte-highlight/languages/xml';
|
|
20
|
+
import json from 'svelte-highlight/languages/json';
|
|
21
|
+
import yaml from 'svelte-highlight/languages/yaml';
|
|
22
|
+
import sql from 'svelte-highlight/languages/sql';
|
|
23
|
+
import bash from 'svelte-highlight/languages/bash';
|
|
24
|
+
import powershell from 'svelte-highlight/languages/powershell';
|
|
25
|
+
import dockerfile from 'svelte-highlight/languages/dockerfile';
|
|
26
|
+
import markdown from 'svelte-highlight/languages/markdown';
|
|
27
|
+
|
|
28
|
+
// Import a clean dark theme
|
|
29
|
+
import githubDark from 'svelte-highlight/styles/github-dark';
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
code: string;
|
|
33
|
+
language?: string;
|
|
34
|
+
class?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let { code, language = 'text', class: className = '' }: Props = $props();
|
|
38
|
+
|
|
39
|
+
// Language mapping
|
|
40
|
+
const languageMap: Record<string, any> = {
|
|
41
|
+
javascript: javascript,
|
|
42
|
+
js: javascript,
|
|
43
|
+
typescript: typescript,
|
|
44
|
+
ts: typescript,
|
|
45
|
+
python: python,
|
|
46
|
+
py: python,
|
|
47
|
+
java: java,
|
|
48
|
+
cpp: cpp,
|
|
49
|
+
'c++': cpp,
|
|
50
|
+
csharp: csharp,
|
|
51
|
+
'c#': csharp,
|
|
52
|
+
php: php,
|
|
53
|
+
ruby: ruby,
|
|
54
|
+
rb: ruby,
|
|
55
|
+
go: go,
|
|
56
|
+
golang: go,
|
|
57
|
+
rust: rust,
|
|
58
|
+
rs: rust,
|
|
59
|
+
swift: swift,
|
|
60
|
+
kotlin: kotlin,
|
|
61
|
+
kt: kotlin,
|
|
62
|
+
css: css,
|
|
63
|
+
html: html,
|
|
64
|
+
xml: html,
|
|
65
|
+
json: json,
|
|
66
|
+
yaml: yaml,
|
|
67
|
+
yml: yaml,
|
|
68
|
+
sql: sql,
|
|
69
|
+
bash: bash,
|
|
70
|
+
sh: bash,
|
|
71
|
+
shell: bash,
|
|
72
|
+
powershell: powershell,
|
|
73
|
+
ps1: powershell,
|
|
74
|
+
dockerfile: dockerfile,
|
|
75
|
+
docker: dockerfile,
|
|
76
|
+
markdown: markdown,
|
|
77
|
+
md: markdown
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const selectedLanguage = $derived(languageMap[language.toLowerCase()] || null);
|
|
81
|
+
const displayLanguage = $derived(language.toUpperCase());
|
|
82
|
+
|
|
83
|
+
let copied = $state(false);
|
|
84
|
+
|
|
85
|
+
async function copyCode() {
|
|
86
|
+
try {
|
|
87
|
+
await navigator.clipboard.writeText(code);
|
|
88
|
+
copied = true;
|
|
89
|
+
// Reset after 2 seconds
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
copied = false;
|
|
92
|
+
}, 2000);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('Failed to copy code:', err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<svelte:head>
|
|
100
|
+
{@html githubDark}
|
|
101
|
+
</svelte:head>
|
|
102
|
+
|
|
103
|
+
<div class="code-renderer my-4 overflow-hidden rounded-lg border border-gray-700 bg-gray-900 {className}">
|
|
104
|
+
<!-- Header -->
|
|
105
|
+
<div class="flex items-center justify-between bg-gray-800 px-4 py-2 border-b border-gray-700">
|
|
106
|
+
<span class="text-xs font-medium text-gray-300 uppercase tracking-wide">
|
|
107
|
+
{displayLanguage}
|
|
108
|
+
</span>
|
|
109
|
+
<button
|
|
110
|
+
onclick={copyCode}
|
|
111
|
+
class="flex items-center gap-1.5 rounded px-2 py-1 text-xs transition-all duration-200 {copied
|
|
112
|
+
? 'text-green-400 border border-green-500 bg-green-500/10'
|
|
113
|
+
: 'text-gray-400 hover:bg-gray-700 hover:text-gray-200'}"
|
|
114
|
+
title={copied ? "Copied!" : "Copy code"}
|
|
115
|
+
>
|
|
116
|
+
{#if copied}
|
|
117
|
+
<!-- Checkmark icon -->
|
|
118
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
119
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
120
|
+
</svg>
|
|
121
|
+
Copied!
|
|
122
|
+
{:else}
|
|
123
|
+
<!-- Copy icon -->
|
|
124
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
125
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
126
|
+
</svg>
|
|
127
|
+
Copy
|
|
128
|
+
{/if}
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Code Content -->
|
|
133
|
+
<div class="relative">
|
|
134
|
+
{#if selectedLanguage}
|
|
135
|
+
<Highlight language={selectedLanguage} {code} />
|
|
136
|
+
{:else}
|
|
137
|
+
<HighlightAuto {code} />
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<style>
|
|
143
|
+
/* Override highlight.js styles for consistent theming */
|
|
144
|
+
:global(.code-renderer .hljs) {
|
|
145
|
+
color: #e6edf3 !important;
|
|
146
|
+
margin: 0 !important;
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
line-height: 1.6;
|
|
149
|
+
overflow-x: auto;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
:global(.code-renderer pre) {
|
|
153
|
+
margin: 0 !important;
|
|
154
|
+
background: transparent !important;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Ensure consistent scrollbar styling */
|
|
158
|
+
:global(.code-renderer .hljs::-webkit-scrollbar) {
|
|
159
|
+
height: 8px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
:global(.code-renderer .hljs::-webkit-scrollbar-track) {
|
|
163
|
+
background: #21262d;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:global(.code-renderer .hljs::-webkit-scrollbar-thumb) {
|
|
167
|
+
background: #484f58;
|
|
168
|
+
border-radius: 4px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
:global(.code-renderer .hljs::-webkit-scrollbar-thumb:hover) {
|
|
172
|
+
background: #6e7681;
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tv } from 'tailwind-variants';
|
|
3
|
+
import Toggle from '../forms/Toggle.svelte';
|
|
4
|
+
import { Size } from '../variants.js';
|
|
5
|
+
import type { VariantColors } from '../index.js';
|
|
6
|
+
|
|
7
|
+
interface ComposeDropdownProps {
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
isAdapterConfigured?: boolean;
|
|
10
|
+
thinkingMode?: boolean;
|
|
11
|
+
onThinkingToggle?: (enabled: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
disabled = false,
|
|
16
|
+
isAdapterConfigured = true,
|
|
17
|
+
thinkingMode = $bindable(false),
|
|
18
|
+
onThinkingToggle
|
|
19
|
+
}: ComposeDropdownProps = $props();
|
|
20
|
+
|
|
21
|
+
let isOpen = $state(false);
|
|
22
|
+
let dropdownRef: HTMLDivElement | undefined = $state();
|
|
23
|
+
|
|
24
|
+
// Mock models data - will be replaced with real data later
|
|
25
|
+
const models = [
|
|
26
|
+
{ id: 'gpt-4', name: 'GPT-4', provider: 'OpenAI' },
|
|
27
|
+
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', provider: 'OpenAI' },
|
|
28
|
+
{ id: 'claude-3', name: 'Claude 3', provider: 'Anthropic' }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const plusButton = tv({
|
|
32
|
+
base: 'flex h-10 w-10 items-center justify-center rounded-full transition-all duration-200',
|
|
33
|
+
variants: {
|
|
34
|
+
state: {
|
|
35
|
+
disabled: 'bg-gray-100 text-gray-300 cursor-not-allowed',
|
|
36
|
+
enabled: 'bg-gray-200 text-gray-600 hover:bg-gray-300 hover:text-gray-800 focus:ring-2 focus:ring-gray-300/50'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const dropdown = tv({
|
|
42
|
+
base: 'absolute bottom-12 left-0 z-50 w-64 bg-white border border-gray-200 rounded-lg shadow-lg py-2',
|
|
43
|
+
variants: {
|
|
44
|
+
visible: {
|
|
45
|
+
true: 'opacity-100 scale-100',
|
|
46
|
+
false: 'opacity-0 scale-95 pointer-events-none'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const menuItem = tv({
|
|
52
|
+
base: 'flex items-center px-4 py-2 text-sm transition-colors',
|
|
53
|
+
variants: {
|
|
54
|
+
state: {
|
|
55
|
+
enabled: 'text-gray-700 hover:bg-gray-50 cursor-pointer',
|
|
56
|
+
disabled: 'text-gray-400 cursor-not-allowed'
|
|
57
|
+
},
|
|
58
|
+
layout: {
|
|
59
|
+
default: 'justify-between',
|
|
60
|
+
full: 'justify-start'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const plusButtonClasses = $derived(
|
|
66
|
+
plusButton({
|
|
67
|
+
state: disabled || !isAdapterConfigured ? 'disabled' : 'enabled'
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const dropdownClasses = $derived(
|
|
72
|
+
dropdown({
|
|
73
|
+
visible: isOpen
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
function toggleDropdown() {
|
|
78
|
+
if (disabled || !isAdapterConfigured) return;
|
|
79
|
+
isOpen = !isOpen;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Watch for thinkingMode changes and call the callback
|
|
83
|
+
$effect(() => {
|
|
84
|
+
onThinkingToggle?.(thinkingMode);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function closeDropdown() {
|
|
88
|
+
isOpen = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Close dropdown when clicking outside
|
|
92
|
+
function handleClickOutside(event: MouseEvent) {
|
|
93
|
+
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
|
94
|
+
closeDropdown();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
$effect(() => {
|
|
99
|
+
if (isOpen) {
|
|
100
|
+
document.addEventListener('click', handleClickOutside);
|
|
101
|
+
return () => {
|
|
102
|
+
document.removeEventListener('click', handleClickOutside);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<div class="relative" bind:this={dropdownRef}>
|
|
109
|
+
<!-- Plus Button -->
|
|
110
|
+
<button
|
|
111
|
+
class={plusButtonClasses}
|
|
112
|
+
onclick={toggleDropdown}
|
|
113
|
+
disabled={disabled || !isAdapterConfigured}
|
|
114
|
+
aria-label="Compose options"
|
|
115
|
+
aria-expanded={isOpen}
|
|
116
|
+
>
|
|
117
|
+
<svg
|
|
118
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
119
|
+
width="0.7em"
|
|
120
|
+
height="0.7em"
|
|
121
|
+
viewBox="0 0 20 20"
|
|
122
|
+
class="w-5 h-5"
|
|
123
|
+
>
|
|
124
|
+
<path fill="currentColor" d="M10 2.25a.75.75 0 0 1 .75.75v6.25H17a.75.75 0 0 1 0 1.5h-6.25V17a.75.75 0 0 1-1.5 0v-6.25H3a.75.75 0 0 1 0-1.5h6.25V3a.75.75 0 0 1 .75-.75"/>
|
|
125
|
+
</svg>
|
|
126
|
+
</button>
|
|
127
|
+
|
|
128
|
+
<!-- Dropdown Menu -->
|
|
129
|
+
<div class={dropdownClasses}>
|
|
130
|
+
<!-- Models Section -->
|
|
131
|
+
<div class="px-4 py-2 border-b border-gray-100">
|
|
132
|
+
<h3 class="text-xs font-medium text-gray-500 uppercase tracking-wide">Models</h3>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{#each models as model}
|
|
136
|
+
<div class={menuItem({ state: 'disabled', layout: 'default' })}>
|
|
137
|
+
<div class="flex flex-col">
|
|
138
|
+
<span class="font-medium">{model.name}</span>
|
|
139
|
+
<span class="text-xs text-gray-400">{model.provider}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="text-xs text-gray-400">Disabled</div>
|
|
142
|
+
</div>
|
|
143
|
+
{/each}
|
|
144
|
+
|
|
145
|
+
<!-- Divider -->
|
|
146
|
+
<div class="border-t border-gray-100 my-2"></div>
|
|
147
|
+
|
|
148
|
+
<!-- Thinking Mode Option -->
|
|
149
|
+
<div class="px-4 py-2 border-b border-gray-100">
|
|
150
|
+
<h3 class="text-xs font-medium text-gray-500 uppercase tracking-wide">Options</h3>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class={menuItem({ state: 'enabled', layout: 'full' }) + ' w-full cursor-pointer'}>
|
|
154
|
+
<div class="flex items-center justify-between w-full">
|
|
155
|
+
<div class="flex flex-col items-start">
|
|
156
|
+
<span class="font-medium">Thinking Mode</span>
|
|
157
|
+
<span class="text-xs text-gray-500">Show reasoning process</span>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="flex items-center ml-4">
|
|
160
|
+
<Toggle
|
|
161
|
+
name="thinking-mode"
|
|
162
|
+
bind:value={thinkingMode}
|
|
163
|
+
size={Size.BASE}
|
|
164
|
+
color="primary"
|
|
165
|
+
class="ml-2"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ComposeDropdownProps {
|
|
2
|
+
disabled?: boolean;
|
|
3
|
+
isAdapterConfigured?: boolean;
|
|
4
|
+
thinkingMode?: boolean;
|
|
5
|
+
onThinkingToggle?: (enabled: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const ComposeDropdown: import("svelte").Component<ComposeDropdownProps, {}, "thinkingMode">;
|
|
8
|
+
type ComposeDropdown = ReturnType<typeof ComposeDropdown>;
|
|
9
|
+
export default ComposeDropdown;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Mermaid } from '@friendofsvelte/mermaid';
|
|
3
|
+
import type { MermaidConfig } from '@friendofsvelte/mermaid';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
diagram: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { diagram, class: className = '' }: Props = $props();
|
|
11
|
+
|
|
12
|
+
// Mermaid configuration with multiple node colors like your image
|
|
13
|
+
const config: MermaidConfig = {
|
|
14
|
+
theme: 'base',
|
|
15
|
+
themeVariables: {
|
|
16
|
+
// Background and typography
|
|
17
|
+
background: '#ffffff',
|
|
18
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
19
|
+
fontSize: '14px',
|
|
20
|
+
|
|
21
|
+
// Default node styling
|
|
22
|
+
primaryColor: '#E8F4FD',
|
|
23
|
+
primaryTextColor: '#2C3E50',
|
|
24
|
+
primaryBorderColor: '#B8D4F0',
|
|
25
|
+
|
|
26
|
+
// Lines and connections
|
|
27
|
+
lineColor: '#7F8C8D',
|
|
28
|
+
textColor: '#2C3E50',
|
|
29
|
+
defaultLinkColor: '#7F8C8D',
|
|
30
|
+
edgeLabelBackground: '#ffffff',
|
|
31
|
+
|
|
32
|
+
// Color scale for different node types (matching your image)
|
|
33
|
+
cScale0: '#E8F4FD', // Light blue - Frontend code, Tauri build
|
|
34
|
+
cScale1: '#E8F5E8', // Light green - Bundler, App binary
|
|
35
|
+
cScale2: '#FFF2E8', // Light orange - Dist assets
|
|
36
|
+
cScale3: '#F0E8FF', // Light purple - Rust code
|
|
37
|
+
cScale4: '#FFFDE8', // Light yellow - Installer
|
|
38
|
+
cScale5: '#F8F8F8', // Light gray - fallback
|
|
39
|
+
cScale6: '#E0E0E0', // Medium gray - borders
|
|
40
|
+
cScale7: '#2C3E50', // Dark text
|
|
41
|
+
cScale8: '#7F8C8D', // Line color
|
|
42
|
+
cScale9: '#FFFFFF', // White background
|
|
43
|
+
|
|
44
|
+
// Node styling
|
|
45
|
+
nodeBorder: '#B8D4F0',
|
|
46
|
+
nodeTextColor: '#2C3E50'
|
|
47
|
+
},
|
|
48
|
+
flowchart: {
|
|
49
|
+
useMaxWidth: true,
|
|
50
|
+
htmlLabels: true,
|
|
51
|
+
curve: 'basis',
|
|
52
|
+
nodeSpacing: 50,
|
|
53
|
+
rankSpacing: 80,
|
|
54
|
+
padding: 20,
|
|
55
|
+
diagramPadding: 20
|
|
56
|
+
},
|
|
57
|
+
sequence: {
|
|
58
|
+
useMaxWidth: true,
|
|
59
|
+
actorMargin: 50
|
|
60
|
+
},
|
|
61
|
+
gantt: {
|
|
62
|
+
useMaxWidth: true
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<div class="mermaid-rounded">
|
|
68
|
+
<Mermaid string={diagram} {config}>
|
|
69
|
+
{#snippet error(errorObj)}
|
|
70
|
+
<div class="rounded-lg border border-red-200 bg-red-50 p-4">
|
|
71
|
+
<div class="flex items-center gap-2 text-red-800">
|
|
72
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
73
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
74
|
+
</svg>
|
|
75
|
+
<span class="font-medium">Failed to render Mermaid diagram</span>
|
|
76
|
+
</div>
|
|
77
|
+
<p class="mt-2 text-sm text-red-700">${errorObj.message}</p>
|
|
78
|
+
</div>
|
|
79
|
+
{/snippet}
|
|
80
|
+
</Mermaid>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<style>
|
|
84
|
+
/* Minimal CSS for rounded edges - only what config can't do */
|
|
85
|
+
:global(.mermaid-rounded .node rect) {
|
|
86
|
+
rx: 8px !important;
|
|
87
|
+
ry: 8px !important;
|
|
88
|
+
}
|
|
89
|
+
</style>
|