Skip to content

Commit cbba829

Browse files
centdixHugoCasaclaude[bot]rubenfiszelgraphite-app[bot]
authored
feat: add navigator mode to AIChat and unify UI (#5859)
* feat: ai flow chat * youpi * feat: preprocessor and error handler support * fix: reactivity * Add GlobalChat component with drawer functionality - Create GlobalChat.svelte with placeholder chat functionality - Create GlobalChatDrawer.svelte as drawer wrapper - Add global chat button to sidebar menu (both mobile and desktop) - Integrate global chat state management in main layout - Include message history, loading states, and error handling - Implement responsive design and proper drawer behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: rubenfiszel <[email protected]> * draft * use triggerable by ai compoennt * make drawer triggerable * implement logic * add inkeep tool * cleaner code * make more things available * more integrations + better system prompt * fix docs fetching * small fix * cleaning * add ask in search bar + right top icon on homepage + suggestions * fix button * disable chat if no ai providers * add inkeep endpoint * draft working stuff * cleaner code * better chat * fix * send license and uid * better anim * move logic * parse links in chat * add missing integration * add reset button * fix * rm file * integrate navigator mode * integrate all changes * add hide button * adjust drawer size * add script ai chat integration * fix drawer * small fixes * small fixes * draft * merge script ai chat with global one * cleaning * fixes * working draft * add aichat service * cleaning more * remove left over from store * more descriptive states * better icon * fix * use pending prompt * cleaning * cleaning * small fix * add inkeep file * clean * add route * Update backend/windmill-api/src/lib.rs Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> * cleaning * fix drawer * save open state in local storage * small fix * fixes * small fixes * move chat request to manager * renaming * move flow effects in manager * move chat effects in manager * remove log * Update frontend/src/lib/components/copilot/CronGen.svelte Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update frontend/src/lib/components/copilot/chat/flow/core.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * change askaibutton folder * define button * ifelse * no any + no default size * use tailwind * use splitpanes * move effects * remove deprecated file * wording * add back disable ai * add error message * modify system prompt * handle confirmation modal * fix * fix * close script settings * fix icon color * fix * fix history manager * fix test panel * save size * remove floating button * fix delete chat * fix * better fix --------- Co-authored-by: HugoCasa <[email protected]> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: rubenfiszel <[email protected]> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 39dbd64 commit cbba829

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2808
-1782
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#[cfg(feature = "private")]
2+
#[allow(unused)]
3+
pub use crate::inkeep_ee::*;
4+
5+
#[cfg(not(feature = "private"))]
6+
use axum::Router;
7+
8+
#[cfg(not(feature = "private"))]
9+
pub fn global_service() -> Router {
10+
Router::new()
11+
}

backend/windmill-api/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ pub mod http_triggers;
9393
#[cfg(feature = "private")]
9494
pub mod indexer_ee;
9595
mod indexer_oss;
96+
#[cfg(feature = "private")]
97+
mod inkeep_ee;
98+
mod inkeep_oss;
9699
mod inputs;
97100
mod integration;
98101
#[cfg(feature = "postgres_trigger")]
@@ -619,6 +622,7 @@ pub async fn run_server(
619622
.nest("/schedules", schedule::global_service())
620623
.nest("/embeddings", embeddings::global_service())
621624
.nest("/ai", ai::global_service())
625+
.nest("/inkeep", inkeep_oss::global_service())
622626
.route_layer(from_extractor::<ApiAuthed>())
623627
.route_layer(from_extractor::<users::Tokened>())
624628
.nest("/jobs", jobs::global_root_service())

frontend/src/lib/components/AppConnectInner.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
(x) =>
6464
passwords.includes(x) ||
6565
['token', 'secret', 'key', 'pass', 'private'].some((y) => x.toLowerCase().includes(y))
66-
)
66+
)
6767
}
6868
6969
let linkedSecret: string | undefined = undefined
@@ -487,6 +487,8 @@
487487
{#if !nativeLanguagesCategory.includes(key)}
488488
<!-- Exclude specific items -->
489489
<Button
490+
aiId={`app-connect-inner-${key}`}
491+
aiDescription={`Connect to ${key}`}
490492
size="sm"
491493
variant="border"
492494
color={key === resourceType ? 'blue' : 'light'}
@@ -552,7 +554,8 @@
552554
{#if renderDescription}
553555
<div>
554556
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-1">GH Markdown</div>
555-
<textarea use:autosize bind:value={description} placeholder={'Resource description'}></textarea>
557+
<textarea use:autosize bind:value={description} placeholder={'Resource description'}
558+
></textarea>
556559
</div>
557560
{:else if description == undefined || description == ''}
558561
<div class="text-sm text-tertiary">No description provided</div>

frontend/src/lib/components/DropdownV2.svelte

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
import ResolveOpen from '$lib/components/common/menu/ResolveOpen.svelte'
1717
import Button from '$lib/components/common/button/Button.svelte'
1818
import { twMerge } from 'tailwind-merge'
19+
import TriggerableByAI from './TriggerableByAI.svelte'
1920
21+
export let aiId: string | undefined = undefined
22+
export let aiDescription: string | undefined = undefined
2023
export let items: Item[] | (() => Item[]) | (() => Promise<Item[]>) = []
2124
export let disabled = false
2225
export let placement: Placement = 'bottom-end'
@@ -28,6 +31,8 @@
2831
export let customWidth: number | undefined = undefined
2932
export let customMenu = false
3033
34+
let buttonEl: HTMLButtonElement | undefined = undefined
35+
3136
const {
3237
elements: { menu, item, trigger },
3338
states,
@@ -76,36 +81,43 @@
7681

7782
<ResolveOpen {open} on:open on:close />
7883

79-
<button
80-
class={twMerge('w-full flex items-center justify-end', fixedHeight && 'h-8', $$props.class)}
81-
use:melt={$trigger}
82-
{disabled}
83-
on:click={(e) => e.stopPropagation()}
84-
use:pointerDownOutside={{
85-
capture: true,
86-
stopPropagation: false,
87-
exclude: getMenuElements,
88-
customEventName: 'pointerdown_menu'
89-
}}
90-
on:pointerdown_outside={() => {
91-
if (usePointerDownOutside) {
92-
close()
93-
}
94-
}}
95-
data-menu
96-
>
97-
{#if $$slots.buttonReplacement}
98-
<slot name="buttonReplacement" />
99-
{:else}
100-
<Button
101-
nonCaptureEvent
102-
size="xs"
103-
color="light"
104-
startIcon={{ icon: MoreVertical }}
105-
btnClasses="bg-transparent"
106-
/>
107-
{/if}
108-
</button>
84+
<TriggerableByAI id={aiId} description={aiDescription} onTrigger={() => buttonEl?.click()}>
85+
<button
86+
bind:this={buttonEl}
87+
class={twMerge(
88+
'w-full flex items-center justify-end h-full',
89+
fixedHeight && 'h-8',
90+
$$props.class
91+
)}
92+
use:melt={$trigger}
93+
{disabled}
94+
on:click={(e) => e.stopPropagation()}
95+
use:pointerDownOutside={{
96+
capture: true,
97+
stopPropagation: false,
98+
exclude: getMenuElements,
99+
customEventName: 'pointerdown_menu'
100+
}}
101+
on:pointerdown_outside={() => {
102+
if (usePointerDownOutside) {
103+
close()
104+
}
105+
}}
106+
data-menu
107+
>
108+
{#if $$slots.buttonReplacement}
109+
<slot name="buttonReplacement" />
110+
{:else}
111+
<Button
112+
nonCaptureEvent
113+
size="xs"
114+
color="light"
115+
startIcon={{ icon: MoreVertical }}
116+
btnClasses="bg-transparent"
117+
/>
118+
{/if}
119+
</button>
120+
</TriggerableByAI>
109121

110122
{#if open && !hidePopup}
111123
<div use:melt={$menu} data-menu class="z-[6000] transition-all duration-100">
@@ -116,7 +128,7 @@
116128
class="bg-surface border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
117129
style={customWidth ? `width: ${customWidth}px` : ''}
118130
>
119-
<DropdownV2Inner items={computeItems} meltItem={item} />
131+
<DropdownV2Inner {aiId} items={computeItems} meltItem={item} />
120132
</div>
121133
{/if}
122134
</div>

frontend/src/lib/components/DropdownV2Inner.svelte

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import { twMerge } from 'tailwind-merge'
55
import type { MenubarMenuElements } from '@melt-ui/svelte'
66
import type { Item } from '$lib/utils'
7+
import TriggerableByAI from './TriggerableByAI.svelte'
8+
import { goto } from '$app/navigation'
79
810
interface Props {
11+
aiId?: string
912
items?: Item[] | (() => Item[]) | (() => Promise<Item[]>)
1013
meltItem: MenubarMenuElements['item']
1114
}
1215
13-
let { items = [], meltItem }: Props = $props()
16+
let { aiId, items = [], meltItem }: Props = $props()
1417
1518
let computedItems: Item[] | undefined = $state(undefined)
1619
async function computeItems() {
@@ -27,29 +30,42 @@
2730
{#if computedItems}
2831
<div class="flex flex-col">
2932
{#each computedItems ?? [] as item}
30-
<MenuItem
31-
on:click={(e) => item?.action?.(e)}
32-
href={item?.href}
33-
disabled={item?.disabled}
34-
class={twMerge(
35-
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all',
36-
'data-[highlighted]:bg-surface-hover',
37-
'flex flex-row gap-2 items-center',
38-
item?.disabled && 'text-gray-400 cursor-not-allowed',
39-
item?.type === 'delete' &&
40-
!item?.disabled &&
41-
'text-red-500 hover:bg-red-100 hover:text-red-500 data-[highlighted]:text-red-500 data-[highlighted]:bg-red-100'
42-
)}
43-
item={meltItem}
33+
<TriggerableByAI
34+
id={`${aiId ? `${aiId}-${item.displayName}` : undefined}`}
35+
description={item.displayName}
36+
onTrigger={() => {
37+
if (item.action) {
38+
item.action({} as MouseEvent)
39+
}
40+
if (item.href) {
41+
goto(item.href)
42+
}
43+
}}
4444
>
45-
{#if item.icon}
46-
<item.icon size={14} color={item.iconColor} />
47-
{/if}
48-
<p title={item.displayName} class="truncate grow min-w-0 whitespace-nowrap text-left">
49-
{item.displayName}
50-
</p>
51-
{@render item.extra?.()}
52-
</MenuItem>
45+
<MenuItem
46+
on:click={(e) => item?.action?.(e)}
47+
href={item?.href}
48+
disabled={item?.disabled}
49+
class={twMerge(
50+
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all w-full',
51+
'data-[highlighted]:bg-surface-hover',
52+
'flex flex-row gap-2 items-center',
53+
item?.disabled && 'text-gray-400 cursor-not-allowed',
54+
item?.type === 'delete' &&
55+
!item?.disabled &&
56+
'text-red-500 hover:bg-red-100 hover:text-red-500 data-[highlighted]:text-red-500 data-[highlighted]:bg-red-100'
57+
)}
58+
item={meltItem}
59+
>
60+
{#if item.icon}
61+
<item.icon size={14} color={item.iconColor} />
62+
{/if}
63+
<p title={item.displayName} class="truncate grow min-w-0 whitespace-nowrap text-left">
64+
{item.displayName}
65+
</p>
66+
{@render item.extra?.()}
67+
</MenuItem>
68+
</TriggerableByAI>
5369
{/each}
5470
</div>
5571
{:else}

frontend/src/lib/components/EditorBar.svelte

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
594594
<div class="flex items-center gap-0.5">
595595
{#if showContextVarPicker && customUi?.contextVar != false}
596596
<Button
597+
aiId="editor-bar-add-context-variable"
598+
aiDescription="Add context variable"
597599
title="Add context variable"
598600
color="light"
599601
on:click={contextualVariablePicker.openDrawer}
@@ -607,6 +609,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
607609
{/if}
608610
{#if showVarPicker && customUi?.variable != false}
609611
<Button
612+
aiId="editor-bar-add-variable"
613+
aiDescription="Add variable"
610614
title="Add variable"
611615
color="light"
612616
btnClasses="!font-medium text-tertiary"
@@ -622,6 +626,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
622626

623627
{#if showResourcePicker && customUi?.resource != false}
624628
<Button
629+
aiId="editor-bar-add-resource"
630+
aiDescription="Add resource"
625631
title="Add resource"
626632
btnClasses="!font-medium text-tertiary"
627633
size="xs"
@@ -637,6 +643,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
637643

638644
{#if showResourceTypePicker && customUi?.type != false}
639645
<Button
646+
aiId="editor-bar-add-resource-type"
647+
aiDescription="Add resource type"
640648
title="Add resource type"
641649
btnClasses="!font-medium text-tertiary"
642650
size="xs"
@@ -652,6 +660,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
652660

653661
{#if customUi?.reset != false}
654662
<Button
663+
aiId="editor-bar-reset-content"
664+
aiDescription="Reset content"
655665
title="Reset Content"
656666
btnClasses="!font-medium text-tertiary"
657667
size="xs"
@@ -668,6 +678,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
668678
{#if customUi?.assistants != false}
669679
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash' || lang == 'nu'}
670680
<Button
681+
aiId="editor-bar-reload-assistants"
682+
aiDescription="Reload assistants"
671683
btnClasses="!font-medium text-tertiary"
672684
size="xs"
673685
spacingSize="md"

frontend/src/lib/components/FlowBuilder.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
} from './triggers/utils'
7070
import DraftTriggersConfirmationModal from './common/confirmationModal/DraftTriggersConfirmationModal.svelte'
7171
import { Triggers } from './triggers/triggers.svelte'
72+
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
7273
7374
export let initialPath: string = ''
7475
export let pathStoreInit: string | undefined = undefined
@@ -931,8 +932,8 @@
931932
</div>
932933
</Button>
933934
{/if}
934-
{#if !disableAi && customUi?.topBar?.aiBuilder != false && flowEditor?.getIsAiPanelClosed()}
935-
<FlowAIButton openPanel={() => flowEditor?.toggleAiPanel()} />
935+
{#if !disableAi && customUi?.topBar?.aiBuilder != false && !aiChatManager.open}
936+
<FlowAIButton openPanel={() => aiChatManager.openChat()} />
936937
{/if}
937938
<FlowPreviewButtons
938939
on:openTriggers={(e) => {

0 commit comments

Comments
 (0)