Skip to content

Commit 6ec238d

Browse files
committed
more improvements
1 parent 05cdb15 commit 6ec238d

File tree

7 files changed

+237
-52
lines changed

7 files changed

+237
-52
lines changed

.eslintrc.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ module.exports = {
3131
}
3232
}],
3333
'rules': {
34+
'arrow-body-style': 'off',
3435
'react/react-in-jsx-scope': 'off',
3536
'react/display-name': 'off',
3637
'no-unused-vars': [0],
@@ -42,7 +43,6 @@ module.exports = {
4243
'no-relative-import-paths/no-relative-import-paths': [
4344
'warn',
4445
{ 'allowSameFolder': true, 'prefix': '@', 'rootDir': 'src' }
45-
],
46-
'arrow-body-style': ['warn', 'as-needed'],
46+
]
4747
}
4848
};

src/SessionListItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const SessionListItem: FC<SessionListItemProps> = ({
1616
onDeleteSession
1717
}) => (
1818
<ListItem
19+
disableGutters
1920
active={isActive}
2021
className="mb-4"
2122
onClick={() => onSelectSession && onSelectSession(session.id)}

src/Sessions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export const Sessions: FC<SessionsProps> = ({
116116
<>
117117
<SessionsList
118118
sessions={sessions}
119+
theme={theme}
119120
activeSessionId={activeSessionId}
120121
onSelectSession={onSelectSession}
121122
onDeleteSession={onDeleteSession}

src/SessionsList.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
import { FC } from 'react';
22
import { SessionListItem } from './SessionListItem';
33
import { Session } from './types';
4-
import { List, ListItem, Button } from 'reablocks';
4+
import { List, ListItem, Button, cn } from 'reablocks';
5+
import { ChatTheme } from './theme';
56

67
interface SessionsListProps {
78
sessions: Session[];
89
activeSessionId?: string;
910
className?: string;
11+
theme?: ChatTheme;
1012
onSelectSession?: (sessionId: string) => void;
1113
onDeleteSession?: (sessionId: string) => void;
1214
onCreateNewSession?: () => void;
1315
}
1416

1517
export const SessionsList: FC<SessionsListProps> = ({
1618
sessions,
19+
theme,
1720
className,
1821
activeSessionId,
1922
onSelectSession,
2023
onDeleteSession,
2124
onCreateNewSession
2225
}) => {
2326
return (
24-
<List className={className}>
25-
<ListItem>
27+
<List className={cn(theme.list.base, className)}>
28+
<ListItem disableGutters>
2629
<Button
2730
fullWidth
28-
className="mb-4 px-4 py-2"
31+
className={cn('mb-4', theme.list.create)}
2932
onClick={onCreateNewSession}
3033
>
3134
Create New Session

src/theme.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
export interface ChatTheme {
22
base: string;
3+
list: {
4+
base: string;
5+
create: string;
6+
}
37
}
48

59
export const chatTheme: ChatTheme = {
6-
base: ''
10+
base: '',
11+
list: {
12+
base: '',
13+
create: ''
14+
}
715
};

src/useLlm.ts

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,174 @@
1+
import { useState, useCallback } from 'react';
2+
import { Session, Conversation } from './types';
3+
4+
export type LlmProvider = 'openai' | 'anthropic' | 'google';
5+
16
export interface LlmOptions {
2-
// provider: 'openai' | 'claude' | 'gemini';
7+
provider: LlmProvider;
8+
apiKey: string;
9+
}
10+
11+
export interface LlmResponse {
12+
text: string;
13+
isComplete: boolean;
314
}
415

516
export const useLlm = (options: LlmOptions) => {
6-
// Goal of the hook is to provider a data provider wrapper
7-
// for the streaming data.
17+
/*
18+
const [isLoading, setIsLoading] = useState(false);
19+
const [error, setError] = useState<Error | null>(null);
20+
21+
const streamResponse = useCallback(async (
22+
prompt: string,
23+
onUpdate: (response: LlmResponse) => void
24+
): Promise<void> => {
25+
if (!prompt.trim()) {
26+
setError(new Error('Prompt cannot be empty'));
27+
return;
28+
}
29+
30+
setIsLoading(true);
31+
setError(null);
32+
33+
try {
34+
const apiEndpoint = API_ENDPOINTS[options.provider];
35+
36+
let headers = {
37+
'Content-Type': 'application/json',
38+
'Authorization': `Bearer ${options.apiKey}`
39+
};
40+
let body: any = { prompt, stream: true };
41+
42+
switch (options.provider) {
43+
case 'openai':
44+
body = {
45+
model: 'gpt-3.5-turbo',
46+
messages: [{ role: 'user', content: prompt }],
47+
stream: true
48+
};
49+
break;
50+
case 'anthropic':
51+
body = {
52+
prompt: `Human: ${prompt}\n\nAssistant:`,
53+
model: 'claude-2',
54+
stream: true
55+
};
56+
break;
57+
case 'google':
58+
headers['x-goog-api-key'] = options.apiKey;
59+
delete headers['Authorization'];
60+
body = {
61+
contents: [{ parts: [{ text: prompt }] }],
62+
generationConfig: { temperature: 0.9 }
63+
};
64+
break;
65+
}
66+
67+
const response = await fetch(apiEndpoint, {
68+
method: 'POST',
69+
headers,
70+
body: JSON.stringify(body)
71+
});
72+
73+
if (!response.ok) {
74+
throw new Error(`HTTP error! status: ${response.status}`);
75+
}
76+
77+
const reader = response.body!.getReader();
78+
let accumulatedResponse = '';
79+
80+
while (true) {
81+
const { done, value } = await reader.read();
82+
if (done) break;
83+
84+
const chunk = new TextDecoder().decode(value);
85+
accumulatedResponse += chunk;
86+
87+
// Parse the chunk based on the provider
88+
let parsedChunk = '';
89+
switch (options.provider) {
90+
case 'openai':
91+
parsedChunk = parseOpenAIResponse(chunk);
92+
break;
93+
case 'anthropic':
94+
parsedChunk = parseAnthropicResponse(chunk);
95+
break;
96+
case 'google':
97+
parsedChunk = parseGoogleResponse(chunk);
98+
break;
99+
}
100+
101+
onUpdate({
102+
text: accumulatedResponse,
103+
isComplete: false
104+
});
105+
}
106+
107+
onUpdate({
108+
text: accumulatedResponse,
109+
isComplete: true
110+
});
111+
} catch (err) {
112+
setError(err instanceof Error ? err : new Error('An error occurred'));
113+
} finally {
114+
setIsLoading(false);
115+
}
116+
}, [options]);
117+
118+
const sendMessage = useCallback(async (
119+
session: Session,
120+
message: string
121+
): Promise<Session> => {
122+
const newConversation: Conversation = {
123+
id: Date.now().toString(),
124+
question: message,
125+
response: '',
126+
createdAt: new Date(),
127+
updatedAt: new Date()
128+
};
129+
130+
let updatedSession = {
131+
...session,
132+
conversations: [...session.conversations, newConversation]
133+
};
134+
135+
await streamResponse(message, (response) => {
136+
newConversation.response = response.text;
137+
newConversation.updatedAt = new Date();
138+
updatedSession = { ...updatedSession };
139+
});
140+
141+
return updatedSession;
142+
}, [streamResponse]);
143+
144+
return {
145+
isLoading,
146+
error,
147+
sendMessage
148+
};
149+
*/
8150
};
151+
152+
/*
153+
const API_ENDPOINTS: Record<LlmProvider, string> = {
154+
openai: 'https://api.openai.com/v1/chat/completions',
155+
anthropic: 'https://api.anthropic.com/v1/complete',
156+
google: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:streamGenerateContent',
157+
};
158+
159+
// Helper functions to parse responses (these need to be implemented)
160+
function parseOpenAIResponse(chunk: string): string {
161+
// Implementation needed
162+
return chunk;
163+
}
164+
165+
function parseAnthropicResponse(chunk: string): string {
166+
// Implementation needed
167+
return chunk;
168+
}
169+
170+
function parseGoogleResponse(chunk: string): string {
171+
// Implementation needed
172+
return chunk;
173+
}
174+
*/

stories/Basic.stories.tsx

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,9 @@ import { Sessions, SessionsProps, Session } from '../src';
44

55
export default {
66
title: 'Examples',
7-
component: Sessions,
8-
decorators: [
9-
(Story) => (
10-
<div style={{ width: '750px' }}>
11-
<Story />
12-
</div>
13-
),
14-
],
7+
component: Sessions
158
} as Meta;
169

17-
const Template: StoryFn<SessionsProps> = (args) => <Sessions {...args} />;
18-
1910
const fakeSessions: Session[] = [
2011
{
2112
id: '1',
@@ -39,40 +30,55 @@ const fakeSessions: Session[] = [
3930
},
4031
];
4132

42-
export const Console = Template.bind({});
43-
Console.args = {
44-
viewType: 'console',
45-
sessions: fakeSessions,
46-
activeSessionId: '1',
47-
isLoading: false,
48-
onSelectSession: (sessionId: string) => console.log(`Selected session: ${sessionId}`),
49-
onDeleteSession: (sessionId: string) => console.log(`Deleted session: ${sessionId}`),
50-
onSendMessage: (message: string) => console.log(`Sent message: ${message}`),
51-
responseTransformers: []
33+
export const Console = () => {
34+
return (
35+
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, padding: 20 }}>
36+
<Sessions
37+
viewType="console"
38+
sessions={fakeSessions}
39+
activeSessionId="1"
40+
isLoading={false}
41+
onSelectSession={() => {}}
42+
onDeleteSession={() => {}}
43+
onSendMessage={() => {}}
44+
responseTransformers={[]}
45+
/>
46+
</div>
47+
);
5248
};
5349

54-
export const Companion = Template.bind({});
55-
Companion.args = {
56-
viewType: 'companion',
57-
sessions: fakeSessions,
58-
activeSessionId: '1',
59-
isLoading: false,
60-
onSelectSession: (sessionId: string) => console.log(`Selected session: ${sessionId}`),
61-
onDeleteSession: (sessionId: string) => console.log(`Deleted session: ${sessionId}`),
62-
onSendMessage: (message: string) => console.log(`Sent message: ${message}`),
63-
responseTransformers: []
50+
export const Companion = () => {
51+
return (
52+
<div style={{ width: 350 }}>
53+
<Sessions
54+
viewType="companion"
55+
sessions={fakeSessions}
56+
activeSessionId="1"
57+
isLoading={false}
58+
onSelectSession={() => {}}
59+
onDeleteSession={() => {}}
60+
onSendMessage={() => {}}
61+
responseTransformers={[]}
62+
/>
63+
</div>
64+
);
6465
};
6566

66-
export const ResponseTransformer = Template.bind({});
67-
ResponseTransformer.args = {
68-
viewType: 'console',
69-
sessions: fakeSessions,
70-
activeSessionId: '1',
71-
isLoading: false,
72-
onSelectSession: (sessionId: string) => console.log(`Selected session: ${sessionId}`),
73-
onDeleteSession: (sessionId: string) => console.log(`Deleted session: ${sessionId}`),
74-
onSendMessage: (message: string) => console.log(`Sent message: ${message}`),
75-
responseTransformers: [
76-
(response, next) => next(response.toUpperCase()), // Example transformer
77-
],
67+
export const ResponseTransformer = () => {
68+
return (
69+
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
70+
<Sessions
71+
viewType="console"
72+
sessions={fakeSessions}
73+
activeSessionId="1"
74+
isLoading={false}
75+
onSelectSession={() => {}}
76+
onDeleteSession={() => {}}
77+
onSendMessage={() => {}}
78+
responseTransformers={[
79+
(response, next) => next(response.toUpperCase())
80+
]}
81+
/>
82+
</div>
83+
);
7884
};

0 commit comments

Comments
 (0)