Skip to content

Commit b63c871

Browse files
committed
Add feature flag, use page, stuff
1 parent d2d75b8 commit b63c871

File tree

17 files changed

+404
-227
lines changed

17 files changed

+404
-227
lines changed

apps/desktop/src/components/ChromeSidebar.svelte

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { goto } from '$app/navigation';
33
import KeyboardShortcutsModal from '$components/KeyboardShortcutsModal.svelte';
44
import ShareIssueModal from '$components/ShareIssueModal.svelte';
5-
import { ircEnabled } from '$lib/config/uiFeatureFlags';
5+
import { experimentalAgent, ircEnabled } from '$lib/config/uiFeatureFlags';
66
import { Project } from '$lib/project/project';
77
import {
88
branchesPath,
@@ -15,7 +15,9 @@
1515
isHistoryPath,
1616
newProjectSettingsPath,
1717
newSettingsPath,
18-
workspacePath
18+
workspacePath,
19+
aiPath,
20+
isAiPath
1921
} from '$lib/routes/routes.svelte';
2022
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
2123
import { TestId } from '$lib/testing/testIds';
@@ -190,6 +192,22 @@
190192
/>
191193
</div>
192194
{/if}
195+
{#if $experimentalAgent}
196+
<div>
197+
{#if isAiPath()}
198+
<div class="active-page-indicator" in:slide={{ axis: 'x', duration: 150 }}></div>
199+
{/if}
200+
<Button
201+
kind="outline"
202+
onclick={() => goto(aiPath(project.id))}
203+
icon="ai"
204+
width={34}
205+
class={['btn-square', isAiPath() && 'btn-active']}
206+
tooltip="Agent"
207+
{disabled}
208+
/>
209+
</div>
210+
{/if}
193211
</div>
194212
<div class="bottom">
195213
<div class="bottom__primary-actions">
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script lang="ts">
2+
import { Ai2Service } from '$lib/ai2/service';
3+
import { getContext } from '@gitbutler/shared/context';
4+
import SectionCard from '@gitbutler/ui/SectionCard.svelte';
5+
import Textbox from '@gitbutler/ui/Textbox.svelte';
6+
7+
// const { projectId }: { projectId: string } = $props(); // Not used, so removed
8+
9+
const ai2Service = getContext(Ai2Service);
10+
const [setOpenRouterToken] = ai2Service.setOpenRouterToken;
11+
const isOpenRouterTokenSet = ai2Service.isOpenRouterTokenSet;
12+
13+
let openRouterToken = $state('');
14+
let lastValue: string | undefined = undefined;
15+
16+
$effect(() => {
17+
if (isOpenRouterTokenSet.current.data) {
18+
if (openRouterToken === '') {
19+
openRouterToken = 'xxxx';
20+
}
21+
}
22+
});
23+
24+
$effect(() => {
25+
if (lastValue !== openRouterToken && openRouterToken !== 'xxxx') {
26+
setOpenRouterToken({ token: openRouterToken });
27+
lastValue = openRouterToken;
28+
}
29+
});
30+
</script>
31+
32+
<SectionCard
33+
labelFor="open-router-token"
34+
orientation="column"
35+
roundedBottom={false}
36+
roundedTop={false}
37+
>
38+
{#snippet title()}
39+
OpenRouter Token
40+
{/snippet}
41+
{#snippet actions()}
42+
<Textbox
43+
id="open-router-token"
44+
value={openRouterToken}
45+
onchange={(value) => {
46+
openRouterToken = value;
47+
}}
48+
wide
49+
/>
50+
{/snippet}
51+
</SectionCard>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<script lang="ts">
2+
import ReduxResult from '$components/ReduxResult.svelte';
3+
import { Ai2Service } from '$lib/ai2/service';
4+
import { getContext } from '@gitbutler/shared/context';
5+
import AsyncButton from '@gitbutler/ui/AsyncButton.svelte';
6+
import Textarea from '@gitbutler/ui/Textarea.svelte';
7+
8+
type Props = {
9+
projectId: string;
10+
};
11+
12+
const { projectId }: Props = $props();
13+
14+
const ai2Service = getContext(Ai2Service);
15+
16+
const isOpenRouterTokenSet = ai2Service.isOpenRouterTokenSet;
17+
const conversations = $derived(ai2Service.conversations({ projectId }));
18+
const [agentCreateConversation] = ai2Service.createConversation;
19+
const [agentSendMessage] = ai2Service.sendMessage;
20+
21+
let currentConversation = $state<string>();
22+
let message = $state('');
23+
24+
async function createConversation({ projectId }: { projectId: string }) {
25+
const id = await agentCreateConversation({ projectId });
26+
currentConversation = id;
27+
}
28+
29+
async function sendMessage({
30+
projectId,
31+
conversationId
32+
}: {
33+
projectId: string;
34+
conversationId: string;
35+
}) {
36+
await agentSendMessage({ projectId, conversationId, message });
37+
message = '';
38+
}
39+
</script>
40+
41+
<div class="page">
42+
<div class="sidebar">
43+
{#if !isOpenRouterTokenSet.current.data}
44+
<p>
45+
The agent token is currently not set. Please configure it in the global experimental
46+
settings.
47+
</p>
48+
{/if}
49+
<div class="conversations">
50+
<p class="text-13">Conversations</p>
51+
<ReduxResult {projectId} result={conversations.current}>
52+
{#snippet children(conversations, { projectId })}
53+
<ul>
54+
{#each Object.entries(conversations) as [id, _conversation] (id)}
55+
<!-- svelte-ignore a11y_click_events_have_key_events -->
56+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
57+
<li onclick={() => (currentConversation = id)}>
58+
{#if currentConversation === id}
59+
<span>🔥</span>
60+
{/if}
61+
{id}
62+
</li>
63+
{/each}
64+
</ul>
65+
<AsyncButton
66+
action={async () => {
67+
await createConversation({ projectId });
68+
}}>Create new conversation</AsyncButton
69+
>
70+
{/snippet}
71+
</ReduxResult>
72+
</div>
73+
</div>
74+
75+
<div class="main">
76+
<div class="conversation">
77+
<ReduxResult {projectId} result={conversations.current}>
78+
{#snippet children(conversations, { projectId })}
79+
{@const conversation = currentConversation
80+
? conversations[currentConversation]
81+
: undefined}
82+
{#if conversation}
83+
<ul>
84+
{#each conversation as message}
85+
<li>
86+
<div class="message">
87+
<p>Role: {message.role}</p>
88+
<pre>{message.content}</pre>
89+
</div>
90+
</li>
91+
{/each}
92+
</ul>
93+
<Textarea bind:value={message} placeholder="Could you help me with..."></Textarea>
94+
<AsyncButton
95+
action={async () => {
96+
await sendMessage({ projectId, conversationId: currentConversation! });
97+
}}>Send</AsyncButton
98+
>
99+
{/if}
100+
{/snippet}
101+
</ReduxResult>
102+
</div>
103+
</div>
104+
</div>
105+
106+
<style lang="postcss">
107+
.page {
108+
display: flex;
109+
110+
width: 100%;
111+
height: 100%;
112+
gap: 1rem;
113+
}
114+
115+
.sidebar {
116+
width: 350px;
117+
height: 100%;
118+
padding: 1rem;
119+
120+
border: 1px solid var(--clr-border-2);
121+
border-radius: var(--radius-l);
122+
background-color: var(--clr-bg-1);
123+
}
124+
125+
.main {
126+
width: 100%;
127+
height: 100%;
128+
padding: 1rem;
129+
130+
border: 1px solid var(--clr-border-2);
131+
border-radius: var(--radius-l);
132+
background-color: var(--clr-bg-1);
133+
}
134+
135+
.conversation {
136+
height: 100%;
137+
overflow: auto;
138+
}
139+
140+
.message {
141+
padding: 0.5rem;
142+
border: 1px solid var(--clr-border-1);
143+
border-radius: 0.5rem;
144+
}
145+
</style>

0 commit comments

Comments
 (0)