add ask in search bar + right top icon on homepage + suggestions
This commit is contained in:
29
frontend/src/lib/components/AskAiButton.svelte
Normal file
29
frontend/src/lib/components/AskAiButton.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { WandSparkles } from 'lucide-svelte'
|
||||
import { globalChatOpen, globalChatInitialInput } from '$lib/stores'
|
||||
|
||||
interface Props {
|
||||
label?: string
|
||||
initialInput?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const { label, initialInput, onClick: onClickProp }: Props = $props()
|
||||
|
||||
const onClick = () => {
|
||||
globalChatOpen.set(true)
|
||||
if (initialInput) {
|
||||
globalChatInitialInput.set(initialInput)
|
||||
}
|
||||
onClickProp?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size="xs2"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
on:click={onClick}
|
||||
startIcon={{ icon: WandSparkles }}>{label}</Button
|
||||
>
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Send, Loader2 } from 'lucide-svelte'
|
||||
import { chatRequest, prepareSystemMessage } from './core'
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
|
||||
import { globalChatInitialInput } from '$lib/stores'
|
||||
|
||||
let chatHistory = [
|
||||
{
|
||||
@@ -17,6 +18,17 @@
|
||||
let currentReply = $state('')
|
||||
let messages = $state(chatHistory)
|
||||
|
||||
// Suggested questions for the user
|
||||
const suggestions = $state([
|
||||
'How do I create a new workflow?',
|
||||
"What's the difference between scripts and flows?",
|
||||
'How can I connect to a database?',
|
||||
'How do I schedule a recurring job?'
|
||||
])
|
||||
|
||||
// Check if there are any user messages
|
||||
const hasUserMessages = $derived(messages.some((msg) => msg.role === 'user'))
|
||||
|
||||
let abortController = new AbortController()
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -45,6 +57,19 @@
|
||||
inputValue = ''
|
||||
isSubmitting = false
|
||||
}
|
||||
|
||||
function submitSuggestion(suggestion: string) {
|
||||
inputValue = suggestion
|
||||
handleSubmit()
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (globalChatInitialInput) {
|
||||
inputValue = $globalChatInitialInput
|
||||
globalChatInitialInput.set('')
|
||||
handleSubmit()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full bg-surface z-10">
|
||||
@@ -75,6 +100,24 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Suggestion buttons when no user messages yet -->
|
||||
{#if !hasUserMessages && !isSubmitting}
|
||||
<div class="w-full pt-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each suggestions as suggestion}
|
||||
<Button
|
||||
on:click={() => submitSuggestion(suggestion)}
|
||||
size="xs2"
|
||||
color="gray"
|
||||
buttonType="button"
|
||||
>
|
||||
{suggestion}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Input Area -->
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
type ListableApp,
|
||||
type ListableRawApp,
|
||||
type Script,
|
||||
|
||||
type SearchJobsIndexResponse
|
||||
|
||||
} from '$lib/gen'
|
||||
import { clickOutside, isMac } from '$lib/utils'
|
||||
import {
|
||||
@@ -44,6 +42,7 @@
|
||||
import Logs from 'lucide-svelte/icons/logs'
|
||||
import { AwsIcon, GoogleCloudIcon, KafkaIcon, MqttIcon, NatsIcon } from '../icons'
|
||||
import RunsSearch from './RunsSearch.svelte'
|
||||
import AskAiButton from '../AskAiButton.svelte'
|
||||
|
||||
let open: boolean = false
|
||||
|
||||
@@ -264,7 +263,6 @@
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
let queryParseErrors: string[] = []
|
||||
|
||||
async function handleSearch() {
|
||||
@@ -320,8 +318,8 @@
|
||||
)
|
||||
}
|
||||
if (tab === 'runs') {
|
||||
await tick()
|
||||
runsSearch?.handleRunSearch(removePrefix(searchTerm, RUNS_PREFIX))
|
||||
await tick()
|
||||
runsSearch?.handleRunSearch(removePrefix(searchTerm, RUNS_PREFIX))
|
||||
}
|
||||
selectedItem = selectItem(0)
|
||||
}
|
||||
@@ -413,7 +411,7 @@
|
||||
open = false
|
||||
goto(path)
|
||||
} else {
|
||||
window.open(path, "_blank")
|
||||
window.open(path, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,8 +579,7 @@
|
||||
let runsSearch: RunsSearch
|
||||
let runSearchRemainingCount: number | undefined = undefined
|
||||
let runSearchTotalCount: number | undefined = undefined
|
||||
let indexMetadata: SearchJobsIndexResponse["index_metadata"] = undefined
|
||||
|
||||
let indexMetadata: SearchJobsIndexResponse['index_metadata'] = undefined
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
@@ -618,6 +615,15 @@
|
||||
>{placeholderFromPrefix(searchTerm)}</label
|
||||
>
|
||||
</div>
|
||||
{#if (itemMap[tab] ?? []).length === 0 && searchTerm.length > 0}
|
||||
<AskAiButton
|
||||
label="Ask AI"
|
||||
initialInput={searchTerm}
|
||||
onClick={() => {
|
||||
closeModal()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if queryParseErrors.length > 0}
|
||||
<Popover notClickable placement="bottom-start">
|
||||
<AlertTriangle size={16} class="text-yellow-500" />
|
||||
@@ -682,7 +688,9 @@
|
||||
{#if (itemMap[tab] ?? []).length === 0}
|
||||
<div class="flex w-full justify-center items-center">
|
||||
<div class="text-tertiary text-center">
|
||||
<div class="text-2xl font-bold">Nothing found</div>
|
||||
<div class="text-2xl font-bold"
|
||||
>Nothing found, ask the AI to find what you need !</div
|
||||
>
|
||||
<div class="text-sm">Tip: press `esc` to quickly clear the search bar</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -718,20 +726,20 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if tab === 'runs'}
|
||||
<RunsSearch
|
||||
bind:queryParseErrors
|
||||
bind:this={runsSearch}
|
||||
bind:selectedItem
|
||||
bind:selectedWorkspace
|
||||
bind:mouseMoved
|
||||
bind:loadedRuns={itemMap['runs']}
|
||||
bind:open
|
||||
{selectItem}
|
||||
searchTerm={removePrefix(searchTerm, RUNS_PREFIX)}
|
||||
bind:runSearchRemainingCount
|
||||
bind:runSearchTotalCount
|
||||
bind:indexMetadata
|
||||
/>
|
||||
<RunsSearch
|
||||
bind:queryParseErrors
|
||||
bind:this={runsSearch}
|
||||
bind:selectedItem
|
||||
bind:selectedWorkspace
|
||||
bind:mouseMoved
|
||||
bind:loadedRuns={itemMap['runs']}
|
||||
bind:open
|
||||
{selectItem}
|
||||
searchTerm={removePrefix(searchTerm, RUNS_PREFIX)}
|
||||
bind:runSearchRemainingCount
|
||||
bind:runSearchTotalCount
|
||||
bind:indexMetadata
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -174,6 +174,7 @@ export const triggerablesByAI = writable<
|
||||
Record<string, { description: string; onTrigger: ((id: string) => void) | undefined }>
|
||||
>({})
|
||||
export const globalChatOpen = writable<boolean>(false)
|
||||
export const globalChatInitialInput = writable<string>('')
|
||||
|
||||
type SQLBaseSchema = {
|
||||
[schemaKey: string]: {
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
on:click={() => openGlobalChat()}
|
||||
isCollapsed={false}
|
||||
icon={MessageCircle}
|
||||
label="Global Chat"
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -527,7 +527,7 @@
|
||||
on:click={() => openGlobalChat()}
|
||||
{isCollapsed}
|
||||
icon={MessageCircle}
|
||||
label="Global Chat"
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -634,7 +634,7 @@
|
||||
on:click={() => openGlobalChat()}
|
||||
{isCollapsed}
|
||||
icon={MessageCircle}
|
||||
label="Global Chat"
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { AppService, FlowService, type OpenFlow, type Script } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { globalChatOpen, userStore, workspaceStore } from '$lib/stores'
|
||||
import { Alert, Button, Drawer, DrawerContent, Tab, Tabs } from '$lib/components/common'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import CreateActionsFlow from '$lib/components/flows/CreateActionsFlow.svelte'
|
||||
@@ -23,6 +23,7 @@
|
||||
import { setQuery } from '$lib/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { goto, replaceState } from '$app/navigation'
|
||||
import AskAiButton from '$lib/components/AskAiButton.svelte'
|
||||
|
||||
type Tab = 'hub' | 'workspace'
|
||||
|
||||
@@ -214,7 +215,12 @@
|
||||
</Drawer>
|
||||
|
||||
<div>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 h-fit-content">
|
||||
{#if !$globalChatOpen}
|
||||
<div class="fixed top-4 right-4 z-50">
|
||||
<AskAiButton />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-8 md:px-8 h-fit-content">
|
||||
{#if $workspaceStore == 'admins'}
|
||||
<div class="my-4"></div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user