code7-leia 1.0.16 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code7-leia",
3
- "version": "1.0.16",
3
+ "version": "1.0.20",
4
4
  "author": "code7-xlab",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.es.js",
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import styled, { css } from 'styled-components';
3
+ import * as S from './styles';
4
+ import {
5
+ FaCheckCircle,
6
+ FaExclamationTriangle,
7
+ FaTimesCircle,
8
+ FaInfoCircle,
9
+ } from 'react-icons/fa';
10
+
11
+ import { BsFillInfoSquareFill } from "react-icons/bs";
12
+
13
+ type AlertStatus = 'success' | 'warning' | 'error' | 'info';
14
+
15
+ interface AlertProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
16
+ status?: AlertStatus;
17
+ title?: React.ReactNode;
18
+ message?: React.ReactNode | string[];
19
+ icon?: React.ReactNode;
20
+ children?: React.ReactNode;
21
+ }
22
+
23
+ const icons = {
24
+ success: <FaCheckCircle />,
25
+ warning: <FaExclamationTriangle />,
26
+ error: <FaTimesCircle />,
27
+ info: <BsFillInfoSquareFill />,
28
+ };
29
+
30
+ export const Alert: React.FC<AlertProps> = ({
31
+ status = 'info',
32
+ title,
33
+ message,
34
+ icon,
35
+ children,
36
+ ...rest
37
+ }) => {
38
+ const renderMessage = () => {
39
+ if (Array.isArray(message)) {
40
+ return message.map((m, i) => <p key={i}>- {m}</p>);
41
+ }
42
+ return message ? <p>{message}</p> : null;
43
+ };
44
+
45
+ return (
46
+ <S.Container status={status} {...rest}>
47
+ <S.Content>
48
+ <S.Header>
49
+ <S.Icon>{icon || icons[status]}</S.Icon>
50
+ {title && <strong>{title}</strong>}
51
+ </S.Header>
52
+ <div>
53
+ {renderMessage()}
54
+ {children}
55
+ </div>
56
+ </S.Content>
57
+ </S.Container>
58
+ );
59
+ };
@@ -0,0 +1,63 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ type AlertStatus = 'success' | 'warning' | 'error' | 'info';
4
+
5
+ const statusStyles = {
6
+ success: css`
7
+ background: var(--semantic-green-100-light);
8
+ border-color: var(--semantic-green-600-light);
9
+ color: var(--semantic-green-600-light);
10
+ `,
11
+ warning: css`
12
+ background: var(--semantic-orange-100-light);
13
+ border-color: var(--semantic-orange-600-light);
14
+ color: var(--semantic-orange-600-light);
15
+ `,
16
+ error: css`
17
+ background: var(--semantic-red-100-light);
18
+ border-color: var(--semantic-red-600-light);
19
+ color: var(--semantic-red-600-light);
20
+ `,
21
+ info: css`
22
+ background: var(--semantic-cyan-100-light);
23
+ border-color: var(--semantic-cyan-600-light);
24
+ color: var(--semantic-cyan-600-light);
25
+ `,
26
+ };
27
+
28
+ export const Container = styled.div<{ status: AlertStatus }>`
29
+ padding: 16px 24px;
30
+ border: 1px solid;
31
+ border-radius: var(--radius-small);
32
+ margin-bottom: 14px;
33
+
34
+ ${({ status }) => statusStyles[status]}
35
+ p {
36
+ font-size: 14px;
37
+ margin: 4px 0;
38
+ }
39
+ `;
40
+
41
+ export const Content = styled.div`
42
+ display: flex;
43
+ gap: 12px;
44
+ flex-direction: column;
45
+ `;
46
+
47
+ export const Header = styled.div `
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 8px;
51
+ `;
52
+
53
+ export const Icon = styled.div`
54
+ display: flex;
55
+ align-items: center;
56
+ font-size: 18px;
57
+
58
+ svg {
59
+ width: 20px;
60
+ height: 20px;
61
+ }
62
+ `;
63
+
@@ -1,16 +1,16 @@
1
- import { useState } from 'react';
1
+ import { useState, useEffect } from "react";
2
2
  import { getLanguage } from '../../utils/getLanguage';
