leandroaraujodev commited on
Commit
e92e757
·
1 Parent(s): a95e592

feat: bump version 0.4.0

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .streamlit/config.toml +4 -1
  2. README.md +4 -4
  3. app.py +143 -64
  4. chatbot_server.py +140 -144
  5. config.yaml +38 -0
  6. drive_search.py +77 -0
  7. feedback_manager.py +67 -0
  8. files/config.yaml +38 -0
  9. files/credenciais.json +13 -0
  10. logos/ChangelogUser.md +31 -0
  11. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Black.eot +0 -0
  12. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-BlackItalic.eot +0 -0
  13. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Bold.eot +0 -0
  14. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-BoldItalic.eot +0 -0
  15. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Extrabold.eot +0 -0
  16. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ExtraboldItalic.eot +0 -0
  17. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Extralight.eot +0 -0
  18. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ExtralightItalic.eot +0 -0
  19. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Italic.eot +0 -0
  20. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Light.eot +0 -0
  21. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-LightItalic.eot +0 -0
  22. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Medium.eot +0 -0
  23. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-MediumItalic.eot +0 -0
  24. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Regular.eot +0 -0
  25. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Semibold.eot +0 -0
  26. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-SemiboldItalic.eot +0 -0
  27. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Thin.eot +0 -0
  28. logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ThinItalic.eot +0 -0
  29. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Black.ttf +0 -0
  30. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-BlackItalic.ttf +0 -0
  31. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Bold.ttf +0 -0
  32. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-BoldItalic.ttf +0 -0
  33. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Extrabold.ttf +0 -0
  34. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ExtraboldItalic.ttf +0 -0
  35. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Extralight.ttf +0 -0
  36. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ExtralightItalic.ttf +0 -0
  37. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Italic.ttf +0 -0
  38. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Light.ttf +0 -0
  39. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-LightItalic.ttf +0 -0
  40. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Medium.ttf +0 -0
  41. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-MediumItalic.ttf +0 -0
  42. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Regular.ttf +0 -0
  43. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Semibold.ttf +0 -0
  44. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-SemiboldItalic.ttf +0 -0
  45. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Thin.ttf +0 -0
  46. logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ThinItalic.ttf +0 -0
  47. logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-Black.woff +0 -0
  48. logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-BlackItalic.woff +0 -0
  49. logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-Bold.woff +0 -0
  50. logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-BoldItalic.woff +0 -0
.streamlit/config.toml CHANGED
@@ -1,3 +1,6 @@
1
  [theme]
 
 
 
 
2
 
3
- base = 'light'
 
1
  [theme]
2
+ font = "sans serif"
3
+
4
+ [client]
5
+ toolbarMode = "developer"
6
 
 
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: Chatbot Carometro
3
- emoji: 👁
4
- colorFrom: blue
5
  colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Chatbot Carometro Staging
3
+ emoji: 💻
4
+ colorFrom: yellow
5
  colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,15 +1,61 @@
1
  import streamlit as st
2
  import requests
3
- from PIL import Image
4
- import base64
 
 
 
 
 
 
 
5
 
6
 
7
  class ChatbotApp:
8
  def __init__(self):
9
- # URL do backend (Flask)
 
10
  self.backend_url = "http://localhost:5001/chat"
11
- self.title = "Chatbot Carômetro"
12
- self.description = "Este assistente virtual pode te ajudar com informações sobre carômetros da Sicoob."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def stream_chat(self, user_input):
15
  """
@@ -30,73 +76,106 @@ class ChatbotApp:
30
  except Exception as e:
31
  yield f"Erro ao conectar ao servidor: {e}"
32
 
 
 
 
 
33
  def render_sidebar(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  """
35
- Exibe opções na barra lateral e renderiza a logo do Sicoob.
36
  """
37
- st.sidebar.title("Configuração de LLM")
38
- sidebar_option = st.sidebar.radio("Selecione o LLM", ["gpt-3.5-turbo"])
39
- if sidebar_option != "gpt-3.5-turbo":
40
- raise Exception("Opção de LLM inválida!")
41
-
42
- # Exibe a logo do Sicoob na barra lateral
43
- with open("sicoob-logo.png", "rb") as f:
44
- data = base64.b64encode(f.read()).decode("utf-8")
45
- st.sidebar.markdown(
46
- f"""
47
- <div style="display:table;margin-top:-80%;margin-left:0%;">
48
- <img src="data:image/png;base64,{data}" width="250" height="70">
49
- </div>
50
- """,
51
- unsafe_allow_html=True,
52
- )
53
 
54
  def render(self):
55
  """
56
  Renderiza a interface do chatbot.
