wip
This commit is contained in:
+12
-11
@@ -21,8 +21,8 @@ services:
|
||||
- MINIO_ROOT_USER=impress
|
||||
- MINIO_ROOT_PASSWORD=password
|
||||
ports:
|
||||
- '9000:9000'
|
||||
- '9001:9001'
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
entrypoint: ""
|
||||
command: minio server --console-address :9001 /data
|
||||
volumes:
|
||||
@@ -59,11 +59,11 @@ services:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
depends_on:
|
||||
- postgresql
|
||||
- mailcatcher
|
||||
- redis
|
||||
- createbuckets
|
||||
|
||||
- postgresql
|
||||
- mailcatcher
|
||||
- redis
|
||||
- createbuckets
|
||||
|
||||
celery-dev:
|
||||
user: ${DOCKER_USER:-1000}
|
||||
image: impress:backend-development
|
||||
@@ -122,7 +122,7 @@ services:
|
||||
|
||||
frontend-dev:
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
build:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./src/frontend/Dockerfile
|
||||
target: frontend-production
|
||||
@@ -151,13 +151,13 @@ services:
|
||||
image: node:18
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
environment:
|
||||
HOME: /tmp
|
||||
HOME: /tmp
|
||||
volumes:
|
||||
- ".:/app"
|
||||
|
||||
y-provider:
|
||||
user: ${DOCKER_USER:-1000}
|
||||
build:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./src/frontend/servers/y-provider/Dockerfile
|
||||
target: y-provider
|
||||
@@ -169,6 +169,7 @@ services:
|
||||
|
||||
kc_postgresql:
|
||||
image: postgres:14.3
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "5433:5432"
|
||||
env_file:
|
||||
@@ -196,7 +197,7 @@ services:
|
||||
KC_DB_PASSWORD: pass
|
||||
KC_DB_USERNAME: impress
|
||||
KC_DB_SCHEMA: public
|
||||
PROXY_ADDRESS_FORWARDING: 'true'
|
||||
PROXY_ADDRESS_FORWARDING: "true"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
|
||||
@@ -1079,6 +1079,41 @@ class DocumentViewSet(
|
||||
|
||||
return drf.response.Response(response, status=drf.status.HTTP_200_OK)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
name="Just proxy ai call",
|
||||
url_path="ai-proxy"
|
||||
)
|
||||
def ai_proxy(self, request, *args, **kwargs):
|
||||
"""
|
||||
POST /api/v1.0/documents/<resource_id>/ai-transform
|
||||
with expected data:
|
||||
- text: str
|
||||
- action: str [prompt, correct, rephrase, summarize]
|
||||
Return JSON response with the processed text.
|
||||
"""
|
||||
print('PROXY 1')
|
||||
# Check permissions first
|
||||
# self.get_object()
|
||||
|
||||
print('PROXY 2')
|
||||
print(request.data)
|
||||
# serializer = serializers.AITransformSerializer(data=request.data)
|
||||
# serializer.is_valid(raise_exception=True)
|
||||
|
||||
print('PROXY 3')
|
||||
system_content = request.data["system"]
|
||||
text = request.data["text"]
|
||||
|
||||
print('PROXY 4')
|
||||
response = AIService().call_proxy(system_content, text)
|
||||
|
||||
print('PROXY 5')
|
||||
print(response)
|
||||
|
||||
return drf.response.Response(response, status=drf.status.HTTP_200_OK)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
|
||||
@@ -55,6 +55,22 @@ class AIService:
|
||||
raise ImproperlyConfigured("AI configuration not set")
|
||||
self.client = OpenAI(base_url=settings.AI_BASE_URL, api_key=settings.AI_API_KEY)
|
||||
|
||||
def call_proxy(self, system_content, text):
|
||||
messages = [
|
||||
{"role": "system", "content": system_content},
|
||||
{"role": "user", "content": text},
|
||||
]
|
||||
print('REQUEST', messages)
|
||||
response = self.client.chat.completions.create(
|
||||
model='meta-llama/Llama-3.1-8B-Instruct',
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
print('RESPONSE', response)
|
||||
content = response.choices[0].message.content
|
||||
print('CONTENT', content)
|
||||
return content
|
||||
|
||||
def call_ai_api(self, system_content, text):
|
||||
"""Helper method to call the OpenAI API and process the response."""
|
||||
response = self.client.chat.completions.create(
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"idb": "8.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "^15.0.7",
|
||||
"next": "15.1.6",
|
||||
"posthog-js": "1.215.6",
|
||||
"react": "*",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,253 @@
|
||||
import { Button, Input, Loader, TextArea } from '@openfun/cunningham-react';
|
||||
import { marked } from 'marked';
|
||||
import { useState } from 'react';
|
||||
import { ButtonProps } from 'react-aria-components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { fetchAPI } from '@/api';
|
||||
import { Box, Text } from '@/components';
|
||||
import { Doc } from '@/features/docs';
|
||||
|
||||
import { useEditorStore } from '../../stores/useEditorStore';
|
||||
|
||||
export const AIButtonEl = styled.button`
|
||||
background-image: url('/assets/ia_baguette.png');
|
||||
background-size: cover;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
`;
|
||||
|
||||
export const SuggestionButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
color: var(--c--theme--colors--greyscale-900);
|
||||
height: 32px;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
|
||||
span.material-icons {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
span.sub {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
margin-left: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AiButton = ({ doc }: { doc: Doc }) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
$position="absolute"
|
||||
$css={`
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
z-index: 1;
|
||||
|
||||
`}
|
||||
>
|
||||
<AIButtonEl
|
||||
aria-label="Ask anything to our AI"
|
||||
onClick={() => setIsOpen(true)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<AiChat doc={doc} isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
type Message = {
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
};
|
||||
|
||||
const AiChat = (props: { isOpen: boolean; onClose: () => void; doc: Doc }) => {
|
||||
const [prompt, setPrompt] = useState('');
|
||||
const { editor } = useEditorStore();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
|
||||
if (!props.isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const submit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setMessages([...messages, { role: 'user', content: prompt }]);
|
||||
setPrompt(''); // Clear the prompt after submitting the form
|
||||
|
||||
const editorContentFormatted = await editor.blocksToMarkdownLossy();
|
||||
|
||||
console.log('submit', e, prompt);
|
||||
console.log('editorContentFormatted', editorContentFormatted);
|
||||
|
||||
const response = await fetchAPI(`documents/${props.doc.id}/ai-proxy/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
system:
|
||||
'You are a helpful assistant. You are given a text in markdown format and you need to answer the question. Here is the text: ' +
|
||||
editorContentFormatted,
|
||||
text: prompt,
|
||||
}),
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const data = await response.json();
|
||||
|
||||
console.log('response', data);
|
||||
setMessages((messages) => [
|
||||
...messages,
|
||||
{ role: 'assistant', content: data },
|
||||
]);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
$position="absolute"
|
||||
$css={`
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 1rem;
|
||||
width: 450px;
|
||||
min-height: min(61vh, 365px);
|
||||
box-shadow: rgba(15, 15, 15, 0.04) 0px 0px 0px 1px, rgba(15, 15, 15, 0.03) 0px 3px 6px, rgba(15, 15, 15, 0.06) 0px 9px 24px;
|
||||
max-height: max(-180px + 100vh, 365px);
|
||||
overflow-y: auto;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
margin: 1rem;
|
||||
z-index: 2;
|
||||
`}
|
||||
$direction="column"
|
||||
>
|
||||
<Box $direction="row" $align="center" $justify="space-between">
|
||||
<Text $theme="greyscale" $variation="1000" $weight="bold" $size="s">
|
||||
{messages.length == 0 ? '' : 'Demander à Albert'}
|
||||
</Text>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={props.onClose}
|
||||
color="tertiary-text"
|
||||
icon={<span className="material-icons">close</span>}
|
||||
/>
|
||||
</Box>
|
||||
{messages.length == 0 && (
|
||||
<Box $gap="1rem" $position="relative" $css="top: -24px;">
|
||||
<Box $gap="0.5rem">
|
||||
<Box
|
||||
$css={`
|
||||
background-image: url('/assets/ia_baguette_question_mark.png');
|
||||
background-size: cover;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
`}
|
||||
></Box>
|
||||
<Text $theme="primary" $variation="800">
|
||||
Bonjour, comment puis-je vous aider ?
|
||||
</Text>
|
||||
</Box>
|
||||
<Box $gap="0.5rem">
|
||||
<Text $theme="greyscale" $variation="1000" $weight="bold" $size="s">
|
||||
Suggestions
|
||||
</Text>
|
||||
<Box>
|
||||
<SuggestionButton>
|
||||
<span className="material-icons">description</span>
|
||||
Résumer <span className="sub">cette page</span>
|
||||
</SuggestionButton>
|
||||
<SuggestionButton>
|
||||
<span className="material-icons">help_center</span>
|
||||
Poser des questions <span className="sub">surcette page</span>
|
||||
</SuggestionButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
$flex={1}
|
||||
$direction="column"
|
||||
$gap="1rem"
|
||||
$css={`
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
mask-image: linear-gradient(black calc(100% - 32px), transparent calc(100% - 4px));
|
||||
padding-bottom: 32px;
|
||||
`}
|
||||
>
|
||||
{messages.map((message, index) => {
|
||||
if (message.role === 'user') {
|
||||
return (
|
||||
<Box
|
||||
key={index}
|
||||
$css={`
|
||||
border-radius: 16px;
|
||||
padding: 6px 14px;
|
||||
background-color: rgba(55, 53, 47, 0.04);
|
||||
${message.role === 'user' ? 'margin-left: auto;' : ''}
|
||||
`}
|
||||
>
|
||||
{message.content}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
key={index}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parse(message.content),
|
||||
}}
|
||||
></Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{(isLoading || false) && (
|
||||
<Box $display="flex" $direction="row" $align="center" $gap="0.5rem">
|
||||
<Loader size="small" />
|
||||
Thinking ...
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<form onSubmit={(e) => void submit(e)} style={{ width: '100%' }}>
|
||||
<Input
|
||||
type="text"
|
||||
label="Posez votre question"
|
||||
name="prompt"
|
||||
fullWidth={true}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
value={prompt} // Ensure the input value is updated with the state
|
||||
rightIcon={<span className="material-icons">send</span>}
|
||||
/>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { TableContent } from '@/features/docs/doc-table-content/';
|
||||
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { AiButton } from './Ai/AiButton';
|
||||
import { BlockNoteEditor, BlockNoteEditorVersion } from './BlockNoteEditor';
|
||||
|
||||
interface DocEditorProps {
|
||||
@@ -38,6 +39,7 @@ export const DocEditor = ({ doc, versionId }: DocEditorProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<AiButton doc={doc} />
|
||||
{isDesktop && !isVersion && (
|
||||
<Box
|
||||
$position="absolute"
|
||||
|
||||
Generated
+25849
File diff suppressed because it is too large
Load Diff
+14650
-14666
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user