3
- import { FaPlus, FaCopy, FaEdit, FaQuestionCircle, FaTrash } from 'react-icons/fa';
3
+ import { FaPlus, FaCopy, FaEdit, FaTrash } from 'react-icons/fa';
4
4
  import Modal from '../Modal';
5
5
  import Input from '../TestArea/components/InputTest';
6
6
  import TextArea from '../TestArea/components/TextArea';
7
7
  import Search from '../FileArea/components/Search';
8
8
  import LengthCounter from '../LengthCounter';
9
9
 
10
- import { useLeia } from '../../contexts/LeiaProvider';
10
+ import { useLeia } from "../../contexts/LeiaProvider";
11
11
 
12
- import * as S from './styles';
13
- import Tooltip from '../Tootip';
12
+ import * as S from "./styles";
13
+ import { Alert } from "../Alert";
14
14
 
15
15
  interface Persona {
16
16
  id: number;
@@ -18,29 +18,36 @@ interface Persona {
18
18
  description: string;
19
19
  prompt: string;
20
20
  type?: string;
21
+ created_at?: string;
21
22
  }
22
23
 
23
24
  export const PersonasArea = () => {
24
25
  const { language } = useLeia();
25
26
  const t = getLanguage(language);
26
- const [_,setPersonas] = useState<Persona[]>([]);
27
27
  const { createPersona, personas, updatePersona, deletePersona } = useLeia();
28
28
 
29
- const [search, setSearch] = useState('');
29
+ const [search, setSearch] = useState("");
30
30
  const [isOpen, setIsOpen] = useState(false);
31
31
  const [editingPersona, setEditingPersona] = useState<any>(null);
32
32
  const [isDeleteOpen, setIsDeleteOpen] = useState(false);
33
33
  const [personaToDelete, setPersonaToDelete] = useState<any>(null);
34
+ const [_, setPersonas] = useState<Persona[]>([]);
35
+ const [filteredPersonas, setFilteredPersonas] = useState<any>([]);
34
36
 
35
37
  const [form, setForm] = useState({
36
- name: '',
37
- description: '',
38
- prompt: '',
38
+ name: "",
39
+ description: "",
40
+ prompt: "",
39
41
  });
40
42
 
43
+ const isFormValid =
44
+ form.name.trim() !== "" &&
45
+ form.description.trim() !== "" &&
46
+ form.prompt.trim() !== "";
47
+
41
48
  const openCreate = () => {
42
49
  setEditingPersona(null);
43
- setForm({ name: '', description: '', prompt: '' });
50
+ setForm({ name: "", description: "", prompt: "" });
44
51
  setIsOpen(true);
45
52
  };
46
53
 
@@ -65,13 +72,14 @@ export const PersonasArea = () => {
65
72
  };
66
73
 
67
74
  const handleSave = async () => {
75
+ if (!isFormValid) return;
68
76
  try {
69
77
  if (!editingPersona) {
70
78
  const payload = {
71
79
  name: form.name,
72
80
  description: form.description,
73
81
  prompt: form.prompt,
74
- type: 'persona',
82
+ type: "persona",
75
83
  };
76
84
  await createPersona(payload);
77
85
  } else {
@@ -80,7 +88,7 @@ export const PersonasArea = () => {
80
88
 
81
89
  setIsOpen(false);
82
90
  } catch (err) {
83
- console.error('Erro ao salvar persona', err);
91
+ console.error("Erro ao salvar persona", err);
84
92
  }
85
93
  };
86
94
 
@@ -95,37 +103,22 @@ export const PersonasArea = () => {
95
103
  setIsDeleteOpen(false);
96
104
  setPersonaToDelete(null);
97
105
  } catch (err) {
98
- console.error('Erro ao deletar persona', err);
106
+ console.error("Erro ao deletar persona", err);
99
107
  }
100
108
  };
101
109
 
102
- const filteredPersonas = personas?.filter((p) =>
103
- p.name.toLowerCase().includes(search.toLowerCase())
104
- );
105
-
106
- const ModalTitle = () => {
107
- return (
108
- <S.ModalTitleContainer className='modal-title'>
109
- <h3>{t.personas?.modalTitle}</h3>
110
- <Tooltip
111
- position="right"
112
- width={'200px'}
113
- render={() =>
114
- t.personas?.modalTooltip
115
- }
116
- >
117
- <>
118
- <FaQuestionCircle color = "#8D9CA1" size={16}/>
119
- </>
120
- </Tooltip>
121
- </S.ModalTitleContainer>
122
- )
123
- }
110
+ useEffect(() => {
111
+ setFilteredPersonas(
112
+ personas?.filter((p) =>
113
+ p.name.toLowerCase().includes(search.toLowerCase()),
114
+ ),
115
+ );
116
+ }, [personas]);
124
117
 
125
118
  return (
126
119
  <S.Container>
127
120
  <S.Header>
128
- <div className='infos'>
121
+ <div className="infos">
129
122
  <h2>{t.personas?.title}</h2>
130
123
  <p>{t.personas?.description}</p>
131
124
  </div>
@@ -134,44 +127,49 @@ export const PersonasArea = () => {
134
127
  </button>
135
128
  </S.Header>
136
129
  <S.Search>
137
- <Search placeholder={t.search} setFiles={setPersonas} initialFiles={personas} />
130
+ <Search
131
+ placeholder={t.search}
132
+ setFiles={setFilteredPersonas}
133
+ initialFiles={personas}
134
+ />
138
135
  </S.Search>
139
136
  <S.TableContainer>
140
137
  <tbody>
141
138
  {filteredPersonas?.map((persona: any) => (
142
-
143
- <tr className = "tr-Row" key = {persona.id}>
139
+ <tr className="tr-Row" key={persona.id}>
144
140
  <td>
145
141
  <div className="info">
146
- <p><strong>{persona.name}</strong></p>
142
+ <p>
143
+ <strong>{persona.name}</strong>
144
+ </p>
147
145
  <span>{persona.description}</span>
148
146
  </div>
149
147
  </td>
150
- <td className='td-actions'>
148
+ <td className="td-actions">
151
149
  {persona.created_at && (
152
- <div className="actions">
153
- <button onClick={() => openEdit(persona)}>
154
- <FaEdit /> {t.edit}
155
- </button>
156
- <button onClick={() => openClone(persona)}>
157
- <FaCopy /> {t.clone}
158
- </button>
159
- <button className="button-delete" onClick={() => openDelete(persona)}>
160
- <FaTrash /> {t.delete}
161
- </button>
162
- </div>
163
- )}
150
+ <div className="actions">
151
+ <button onClick={() => openEdit(persona)}>
152
+ <FaEdit /> {t.edit}
153
+ </button>
154
+ <button onClick={() => openClone(persona)}>
155
+ <FaCopy /> {t.clone}
156
+ </button>
157
+ <button
158
+ className="button-delete"
159
+ onClick={() => openDelete(persona)}
160
+ >
161
+ <FaTrash /> {t.delete}
162
+ </button>
163
+ </div>
164
+ )}
164
165
  </td>
165
166
  </tr>
166
-
167
-
168
167
  ))}
169
168
  </tbody>
170
169
  </S.TableContainer>
171
170
 
172
-
173
171
  {isOpen && (
174
- <Modal onClose={() => setIsOpen(false)} title={<ModalTitle/>}>
172
+ <Modal onClose={() => setIsOpen(false)} title={t.personas?.modalTitle}>
175
173
  <S.InputWrapper>
176
174
  <Input
177
175
  value={form.name}
@@ -182,30 +180,44 @@ export const PersonasArea = () => {
182
180
  <Input
183
181
  value={form.description}
184
182
  placeholder={t.personas?.fields.description}
185
- onChange={(value: any) => setForm({ ...form, description: value })}
183
+ onChange={(value: any) =>
184
+ setForm({ ...form, description: value })
185
+ }
186
186
  maxLength={200}
187
187
  />
188
188
  </S.InputWrapper>
189
- <TextArea
190
- label={t.personas?.fields.prompt}
191
- maxLength={10000}
192
- value={form.prompt}
193
- onChange={(e: any) => setForm({ ...form, prompt: e.target.value })}
189
+ <div className="textarea-wrapper">
190
+ <TextArea
191
+ label={t.personas?.fields.prompt}
192
+ maxLength={10000}
193
+ value={form.prompt}
194
+ onChange={(e: any) => setForm({ ...form, prompt: e.target.value })}
195
+ />
196
+ <LengthCounter value={form.prompt} maxLength={10000} />
197
+ </div>
198
+ <Alert
199
+ title={t.personas?.alertTitle}
200
+ message={t.personas?.alertMessage}
194
201
  />
195
- <LengthCounter value={form.prompt} maxLength={10000} />
196
202
 
197
203
  <S.ModalActions>
198
204
  <button onClick={() => setIsOpen(false)}>{t.buttons.cancel}</button>
199
- <button onClick={handleSave}>{t.save}</button>
205
+ <button
206
+ onClick={handleSave}
207
+ disabled={!isFormValid}
208
+ style={{
209
+ opacity: isFormValid ? 1 : 0.5,
210
+ cursor: isFormValid ? "pointer" : "not-allowed",
211
+ }}
212
+ >
213
+ {t.save}
214
+ </button>
200
215
  </S.ModalActions>
201
216
  </Modal>
202
217
  )}
203
218
 
204
219
  {isDeleteOpen && personaToDelete && (
205
- <Modal
206
- onClose={() => setIsDeleteOpen(false)}
207
- title={t.delete}
208
- >
220
+ <Modal onClose={() => setIsDeleteOpen(false)} title={t.delete}>
209
221
  <p>
210
222
  {t.personas.modalAlert} <strong>{personaToDelete.name}</strong>?
211
223
  </p>
@@ -214,9 +226,7 @@ export const PersonasArea = () => {
214
226
  <button onClick={() => setIsDeleteOpen(false)}>
215
227
  {t.buttons.cancel}
216
228
  </button>
217
- <button onClick={confirmDelete}>
218
- {t.buttons.delete}
219
- </button>
229
+ <button onClick={confirmDelete}>{t.buttons.delete}</button>
220
230
  </S.ModalActions>
221
231
  </Modal>
222
232
  )}
@@ -3,6 +3,10 @@ import styled from 'styled-components';
3
3
  export const Container = styled.div`
4
4
  display: flex;
5
5
  flex-direction: column;
6
+
7
+ .textarea-wrapper{
8
+ margin-bottom: 12px;
9
+ }
6
10
  `;
7
11
 
8
12
  export const Header = styled.div`
@@ -204,8 +208,5 @@ export const TableContainer = styled.table`
204
208
  }
205
209
  `;
206
210
 
207
- export const ModalTitleContainer = styled.div`
208
- display: flex;
209
- gap: 8px;
210
- align-items: center;
211
- `;
211
+
212
+
@@ -17,7 +17,8 @@ export const enTranslation: Language = {
17
17
  add: 'Add persona',
18
18
  modalTitle: 'Persona',
19
19
  modalAlert: 'Are you sure you want to delete',
20
- modalTooltip: "The AI will behave exactly as described in this prompt. Anything it says from here on is the responsibility of the user.",
20
+ alertTitle: 'Important',
21
+ alertMessage: "The AI-generated responses are based on the guidelines in this prompt; defining their content is the user's responsibility.",
21
22
 
22
23
  fields: {
23
24
  name: 'Name',
@@ -17,7 +17,8 @@ export const esTranslation: Language = {
17
17
  add: 'Agregar persona',
18
18
  modalTitle: 'Persona',
19
19
  modalAlert: '¿Seguro que deseas eliminar',
20
- modalTooltip: "La IA se comportará exactamente según este prompt. Todo lo que diga a partir de aquí es responsabilidad del usuario.",
20
+ alertTitle: 'Importante',
21
+ alertMessage: "Las respuestas generadas por IA se basan en las pautas de este mensaje; definir su contenido es responsabilidad del usuario.",
21
22
 
22
23
  fields: {
23
24
  name: 'Nombre',
@@ -17,7 +17,8 @@ export const ptTranslation: Language = {
17
17
  add: 'Adicionar persona',
18
18
  modalTitle: 'Persona',
19
19
  modalAlert: 'Quer mesmo deletar',
20
- modalTooltip: "A IA vai se comportar exatamente como este prompt descreve. Tudo o que ela disser a partir disso é responsabilidade do usuário.",
20
+ alertTitle: 'Importante',
21
+ alertMessage: "As respostas geradas pela IA são baseadas nas orientações deste prompt, sendo responsabilidade do usuário a definição de seu conteúdo.",
21
22
 
22
23
  fields: {
23
24
  name: 'Nome',