57
  """
58
- # Configura título, ícone e layout da página
59
- im = Image.open("pngegg.png")
60
- st.set_page_config(page_title="Chatbot Carômetro", page_icon=im, layout="wide")
61
-
62
- # Renderiza a barra lateral
63
- self.render_sidebar()
64
-
65
- # Título e descrição
66
- st.title(self.title)
67
- st.write(self.description)
68
-
69
- # Inicializa o histórico na sessão
70
- if "chat_history" not in st.session_state:
71
- st.session_state.chat_history = []
72
-
73
- # Renderiza as mensagens do histórico
74
- for message in st.session_state.chat_history:
75
- role, text = message.split(":", 1)
76
- with st.chat_message(role.strip().lower()):
77
- st.write(text.strip())
78
-
79
- # Captura o input do usuário
80
- user_input = st.chat_input("Digite sua pergunta")
81
- if user_input:
82
- # Exibe a mensagem do usuário
83
- with st.chat_message("user"):
84
- st.write(user_input)
85
- st.session_state.chat_history.append(f"user: {user_input}")
86
-
87
- # Placeholder para a resposta do assistente
88
- with st.chat_message("assistant"):
89
- message_placeholder = st.empty()
90
- assistant_message = ""
91
-
92
- # Executa o streaming de tokens enquanto o backend responde
93
- for token in self.stream_chat(user_input):
94
- assistant_message += token
95
- message_placeholder.markdown(assistant_message + "▌")
96
-
97
- # Atualiza o placeholder com a mensagem final
98
- message_placeholder.markdown(assistant_message)
99
- st.session_state.chat_history.append(f"assistant: {assistant_message}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
 
102
  if __name__ == "__main__":
 
1
  import streamlit as st
2
  import requests
3
+ import re
4
+ import streamlit_authenticator as stauth
5
+ import os
6
+ import yaml
7
+ from yaml.loader import SafeLoader
8
+ from pathlib import Path
9
+ from drive_search import search_file_in_drive
10
+ from datetime import datetime
11
+ from feedback_manager import FeedbackManager
12
 
13
 
14
  class ChatbotApp:
15
  def __init__(self):
16
+ # Configura título, ícone e layout da página
17
+ st.set_page_config(page_title="Sicoob Chatbot 🤖", page_icon="logos/sicoob-ico.ico", layout="wide")
18
  self.backend_url = "http://localhost:5001/chat"
19
+ self.title = "Sicoob Chatbot"
20
+ self.description = "Este assistente virtual pode te ajudar com informações sobre documentos da Sicoob."
21
+ self.caption = "Confira as atualizações no botão 'Atualizações'."
22
+ self.style_dir = Path("./logos/styles")
23
+ self.load_styles()
24
+ self.feedback_manager = FeedbackManager()
25
+ st.session_state.first = False
26
+ if "theme" not in st.session_state:
27
+ st.session_state.theme = "light"
28
+ self.configyml = os.path.join(os.getcwd(), "./files/config.yaml")
29
+ if "feedback_submitted" not in st.session_state:
30
+ st.session_state.feedback_submitted = set()
31
+
32
+ def load_styles(self):
33
+ try:
34
+ self.base_style = (self.style_dir / "base.css").read_text()
35
+ self.light_style = (self.style_dir / "light.css").read_text()
36
+ self.dark_style = (self.style_dir / "dark.css").read_text()
37
+ except FileNotFoundError as e:
38
+ st.error(f"Error loading styles: {e}")
39
+ self.base_style = ""
40
+ self.light_style = ""
41
+ self.dark_style = ""
42
+
43
+ def change_style(self):
44
+ with st.sidebar:
45
+ if st.session_state.theme == "light":
46
+ st.logo("./logos/sicoob-logo-horizontal-light.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
47
+ else:
48
+ st.logo("./logos/sicoob-logo-horizontal-dark.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
49
+ st.session_state.theme = "dark" if st.session_state.theme == "light" else "light"
50
+
51
+ @st.dialog("📄 Atualizações Sicoob Chatbot", width="small")
52
+ def changelog_user(self):
53
+ with open("./logos/ChangelogUser.md", encoding="utf-8") as f:
54
+ st.markdown(f"{f.read()}", unsafe_allow_html=True)
55
+
56
+ def get_current_style(self):
57
+ theme_style = self.dark_style if st.session_state.theme == "dark" else self.light_style
58
+ return f"<style>{self.base_style}{theme_style}</style>"
59
 
60
  def stream_chat(self, user_input):
61
  """
 
76
  except Exception as e:
77
  yield f"Erro ao conectar ao servidor: {e}"
78
 
79
+ def clear_chat_history(self):
80
+ st.session_state.chat_history = []
81
+ st.toast("Histórico limpo com sucesso!", icon="✅")
82
+
83
  def render_sidebar(self):
84
+ """Siderbar"""
85
+ with st.sidebar:
86
+ # st.button("Light/Dark Mode", on_click=self.change_style)
87
+ if st.session_state.theme == "light":
88
+ st.logo("./logos/sicoob-logo-horizontal-light.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
89
+ else:
90
+ st.logo("./logos/sicoob-logo-horizontal-dark.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
91
+ if st.button("Limpar Histórico", icon=":material/delete:"):
92
+ self.clear_chat_history()
93
+ st.button("Atualizações", icon=":material/info:", on_click=self.changelog_user)
94
+ st.divider()
95
+
96
+ def add_link_to_text(self, text):
97
  """
98
+ Adiciona links ao texto com base no padrão ||texto||.
99
  """
100
+ return re.sub(r'\|\|(.*?)\|\|', lambda match: f' <br>Fonte: <a href="{search_file_in_drive(match.group(1))}" target="_blank">{match.group(1)}</a>', text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  def render(self):
103
  """
104
  Renderiza a interface do chatbot.
105
  """
106
+ st.markdown(self.get_current_style(), unsafe_allow_html=True)
107
+ with open(self.configyml) as file:
108
+ config = yaml.load(file, Loader=SafeLoader)
109
+
110
+ authenticator = stauth.Authenticate(
111
+ config['credentials'],
112
+ config['cookie']['name'],
113
+ config['cookie']['key'],
114
+ config['cookie']['expiry_days']
115
+ )
116
+
117
+ with open('./config.yaml', 'w', encoding='utf-8') as file:
118
+ yaml.dump(config, file, default_flow_style=False)
119
+
120
+ authentication_status = authenticator.login(fields={'Form name': 'Autenticação', 'Username': 'Nome de Usuário', 'Password': 'Senha', 'Login': 'Entrar'})
121
+
122
+ if st.session_state["authentication_status"]:
123
+ # Renderiza a barra lateral
124
+ self.render_sidebar()
125
+
126
+ # Título e descrição
127
+ st.title(self.title)
128
+ st.write(self.description)
129
+ with st.sidebar:
130
+ st.caption(self.caption)
131
+ authenticator.logout('Sair', 'main')
132
+ # Inicializa o histórico na sessão
133
+ if "chat_history" not in st.session_state:
134
+ st.session_state.chat_history = []
135
+
136
+ # Renderiza as mensagens do histórico
137
+ for message in st.session_state.chat_history:
138
+ role, text = message.split(":", 1)
139
+ with st.chat_message(role.strip().lower()):
140
+ st.markdown(text.strip(), unsafe_allow_html=True)
141
+
142
+ # Captura o input do usuário
143
+ user_input = st.chat_input(placeholder="Digite sua pergunta...")
144
+ if user_input:
145
+ # Exibe a mensagem do usuário
146
+ with st.chat_message("user"):
147
+ st.write(user_input)
148
+ st.session_state.chat_history.append(f"user: {user_input}")
149
+ # Placeholder para a resposta do assistente
150
+ with st.chat_message("assistant"):
151
+ message_placeholder = st.empty()
152
+ assistant_message = ""
153
+ try:
154
+ # Gerando ID único para a mensagem
155
+ message_id = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
156
+ print(f"Message ID gerado: {message_id}")
157
+ # Executa o streaming de tokens enquanto o backend responde
158
+ for token in self.stream_chat(user_input):
159
+ assistant_message += token
160
+ message_placeholder.markdown(assistant_message + "▌", unsafe_allow_html=True)
161
+ except Exception as e:
162
+ st.error(f"Erro durante o chat: {str(e)}")
163
+ print(f"Erro durante o chat: {str(e)}")
164
+ finally:
165
+ assistant_message_with_link = self.add_link_to_text(assistant_message)
166
+ message_placeholder.markdown(assistant_message_with_link, unsafe_allow_html=True)
167
+ # Feedback
168
+ self.feedback_manager.render_feedback_buttons(
169
+ message_id=message_id,
170
+ user_input=user_input,
171
+ assistant_response=assistant_message_with_link
172
+ )
173
+ # Adicionando hist��rico Streamlit
174
+ st.session_state.chat_history.append(f"assistant: {assistant_message_with_link}")
175
+ elif st.session_state["authentication_status"] == False:
176
+ st.error('Nome de Usuário/Senha incorreta.')
177
+ elif st.session_state["authentication_status"] == None:
178
+ st.warning('Por favor entre com seu nome de usuário e senha.')
179
 
180
 
181
  if __name__ == "__main__":
chatbot_server.py CHANGED
@@ -1,144 +1,140 @@
1
- import os
2
- import logging
3
- import sys
4
-
5
- from flask import Flask, request, jsonify, Response
6
- # Inicializa o Flask
7
- app = Flask(__name__)
8
-
9
- logging.basicConfig(stream=sys.stdout, level=logging.INFO)
10
-
11
- from llama_index.llms.openai import OpenAI
12
- from llama_index.embeddings.openai import OpenAIEmbedding
13
- from llama_index.core import (
14
- Settings,
15
- SimpleDirectoryReader,
16
- StorageContext,
17
- Document,
18
- )
19
-
20
- Settings.llm = OpenAI(model="gpt-3.5-turbo")
21
- Settings.embed_model = OpenAIEmbedding(model_name="text-embedding-3-small")
22
- directory_path = "documentos"
23
- from llama_index.readers.file import PDFReader #concatenar todo o documento já vem nativo no pdfreader
24
- file_extractor = {".pdf": PDFReader(return_full_document = True)}
25
- from drive_downloader import GoogleDriveDownloader
26
-
27
- # ID da pasta no Drive e caminho local
28
- folder_id = "1n34bmh9rlbOtCvE_WPZRukQilKeabWsN"
29
- local_path = directory_path
30
-
31
- GoogleDriveDownloader().download_from_folder(folder_id, local_path)
32
-
33
- documents = SimpleDirectoryReader(
34
- input_dir=directory_path,
35
- file_extractor=file_extractor,
36
- filename_as_id=True,
37
- recursive=True
38
- ).load_data()
39
-
40
- from document_creator import create_single_document_with_filenames
41
- document = create_single_document_with_filenames(directory_path = directory_path)
42
- documents.append(document)
43
-
44
- from llama_index.core.ingestion import IngestionPipeline
45
- #ingestion pipeline vai entrar em uso quando adicionar o extrator de metadados
46
- from llama_index.core.node_parser import SentenceSplitter
47
- splitter = SentenceSplitter(chunk_size=1024, chunk_overlap=128)
48
- nodes = splitter.get_nodes_from_documents(documents)
49
- #from llama_index.core.extractors import (
50
- # SummaryExtractor,
51
- #)
52
- #summary = SummaryExtractor(llm = OpenAI(model="gpt-3.5-turbo", system_prompt "Sempre escreva e responda em português brasileiro."))
53
- #pipeline = IngestionPipeline(
54
- # transformations=[splitter, summary]
55
- #)
56
-
57
- #nodes = pipeline.run(
58
- # documents=documents,
59
- # in_place=True,
60
- # show_progress=True,
61
- #)
62
-
63
- from llama_index.core.storage.docstore import SimpleDocumentStore
64
- docstore = SimpleDocumentStore()
65
- docstore.add_documents(nodes)
66
-
67
- from llama_index.core import VectorStoreIndex, StorageContext
68
- from llama_index.vector_stores.chroma import ChromaVectorStore
69
- import chromadb
70
-
71
- db = chromadb.PersistentClient(path="./storage/chroma_db")
72
- chroma_collection = db.get_or_create_collection("dense_vectors")
73
- vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
74
- storage_context = StorageContext.from_defaults(
75
- docstore=docstore, vector_store=vector_store
76
- )
77
- index = VectorStoreIndex(nodes = nodes, storage_context=storage_context, show_progress = True)
78
-
79
- storage_context.docstore.persist("./storage/docstore.json")
80
-
81
- index_retriever = index.as_retriever(similarity_top_k=2)
82
- import nest_asyncio
83
- nest_asyncio.apply()
84
- from llama_index.retrievers.bm25 import BM25Retriever
85
- bm25_retriever = BM25Retriever.from_defaults(
86
- docstore=index.docstore,
87
- similarity_top_k=2,
88
- language = "portuguese",
89
- verbose=True,
90
- )
91
-
92
- from llama_index.core.retrievers import QueryFusionRetriever
93
-
94
- retriever = QueryFusionRetriever(
95
- [index_retriever, bm25_retriever],
96
- num_queries=1, #desativado = 1
97
- mode="reciprocal_rerank",
98
- use_async=True,
99
- verbose=True,
100
- )
101
-
102
- from llama_index.core.storage.chat_store import SimpleChatStore
103
- from llama_index.core.memory import ChatMemoryBuffer
104
- chat_store = SimpleChatStore()
105
- chat_memory = ChatMemoryBuffer.from_defaults(
106
- token_limit=3000,
107
- chat_store=chat_store,
108
- chat_store_key="user1",
109
- )
110
- from llama_index.core.query_engine import RetrieverQueryEngine
111
- query_engine = RetrieverQueryEngine.from_args(retriever)
112
- from llama_index.core.chat_engine import CondensePlusContextChatEngine
113
- chat_engine = CondensePlusContextChatEngine.from_defaults(
114
- query_engine,
115
- memory=chat_memory,
116
- context_prompt=(
117
- "Você é um assistente virtual capaz de interagir normalmente, além de"
118
- " fornecer informações sobre organogramas e listar funcionários."
119
- " Aqui estão os documentos relevantes para o contexto:\n"
120
- "{context_str}"
121
- "\nInstrução: Use o histórico da conversa anterior, ou o contexto acima, para responder."
122
- ),
123
- )
124
-
125
-
126
-
127
- @app.route("/chat", methods=["POST"])
128
- def chat():
129
- user_input = request.json.get("message", "")
130
- if not user_input:
131
- return jsonify({"error": "Mensagem vazia"}), 400
132
-
133
- def generate_response():
134
- try:
135
- response = chat_engine.stream_chat(user_input)
136
- for token in response.response_gen:
137
- yield token # Envia cada token
138
- chat_store.persist(persist_path="./storage/chat_store.json")
139
- except Exception as e:
140
- yield f"Erro: {str(e)}"
141
-
142
- return Response(generate_response(), content_type="text/plain")
143
- if __name__ == "__main__":
144
- app.run(port=5001, debug=False)
 
1
+ import os
2
+ import logging
3
+ import sys
4
+
5
+ from flask import Flask, request, jsonify, Response
6
+ # Inicializa o Flask
7
+ app = Flask(__name__)
8
+
9
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
10
+
11
+ from llama_index.llms.openai import OpenAI
12
+ from llama_index.embeddings.openai import OpenAIEmbedding
13
+ from llama_index.core import (
14
+ Settings,
15
+ SimpleDirectoryReader,
16
+ StorageContext,
17
+ Document,
18
+ )
19
+
20
+ Settings.llm = OpenAI(model="gpt-3.5-turbo")
21
+ Settings.embed_model = OpenAIEmbedding(model_name="text-embedding-3-small")
22
+ directory_path = "documentos"
23
+ from llama_index.readers.file import PDFReader #concatenar todo o documento já vem nativo no pdfreader
24
+ file_extractor = {".pdf": PDFReader(return_full_document = True)}
25
+ from drive_downloader import GoogleDriveDownloader
26
+
27
+ # ID da pasta no Drive e caminho local
28
+ folder_id = "1n34bmh9rlbOtCvE_WPZRukQilKeabWsN"
29
+ local_path = directory_path
30
+
31
+ GoogleDriveDownloader().download_from_folder(folder_id, local_path)
32
+
33
+ documents = SimpleDirectoryReader(
34
+ input_dir=directory_path,
35
+ file_extractor=file_extractor,
36
+ filename_as_id=True,
37
+ recursive=True
38
+ ).load_data()
39
+
40
+ from document_creator import create_single_document_with_filenames
41
+ document = create_single_document_with_filenames(directory_path = directory_path)
42
+ documents.append(document)
43
+
44
+ #from llama_index.core.ingestion import IngestionPipeline
45
+ #ingestion pipeline vai entrar em uso quando adicionar o extrator de metadados
46
+ from llama_index.core.node_parser import SentenceSplitter
47
+ splitter = SentenceSplitter(chunk_size=1024, chunk_overlap=128)
48
+ nodes = splitter.get_nodes_from_documents(documents)
49
+
50
+ from llama_index.core.storage.docstore import SimpleDocumentStore
51
+ docstore = SimpleDocumentStore()
52
+ docstore.add_documents(nodes)
53
+
54
+ from llama_index.core import VectorStoreIndex, StorageContext
55
+ from llama_index.vector_stores.chroma import ChromaVectorStore
56
+ import chromadb
57
+
58
+ db = chromadb.PersistentClient(path="chroma_db")
59
+ chroma_collection = db.get_or_create_collection("dense_vectors")
60
+ vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
61
+ storage_context = StorageContext.from_defaults(
62
+ docstore=docstore, vector_store=vector_store
63
+ )
64
+ index = VectorStoreIndex(nodes = nodes, storage_context=storage_context, show_progress = True)
65
+
66
+ storage_context.docstore.persist("./docstore.json")
67
+
68
+ index_retriever = index.as_retriever(similarity_top_k=2)
69
+ import nest_asyncio
70
+ nest_asyncio.apply()
71
+ from llama_index.retrievers.bm25 import BM25Retriever
72
+ bm25_retriever = BM25Retriever.from_defaults(
73
+ docstore=index.docstore,
74
+ similarity_top_k=2,
75
+ language = "portuguese",
76
+ verbose=True,
77
+ )
78
+
79
+ from llama_index.core.retrievers import QueryFusionRetriever
80
+
81
+ retriever = QueryFusionRetriever(
82
+ [index_retriever, bm25_retriever],
83
+ num_queries=1, #desativado = 1
84
+ mode="reciprocal_rerank",
85
+ use_async=True,
86
+ verbose=True,
87
+ )
88
+
89
+
90
+ from llama_index.core.memory import ChatMemoryBuffer
91
+ from mysqlchatstore import MySQLChatStore
92
+ chat_store = MySQLChatStore.from_params(
93
+ host=os.getenv("MYSQL_HOST"),
94
+ port=os.getenv("MYSQL_PORT"),
95
+ user=os.getenv("MYSQL_USER"),
96
+ password=os.getenv("MYSQL_PASSWORD"),
97
+ database=os.getenv("MYSQL_DATABASE"),
98
+ table_name=os.getenv("MYSQL_TABLE")
99
+ )
100
+ chat_memory = ChatMemoryBuffer.from_defaults(
101
+ token_limit=3000,
102
+ chat_store=chat_store,
103
+ chat_store_key="Sicoob", #Tendo algumas dificuldades ainda pra passar o user
104
+ )
105
+ from llama_index.core.query_engine import RetrieverQueryEngine
106
+ query_engine = RetrieverQueryEngine.from_args(retriever)
107
+ from llama_index.core.chat_engine import CondensePlusContextChatEngine
108
+ chat_engine = CondensePlusContextChatEngine.from_defaults(
109
+ query_engine,
110
+ memory=chat_memory,
111
+ context_prompt=(
112
+ "Você é um assistente virtual capaz de interagir normalmente, além de"
113
+ " fornecer informações sobre organogramas e listar funcionários."
114
+ " Aqui estão os documentos relevantes para o contexto:\n"
115
+ "{context_str}"
116
+ "\nInstrução: Use o histórico da conversa anterior, ou o contexto acima, para responder."
117
+ "No final da resposta, depois de uma quebra de linha escreva o nome do documento que contém a informação entre dois ||, como ||Documento Nome||"
118
+
119
+ ),
120
+ )
121
+
122
+
123
+
124
+ @app.route("/chat", methods=["POST"])
125
+ def chat():
126
+ user_input = request.json.get("message", "")
127
+ if not user_input:
128
+ return jsonify({"error": "Mensagem vazia"}), 400
129
+
130
+ def generate_response():
131
+ try:
132
+ response = chat_engine.stream_chat(user_input)
133
+ for token in response.response_gen:
134
+ yield token # Envia cada token
135
+ except Exception as e:
136
+ yield f"Erro: {str(e)}"
137
+
138
+ return Response(generate_response(), content_type="text/plain")
139
+ if __name__ == "__main__":
140
+ app.run(port=5001, debug=False)
 
 
 
 
config.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cookie:
2
+ expiry_days: 30
3
+ key: some_signature_key
4
+ name: some_cookie_name
5
+ credentials:
6
+ usernames:
7
+ akcit_root:
8
+ email: akcit_root@mail.com
9
+ failed_login_attempts: 0
10
+ first_name: akcit
11
+ last_name: root
12
+ logged_in: false
13
+ password: $2b$12$wd1lg1DTs4qEDRmohdptDegeSJhGstxqgCaTitfRQ0IGN.rnr51aG
14
+ roles:
15
+ - admin
16
+ - editor
17
+ - viewer
18
+ sicoob_central:
19
+ email: sicoob_central@mail.com
20
+ failed_login_attempts: 0
21
+ first_name: sicoob
22
+ last_name: central
23
+ logged_in: false
24
+ password: $2b$12$Y5tHfGABzVP9dm510HuHHuUbeZvNqUibUgj4TYH40rglhZGLPZ8rK
25
+ roles:
26
+ - viewer
27
+ sicoob_unidade:
28
+ email: sicoob_unidade@mail.com
29
+ failed_login_attempts: 0
30
+ first_name: sicoob
31
+ last_name: unidade
32
+ logged_in: false
33
+ password: $2b$12$h8U7XrVfACkHJaqGqcwR0OzDO.YorKF21lHpG/9MVa4K/98AbXtG.
34
+ roles:
35
+ - viewer
36
+ pre-authorized:
37
+ emails:
38
+ - akcit_root@mail.com
drive_search.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from googleapiclient.discovery import build
2
+ from google.oauth2.service_account import Credentials
3
+ from fuzzywuzzy import process # Importando a biblioteca fuzzywuzzy
4
+ import os
5
+
6
+ SCOPES = ["https://www.googleapis.com/auth/drive.readonly"]
7
+ FOLDER_ID = "1hqfPQnsVL2Ld8hu0GRIqcuOp-eDz-CAX" # ID da pasta que você quer buscar
8
+
9
+ SERVICE_ACCOUNT_FILE = os.path.join(os.getcwd(), "./files/credenciais.json")
10
+ def authenticate_drive():
11
+ """Autentica no Google Drive usando uma conta de serviço."""
12
+ credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
13
+ service = build("drive", "v3", credentials=credentials)
14
+ return service
15
+
16
+ def list_files_recursive(service, folder_id, path=""):
17
+ """Lista todos os arquivos e subpastas no Google Drive de forma recursiva."""
18
+ query = f"'{folder_id}' in parents and trashed = false"
19
+ response = service.files().list(
20
+ q=query,
21
+ spaces="drive",
22
+ fields="files(id, name, mimeType, parents)",
23
+ ).execute()
24
+
25
+ files = response.get("files", [])
26
+ all_files = []
27
+
28
+ for file in files:
29
+ # caminho completo
30
+ current_path = f"{path}/{file['name']}"
31
+ all_files.append({"id": file["id"], "name": file["name"], "path": current_path})
32
+
33
+ # buscar recursivamente
34
+ if file["mimeType"] == "application/vnd.google-apps.folder":
35
+ all_files.extend(list_files_recursive(service, file["id"], current_path))
36
+
37
+ return all_files
38
+
39
+ def find_file_by_name(files, search_name):
40
+ """Encontra um arquivo com nome aproximado utilizando fuzzy matching."""
41
+ # Usando fuzzywuzzy para encontrar o arquivo mais próximo
42
+ file_names = [file["name"] for file in files]
43
+ best_match, score = process.extractOne(search_name, file_names)
44
+
45
+ if score >= 80: # Ajuste a pontuação mínima de correspondência (por exemplo, 80%)
46
+ matching_files = [file for file in files if file["name"] == best_match]
47
+ return matching_files
48
+ else:
49
+ return []
50
+
51
+ def search_file_in_drive(search_name):
52
+ """Procura o arquivo mais relevante no Google Drive e retorna o link."""
53
+ service = authenticate_drive()
54
+ print("Autenticado com sucesso no Google Drive!")
55
+
56
+ # Listar arquivos na pasta e subpastas
57
+ files = list_files_recursive(service, FOLDER_ID)
58
+
59
+ if not files:
60
+ print("Nenhum arquivo encontrado na pasta!")
61
+ return None # Retorna None se nenhum arquivo for encontrado
62
+
63
+ print(f"Total de arquivos encontrados: {len(files)}")
64
+
65
+ # Encontrar o arquivo com nome aproximado
66
+ matching_files = find_file_by_name(files, search_name)
67
+
68
+ if matching_files:
69
+ best_file = matching_files[0] # Pega o arquivo mais relevante (primeiro da lista)
70
+ link = f"https://drive.google.com/file/d/{best_file['id']}/view"
71
+ print(f"Arquivo encontrado: {best_file['name']}")
72
+ print(f"Link: {link}")
73
+ return link # Retorna o link do arquivo encontrado
74
+ else:
75
+ print(f"Nenhum arquivo encontrado com nome aproximado '{search_name}'.")
76
+ return None # Retorna None se nenhum arquivo for encontrado
77
+
feedback_manager.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ import streamlit as st
5
+
6
+
7
+ class FeedbackManager:
8
+ def __init__(self, feedback_dir="./feedback"):
9
+ self.feedback_dir = Path(feedback_dir)
10
+ self.feedback_dir.mkdir(exist_ok=True)
11
+ if "feedback_submitted" not in st.session_state:
12
+ st.session_state.feedback_submitted = set()
13
+
14
+ def save_feedback(self, feedback_type: str, user_input: str, assistant_response: str, message_id: str):
15
+ if message_id in st.session_state.feedback_submitted:
16
+ st.toast("Feedback já enviado para está mensagem.", icon="ℹ️")
17
+ return False
18
+ feedback_file = self.feedback_dir / f"feedback_{feedback_type}.json"
19
+ try:
20
+ data = []
21
+ if feedback_file.exists():
22
+ with open(feedback_file, 'r', encoding='utf-8') as f:
23
+ data = json.load(f)
24
+ feedback_entry = {
25
+ "message_id": message_id,
26
+ "timestamp": datetime.now().isoformat(),
27
+ "feedback_type": feedback_type,
28
+ "user_input": user_input,
29
+ "assistant_response": assistant_response,
30
+ }
31
+ data.append(feedback_entry)
32
+ with open(feedback_file, 'w', encoding='utf-8') as f:
33
+ json.dump(data, f, ensure_ascii=False, indent=2)
34
+ st.session_state.feedback_submitted.add(message_id)
35
+ emoji = "👍" if feedback_type == "positive" else "👎"
36
+ st.toast(f"Obrigado por seu Feedback! {emoji}", icon="✅")
37
+ return True
38
+ except Exception as e:
39
+ st.error(f"Error ao salvar feedback: {str(e)}")
40
+ return False
41
+
42
+ def handle_feedback_click(self, feedback_type: str, message_id: str, user_input: str, assistant_response: str):
43
+ self.save_feedback(
44
+ feedback_type=feedback_type,
45
+ user_input=user_input,
46
+ assistant_response=assistant_response,
47
+ message_id=message_id
48
+ )
49
+
50
+ def render_feedback_buttons(self, message_id: str, user_input: str, assistant_response: str):
51
+ col1, col2 = st.columns(2, gap="small")
52
+
53
+ with col1:
54
+ st.button(
55
+ "👍 Gostei",
56
+ key=f"positive_{message_id}",
57
+ on_click=self.handle_feedback_click,
58
+ args=("positive", message_id, user_input, assistant_response)
59
+ )
60
+
61
+ with col2:
62
+ st.button(
63
+ "👎 Não Gostei",
64
+ key=f"negative_{message_id}",
65
+ on_click=self.handle_feedback_click,
66
+ args=("negative", message_id, user_input, assistant_response)
67
+ )
files/config.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cookie:
2
+ expiry_days: 30
3
+ key: some_signature_key
4
+ name: some_cookie_name
5
+ credentials:
6
+ usernames:
7
+ akcit_root:
8
+ email: akcit_root@mail.com
9
+ failed_login_attempts: 0
10
+ first_name: akcit
11
+ last_name: root
12
+ logged_in: false
13
+ password: $2b$12$wd1lg1DTs4qEDRmohdptDegeSJhGstxqgCaTitfRQ0IGN.rnr51aG
14
+ roles:
15
+ - admin
16
+ - editor
17
+ - viewer
18
+ sicoob_central:
19
+ email: sicoob_central@mail.com
20
+ failed_login_attempts: 0
21
+ first_name: sicoob
22
+ last_name: central
23
+ logged_in: false
24
+ password: $2b$12$Y5tHfGABzVP9dm510HuHHuUbeZvNqUibUgj4TYH40rglhZGLPZ8rK
25
+ roles:
26
+ - viewer
27
+ sicoob_unidade:
28
+ email: sicoob_unidade@mail.com
29
+ failed_login_attempts: 0
30
+ first_name: sicoob
31
+ last_name: unidade
32
+ logged_in: false
33
+ password: $2b$12$h8U7XrVfACkHJaqGqcwR0OzDO.YorKF21lHpG/9MVa4K/98AbXtG.
34
+ roles:
35
+ - viewer
36
+ pre-authorized:
37
+ emails:
38
+ - akcit_root@mail.com
files/credenciais.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "light-processor-374314",
4
+ "private_key_id": "a207b3d84c4dfdf97eef75f442a78b79a9af7e4a",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCcU0q1HO5hdzVK\nGcVGnOaZ/Eedjqr+g70yrOylrE1Ke794vXAOFynbq2OifjwjuTJQD0ZSP2KGQC7x\np1jHolEgP7GHsusnCZeb6lPyQCvkk8M6EzxdiSY5cOm4xJAzEENZ0aFB5qMdtR/t\ndjO4YwK8eHGUXAWeL7OezC7xzLpbkFnOJYo44CQVgbIWoROQY/7MNqcw67/z/HS9\nETKyB9+lgv+w1f+1HG9XBvHKjoI79fa4njmJBGu66DN0afp9lYrdqbX24j3fieaB\nyhJOq6w0U16t/nvfLhzxixAVafMQSvuENh6DjXfQ5wCy+U2osALH5zEOqkOmNWo1\neWMPQiwVAgMBAAECggEAENdmWonB4s05eAC7wKZBr8A+pOpMYLwno3B+1EByWT5o\nu+TI0DPNpiVaSBTKfNzEX1yt6kl631TF2fH5/hPuIofKJADyFZQSedEudxBTyj3j\nD/wnijou3IxeGbJPiLjNUL1lXpiu5RHw3R/ZZZmBH3XoYp/hWQ/xjX+Y5SL1xsxk\nuslv+xGmFjAWCzCSFMIosyRl8evElFyuWeEU9IGM8h9JKs/aLTkdkN+X7o77L9pi\ndhQj5RemmKtzm7MStwVFGT2zdkMk3LCnXYi+VbuIi+wHJuZbxn+xP7nhnsu4vLcQ\nNzZKouk/7dK2px2zkaclnu8kNTQy3ONAfFrYZb3BQQKBgQDUICD7aIaA/cBlI5Ph\nifrtsyRKdWMhU2Sobe7qtX2s+azgcX7o57wxIo/dnG/2IG1rFBFbUO6Qv8UcfS9B\n9KNIdfV1+YnudOYn7TaFAhQfudWF9HrTsWZaIHpHS5yyzcpHoXvehKcU6MXy4vMj\nirY4dbx8daeiaBbkJHMtOKJZdQKBgQC8qJdWwjkSW0eJoqnNYP4SHtCo056S6Ufp\n8qkBBrpnnq85jJGzXJ71fWR3pgvGXLRjYSk2t3Csy3S7lwe6G9iszdBloHdTEQb7\nEfPpmE23m+s3YQIL0jY2UdTRrudtmk8RYpwEKZ22fz9d1x4pIBIlU2Pcc26j4uhu\nZ3zbC4mUIQKBgQDEb+RbLRaxyUsr3eCKUg6vpN+MnFxqdiGW4AcKD3wMfUIcrr3J\nzR+3mLwFi2MbWDg7mt/f4niqTwyoLz1eJMA40BO5ZpbW3iZs/v0n+x7LqnoTjK1Z\n8MRJ3h2efGTmKDCUWPSuwcVAVbdKD+T9Gu1YJ5+e2g2dFitsplyKmhGuKQKBgQCx\nPSpBBeMcTckdk0Y3fwHzACREF9wIZUV8ks8X+bwyETDJvjg7664jMBStG8BAMWP/\nYY6YqyoeDF60xiUqQXMEla9Nar3vujV2tt0R/lY1QzRuKKMFfA4WZjasb8dYfvn9\neUjd2EMk6tMbVDgvpsOlcXyF5aRyL4DyCCOSnno4QQKBgQC92jVM7MkDSzlMT6zt\nJ2HQAvKR/uzxz0GQJrqx8YPUsmGUDJDcMXbjVNCSw2UfJkdlZIfZQrbpQJACewqA\nWLfWbsGvoyZk5AnkVVQqcIrRVdk85cL5EG32VSQIyAlH+m5f4DwcEHdVUFysS6BB\nYglZoVNfDeElMat1FZR9dhXo9w==\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "mydriveapi@light-processor-374314.iam.gserviceaccount.com",
7
+ "client_id": "115319015910729374958",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/mydriveapi%40light-processor-374314.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
logos/ChangelogUser.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog - Versão 0.4.0
2
+
3
+ ## Novos Recursos
4
+
5
+ - **Suporte a múltiplos manuais**: O chatbot agora pode acessar e fornecer informações de diversos manuais.
6
+ - Manuais disponíveis nesta versão:
7
+ - Manual de Normatização
8
+ - Manual de Riscos Sociais, Ambientais e Climáticos
9
+ - Manual de Supervisão Auxiliar
10
+ - Manual de Controles Internos e Conformidade
11
+ - Manual de Crédito
12
+ - Manual de Boas Práticas Legais e Tributárias nas Campanhas e Promoções
13
+ - Manual de Gerenciamento do Risco de Mercado e do IRRBB
14
+ - Manual de Seguro Vida Prestamista
15
+ - Manual do Produto Pix
16
+ - Manual de Câmbio - Operações de Crédito
17
+ - ...
18
+
19
+ ## Melhorias
20
+
21
+ - **Qualidade de resposta aprimorada**: Refinamento nos modelos de resposta para oferecer informações mais precisas e relevantes.
22
+ - **Otimização de consultas**: Melhorias no tempo de resposta ao buscar informações em documentos.
23
+
24
+ ## Alterações e Remoções
25
+
26
+ - **Remoção temporária de respostas sobre o organograma**: Devido a ajustes internos, informações sobre a estrutura organizacional foram temporariamente desativadas. Previsto para retorno na versão 0.5.0.
27
+
28
+ ## Próximos Passos
29
+
30
+ - **Expansão do suporte a mais manuais e documentos internos.**
31
+ - **Reintegração das informações sobre o organograma na versão 0.5.0.**
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Black.eot ADDED
Binary file (36.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-BlackItalic.eot ADDED
Binary file (37.5 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Bold.eot ADDED
Binary file (37.9 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-BoldItalic.eot ADDED
Binary file (38.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Extrabold.eot ADDED
Binary file (38.3 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ExtraboldItalic.eot ADDED
Binary file (39.1 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Extralight.eot ADDED
Binary file (37.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ExtralightItalic.eot ADDED
Binary file (38.3 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Italic.eot ADDED
Binary file (37.9 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Light.eot ADDED
Binary file (37.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-LightItalic.eot ADDED
Binary file (38.5 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Medium.eot ADDED
Binary file (37.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-MediumItalic.eot ADDED
Binary file (38.4 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Regular.eot ADDED
Binary file (37.1 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Semibold.eot ADDED
Binary file (38 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-SemiboldItalic.eot ADDED
Binary file (38.8 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-Thin.eot ADDED
Binary file (35.4 kB). View file
 
logos/fonts/Sicoob_Sans/EOT/SicoobSansRC3-ThinItalic.eot ADDED
Binary file (36.3 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Black.ttf ADDED
Binary file (99 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-BlackItalic.ttf ADDED
Binary file (99.3 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Bold.ttf ADDED
Binary file (100 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-BoldItalic.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Extrabold.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ExtraboldItalic.ttf ADDED
Binary file (102 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Extralight.ttf ADDED
Binary file (100 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ExtralightItalic.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Italic.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Light.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-LightItalic.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Medium.ttf ADDED
Binary file (100 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-MediumItalic.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Regular.ttf ADDED
Binary file (100 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Semibold.ttf ADDED
Binary file (101 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-SemiboldItalic.ttf ADDED
Binary file (102 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-Thin.ttf ADDED
Binary file (94.2 kB). View file
 
logos/fonts/Sicoob_Sans/TTF/SicoobSansRC3-ThinItalic.ttf ADDED
Binary file (96.5 kB). View file
 
logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-Black.woff ADDED
Binary file (38.3 kB). View file
 
logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-BlackItalic.woff ADDED
Binary file (39.1 kB). View file
 
logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-Bold.woff ADDED
Binary file (39.3 kB). View file
 
logos/fonts/Sicoob_Sans/WOFF/SicoobSansRC3-BoldItalic.woff ADDED
Binary file (40.1 kB). View file