IO4IT 2.0.0__tar.gz → 2.1.0__tar.gz
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.
- io4it-2.1.0/PKG-INFO +7 -0
- io4it-2.1.0/io4it.egg-info/PKG-INFO +7 -0
- {io4it-2.0.0 → io4it-2.1.0}/io4it.egg-info/SOURCES.txt +2 -1
- io4it-2.1.0/io4it.egg-info/entry_points.txt +7 -0
- {io4it-2.0.0 → io4it-2.1.0}/io4it.egg-info/requires.txt +6 -1
- io4it-2.1.0/orangecontrib/IO4IT/widgets/OWChatGpt.py +201 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWDeep_Search.py +2 -1
- io4it-2.1.0/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +216 -0
- io4it-2.1.0/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +563 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWS3Uploader.py +1 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWS3downloader.py +1 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWS3list.py +1 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWSpeechToText.py +1 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWmailSender.py +1 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +1 -0
- io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owchatgpt.ui +155 -0
- io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui +101 -0
- io4it-2.1.0/setup.py +74 -0
- io4it-2.0.0/PKG-INFO +0 -23
- io4it-2.0.0/io4it.egg-info/PKG-INFO +0 -23
- io4it-2.0.0/io4it.egg-info/entry_points.txt +0 -2
- io4it-2.0.0/orangecontrib/IO4IT/widgets/OWChatGpt.py +0 -121
- io4it-2.0.0/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +0 -153
- io4it-2.0.0/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +0 -234
- io4it-2.0.0/setup.py +0 -98
- {io4it-2.0.0 → io4it-2.1.0}/io4it.egg-info/dependency_links.txt +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/io4it.egg-info/namespace_packages.txt +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/io4it.egg-info/top_level.txt +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/ocr_function/word_converter.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/utils/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/utils/mail.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/utils/offuscation_basique.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/OWmailLoader.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/chart.html +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/nogui.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owmailloader.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owmailsender.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/chatgpt.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/deepsearch.svg +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/download.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/export_md.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/file_extensor.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/mail_loader.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/mail_writer.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/md.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/visualizationer.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/IO4IT/widgets/icons_dev/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/orangecontrib/__init__.py +0 -0
- {io4it-2.0.0 → io4it-2.1.0}/setup.cfg +0 -0
io4it-2.1.0/PKG-INFO
ADDED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
setup.cfg
|
|
2
1
|
setup.py
|
|
3
2
|
io4it.egg-info/PKG-INFO
|
|
4
3
|
io4it.egg-info/SOURCES.txt
|
|
@@ -30,10 +29,12 @@ orangecontrib/IO4IT/widgets/designer/__init__.py
|
|
|
30
29
|
orangecontrib/IO4IT/widgets/designer/chart.html
|
|
31
30
|
orangecontrib/IO4IT/widgets/designer/nogui.ui
|
|
32
31
|
orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui
|
|
32
|
+
orangecontrib/IO4IT/widgets/designer/owchatgpt.ui
|
|
33
33
|
orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui
|
|
34
34
|
orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui
|
|
35
35
|
orangecontrib/IO4IT/widgets/designer/owmailloader.ui
|
|
36
36
|
orangecontrib/IO4IT/widgets/designer/owmailsender.ui
|
|
37
|
+
orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui
|
|
37
38
|
orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui
|
|
38
39
|
orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui
|
|
39
40
|
orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[orange.widgets]
|
|
2
|
+
AAIT - ALGORITHM = orangecontrib.ALGORITHM.widgets
|
|
3
|
+
AAIT - API = orangecontrib.API.widgets
|
|
4
|
+
AAIT - LLM INTEGRATION = orangecontrib.LLM_INTEGRATION.widgets
|
|
5
|
+
AAIT - MODELS = orangecontrib.LLM_MODELS.widgets
|
|
6
|
+
AAIT - TOOLBOX = orangecontrib.TOOLBOX.widgets
|
|
7
|
+
IO4IT = orangecontrib.IO4IT.widgets
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import base64
|
|
4
|
+
import ast
|
|
5
|
+
import openai
|
|
6
|
+
import Orange
|
|
7
|
+
from Orange.data import StringVariable
|
|
8
|
+
from Orange.widgets.widget import OWWidget, Input, Output
|
|
9
|
+
from Orange.widgets.settings import Setting
|
|
10
|
+
from AnyQt.QtWidgets import QApplication, QLineEdit, QSpinBox, QDoubleSpinBox, QPushButton
|
|
11
|
+
|
|
12
|
+
if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
|
|
13
|
+
from Orange.widgets.orangecontrib.AAIT.utils import thread_management
|
|
14
|
+
from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
|
|
15
|
+
from Orange.widgets.orangecontrib.IO4IT.utils import offuscation_basique
|
|
16
|
+
else:
|
|
17
|
+
from orangecontrib.AAIT.utils.import_uic import uic
|
|
18
|
+
from orangecontrib.AAIT.utils import thread_management
|
|
19
|
+
from orangecontrib.IO4IT.utils import offuscation_basique
|
|
20
|
+
|
|
21
|
+
class ChatGpt(OWWidget):
|
|
22
|
+
name = "CallChatGptApi"
|
|
23
|
+
description = "Call to chatgpt API. You need to provide a prompt and an api_keys. You call also add an image_paths and a system_prompt if you want."
|
|
24
|
+
icon = "icons/chatgpt.png"
|
|
25
|
+
if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
|
|
26
|
+
icon = "icons_dev/chatgpt.png"
|
|
27
|
+
priority = 3000
|
|
28
|
+
max_tokens = Setting(4096)
|
|
29
|
+
temperature = Setting(0)
|
|
30
|
+
model = Setting("gpt-4o")
|
|
31
|
+
gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owchatgpt.ui")
|
|
32
|
+
want_control_area = False
|
|
33
|
+
category = "AAIT - LLM INTEGRATION"
|
|
34
|
+
|
|
35
|
+
class Inputs:
|
|
36
|
+
data = Input("Data", Orange.data.Table)
|
|
37
|
+
|
|
38
|
+
@Inputs.data
|
|
39
|
+
def set_data(self, in_data):
|
|
40
|
+
if in_data is None:
|
|
41
|
+
return
|
|
42
|
+
if "prompt" not in in_data.domain:
|
|
43
|
+
self.error("input table need a prompt column")
|
|
44
|
+
return
|
|
45
|
+
self.prompt = in_data.get_column("prompt")[0]
|
|
46
|
+
if "image_paths" in in_data.domain:
|
|
47
|
+
self.image_paths = in_data.get_column("image_paths")[0]
|
|
48
|
+
if "api_keys" not in in_data.domain:
|
|
49
|
+
self.api_keys = offuscation_basique.lire_config_cli_api("CHAT_GPT")
|
|
50
|
+
if offuscation_basique.lire_config_cli_api("CHAT_GPT") is None:
|
|
51
|
+
self.error("input table need a api_keys column or a folder API with you CHAT_GPT keys in aait store")
|
|
52
|
+
if self.api_keys is None:
|
|
53
|
+
self.api_keys = in_data.get_column("api_keys")[0]
|
|
54
|
+
if "system_prompt" in in_data.domain:
|
|
55
|
+
self.system_prompt = in_data.get_column("system_prompt")[0]
|
|
56
|
+
self.data = in_data
|
|
57
|
+
self.run()
|
|
58
|
+
|
|
59
|
+
class Outputs:
|
|
60
|
+
data = Output("Data", Orange.data.Table)
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
super().__init__()
|
|
64
|
+
# Qt Management
|
|
65
|
+
self.setFixedWidth(400)
|
|
66
|
+
self.setFixedHeight(300)
|
|
67
|
+
uic.loadUi(self.gui, self)
|
|
68
|
+
self.edit_model = self.findChild(QLineEdit, 'modelName')
|
|
69
|
+
self.edit_model.setPlaceholderText("Model name")
|
|
70
|
+
self.edit_model.setText(self.model)
|
|
71
|
+
self.edit_model.editingFinished.connect(self.update_parameters)
|
|
72
|
+
|
|
73
|
+
self.edit_max_tokens = self.bind_spinbox("boxMaxTokens", self.max_tokens)
|
|
74
|
+
self.edit_temperature = self.bind_spinbox("boxTemperature", self.temperature, is_double=True)
|
|
75
|
+
self.pushButton_call_api =self.findChild(QPushButton, 'callApi')
|
|
76
|
+
self.pushButton_call_api.clicked.connect(self.run)
|
|
77
|
+
|
|
78
|
+
self.data = None
|
|
79
|
+
self.prompt = None
|
|
80
|
+
self.image_paths = None
|
|
81
|
+
self.api_keys = None
|
|
82
|
+
self.system_prompt = ""
|
|
83
|
+
self.text_response = None
|
|
84
|
+
self.thread = None
|
|
85
|
+
self.run()
|
|
86
|
+
|
|
87
|
+
def bind_spinbox(self, name, value, is_double=False):
|
|
88
|
+
widget_type = QDoubleSpinBox if is_double else QSpinBox
|
|
89
|
+
box = self.findChild(widget_type, name)
|
|
90
|
+
box.setValue(value)
|
|
91
|
+
box.editingFinished.connect(self.update_parameters)
|
|
92
|
+
return box
|
|
93
|
+
|
|
94
|
+
def update_parameters(self):
|
|
95
|
+
self.max_tokens = self.edit_max_tokens.value()
|
|
96
|
+
self.temperature = self.edit_temperature.value()
|
|
97
|
+
self.model = self.edit_model.text()
|
|
98
|
+
|
|
99
|
+
def generate_answers(self):
|
|
100
|
+
try:
|
|
101
|
+
openai.api_key = self.api_keys
|
|
102
|
+
response = openai.chat.completions.create(
|
|
103
|
+
model=self.model,
|
|
104
|
+
messages=[
|
|
105
|
+
{
|
|
106
|
+
"role": "system",
|
|
107
|
+
"content": self.system_prompt
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"role": "user",
|
|
111
|
+
"content": self.prompt
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
max_tokens=self.max_tokens,
|
|
115
|
+
temperature=self.temperature
|
|
116
|
+
)
|
|
117
|
+
self.text_response = response.choices[0].message.content
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(e)
|
|
120
|
+
self.error(f"Error: {e}")
|
|
121
|
+
return
|
|
122
|
+
if self.text_response is None:
|
|
123
|
+
self.error("No response from chatgpt.")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def run(self):
|
|
127
|
+
self.error("")
|
|
128
|
+
self.warning("")
|
|
129
|
+
if self.data is None:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if self.prompt == "" or self.prompt is None:
|
|
133
|
+
self.error("No prompt provided.")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if self.api_keys is None:
|
|
137
|
+
self.error("No api keys provided.")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# si on relance la génération par le bouton le prompt est déjà rempli (déjà une liste)
|
|
141
|
+
# mais on peut changer le model, la temp ou le nombre max de token
|
|
142
|
+
if isinstance(self.prompt, list):
|
|
143
|
+
self.prompt = [{"type": "text", "text": self.prompt}]
|
|
144
|
+
if self.image_paths is not None and self.image_paths != []:
|
|
145
|
+
if type(self.image_paths) == str:
|
|
146
|
+
self.image_paths = ast.literal_eval(self.image_paths)
|
|
147
|
+
for img_path in self.image_paths:
|
|
148
|
+
filename = os.path.basename(img_path)
|
|
149
|
+
|
|
150
|
+
# Ajoute une mention de l’image avant
|
|
151
|
+
self.prompt.append({
|
|
152
|
+
"type": "text",
|
|
153
|
+
"text": f"Photo : {filename}"
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
with open(img_path, "rb") as f:
|
|
157
|
+
b64_img = base64.b64encode(f.read()).decode("utf-8")
|
|
158
|
+
self.prompt.append({
|
|
159
|
+
"type": "image_url",
|
|
160
|
+
"image_url": {
|
|
161
|
+
"url": f"data:image/jpeg;base64,{b64_img}"
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
self.progressBarInit()
|
|
165
|
+
self.thread = thread_management.Thread(self.generate_answers)
|
|
166
|
+
self.thread.progress.connect(self.handle_progress)
|
|
167
|
+
self.thread.result.connect(self.handle_result)
|
|
168
|
+
self.thread.finish.connect(self.handle_finish)
|
|
169
|
+
self.thread.start()
|
|
170
|
+
|
|
171
|
+
def handle_progress(self, progress) -> None:
|
|
172
|
+
value = progress[0]
|
|
173
|
+
text = progress[1]
|
|
174
|
+
if value is not None:
|
|
175
|
+
self.progressBarSet(value)
|
|
176
|
+
if text is None:
|
|
177
|
+
self.textBrowser.setText("")
|
|
178
|
+
else:
|
|
179
|
+
self.textBrowser.insertPlainText(text)
|
|
180
|
+
|
|
181
|
+
def handle_result(self, result):
|
|
182
|
+
new_name_column = StringVariable("answer")
|
|
183
|
+
table = self.data.add_column(new_name_column, [self.text_response])
|
|
184
|
+
self.Outputs.data.send(table)
|
|
185
|
+
|
|
186
|
+
def handle_finish(self):
|
|
187
|
+
print("Generation finished")
|
|
188
|
+
self.progressBarFinished()
|
|
189
|
+
|
|
190
|
+
def post_initialized(self):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
app = QApplication(sys.argv)
|
|
195
|
+
my_widget = ChatGpt()
|
|
196
|
+
my_widget.show()
|
|
197
|
+
|
|
198
|
+
if hasattr(app, "exec"):
|
|
199
|
+
sys.exit(app.exec())
|
|
200
|
+
else:
|
|
201
|
+
sys.exit(app.exec_())
|
|
@@ -26,7 +26,8 @@ else:
|
|
|
26
26
|
@apply_modification_from_python_file(filepath_original_widget=__file__)
|
|
27
27
|
class OWDeep_Search(widget.OWWidget):
|
|
28
28
|
name = "Deep Search"
|
|
29
|
-
description = "Generate a response to a column 'prompt' with a
|
|
29
|
+
description = "Generate a response to a column 'prompt' with a LLM"
|
|
30
|
+
category = "AAIT - LLM INTEGRATION"
|
|
30
31
|
icon = "icons/deepsearch.svg"
|
|
31
32
|
if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
|
|
32
33
|
icon = "icons_dev/owqueryllm.svg"
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import tempfile
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
import Orange
|
|
7
|
+
from Orange.data import StringVariable
|
|
8
|
+
from Orange.widgets import widget
|
|
9
|
+
from Orange.widgets.widget import Input, Output
|
|
10
|
+
from AnyQt.QtWidgets import QMessageBox, QApplication
|
|
11
|
+
|
|
12
|
+
from docx import Document
|
|
13
|
+
from docx.shared import Pt as pt_docx
|
|
14
|
+
from pptx import Presentation
|
|
15
|
+
from pptx.util import Inches, Pt
|
|
16
|
+
import pypandoc
|
|
17
|
+
from docx2pdf import convert
|
|
18
|
+
|
|
19
|
+
# Chargement UI
|
|
20
|
+
if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
|
|
21
|
+
from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
|
|
22
|
+
else:
|
|
23
|
+
from orangecontrib.AAIT.utils.import_uic import uic
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OWExportMarkdown(widget.OWWidget):
|
|
27
|
+
name = "OWExportMarkdown"
|
|
28
|
+
description = "Auto-exporte content→(docx,pptx,pdf) au même path (base + extensions)."
|
|
29
|
+
icon = "icons/export_md.png"
|
|
30
|
+
if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
|
|
31
|
+
icon = "icons_dev/export_md.png"
|
|
32
|
+
want_control_area = True
|
|
33
|
+
priority = 9999
|
|
34
|
+
category = "AAIT - TOOLBOX"
|
|
35
|
+
|
|
36
|
+
class Inputs:
|
|
37
|
+
data = Input("Data", Orange.data.Table)
|
|
38
|
+
|
|
39
|
+
class Outputs:
|
|
40
|
+
data = Output("Data", Orange.data.Table)
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.data = None
|
|
45
|
+
ui_path = os.path.join(os.path.dirname(__file__), "designer", "owexportmarkdown.ui")
|
|
46
|
+
uic.loadUi(ui_path, baseinstance=self)
|
|
47
|
+
|
|
48
|
+
# -------- helpers headers/footers --------
|
|
49
|
+
def ajouter_en_tete_pied_docx(self, file_path, header_text, footer_text):
|
|
50
|
+
try:
|
|
51
|
+
doc = Document(file_path)
|
|
52
|
+
section = doc.sections[0]
|
|
53
|
+
header = section.header
|
|
54
|
+
p = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
|
|
55
|
+
p.text = header_text
|
|
56
|
+
if p.runs:
|
|
57
|
+
p.runs[0].font.size = pt_docx(10)
|
|
58
|
+
footer = section.footer
|
|
59
|
+
p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
|
|
60
|
+
p.text = footer_text
|
|
61
|
+
if p.runs:
|
|
62
|
+
p.runs[0].font.size = pt_docx(10)
|
|
63
|
+
doc.save(file_path)
|
|
64
|
+
except Exception:
|
|
65
|
+
# en-tête/pied non bloquants
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def ajouter_entete_pied_pptx(self, file_path, entete_text, pied_text):
|
|
69
|
+
try:
|
|
70
|
+
prs = Presentation(file_path)
|
|
71
|
+
for slide in prs.slides:
|
|
72
|
+
entete = slide.shapes.add_textbox(Inches(0.3), Inches(0.2), Inches(8), Inches(0.5))
|
|
73
|
+
tf_entete = entete.text_frame
|
|
74
|
+
tf_entete.text = entete_text
|
|
75
|
+
tf_entete.paragraphs[0].font.size = Pt(12)
|
|
76
|
+
tf_entete.paragraphs[0].font.bold = True
|
|
77
|
+
|
|
78
|
+
pied = slide.shapes.add_textbox(Inches(0.3), Inches(6.3), Inches(8), Inches(0.5))
|
|
79
|
+
tf_pied = pied.text_frame
|
|
80
|
+
tf_pied.text = pied_text
|
|
81
|
+
tf_pied.paragraphs[0].font.size = Pt(10)
|
|
82
|
+
prs.save(file_path)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# -------------- input --------------
|
|
87
|
+
@Inputs.data
|
|
88
|
+
def set_data(self, in_data):
|
|
89
|
+
self.error("")
|
|
90
|
+
if in_data is None:
|
|
91
|
+
self.data = None
|
|
92
|
+
self.Outputs.data.send(None)
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# On exige au moins 'path'
|
|
96
|
+
if "path" not in in_data.domain:
|
|
97
|
+
self.error("La table d'entrée doit contenir au moins la colonne 'path'.")
|
|
98
|
+
self.Outputs.data.send(None)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Optionnellement 'content'
|
|
102
|
+
self.data = in_data
|
|
103
|
+
try:
|
|
104
|
+
table_out = self.export_all_rows()
|
|
105
|
+
self.Outputs.data.send(table_out)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
tb = traceback.format_exc()
|
|
108
|
+
QMessageBox.critical(self, "Erreur d'export", f"{e}\n\n{tb}")
|
|
109
|
+
self.Outputs.data.send(None)
|
|
110
|
+
|
|
111
|
+
# -------------- core --------------
|
|
112
|
+
def export_all_rows(self):
|
|
113
|
+
base_paths = self.data.get_column("path")
|
|
114
|
+
has_content = "content" in self.data.domain
|
|
115
|
+
file_contents = self.data.get_column("content") if has_content else [None] * len(base_paths)
|
|
116
|
+
|
|
117
|
+
pdf_paths, docx_paths, pptx_paths = [], [], []
|
|
118
|
+
|
|
119
|
+
for i, (md_text, base_path) in enumerate(zip(file_contents, base_paths)):
|
|
120
|
+
base_path = str(base_path or "").strip()
|
|
121
|
+
|
|
122
|
+
# Lecture du contenu si 'content' absent et path en .md
|
|
123
|
+
if not has_content:
|
|
124
|
+
if base_path.lower().endswith(".md"):
|
|
125
|
+
try:
|
|
126
|
+
with open(base_path, "r", encoding="utf-8") as f:
|
|
127
|
+
md_text = f.read()
|
|
128
|
+
# on remplace base_path par le même (on garde la base pour sorties)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.error(f"Impossible de lire le fichier : {base_path} ({e})")
|
|
131
|
+
pdf_paths.append("")
|
|
132
|
+
docx_paths.append("")
|
|
133
|
+
pptx_paths.append("")
|
|
134
|
+
continue
|
|
135
|
+
else:
|
|
136
|
+
# pas de content et path non .md -> rien à faire pour cette ligne
|
|
137
|
+
pdf_paths.append("")
|
|
138
|
+
docx_paths.append("")
|
|
139
|
+
pptx_paths.append("")
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
md_text = (str(md_text or "")).strip()
|
|
143
|
+
|
|
144
|
+
if not md_text or not base_path:
|
|
145
|
+
pdf_paths.append("")
|
|
146
|
+
docx_paths.append("")
|
|
147
|
+
pptx_paths.append("")
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Normaliser la base: enlever extension si présente
|
|
151
|
+
base_no_ext, _ = os.path.splitext(base_path)
|
|
152
|
+
# Créer dossier si nécessaire
|
|
153
|
+
out_dir = os.path.dirname(base_no_ext)
|
|
154
|
+
if out_dir and not os.path.isdir(out_dir):
|
|
155
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
156
|
+
|
|
157
|
+
docx_out = base_no_ext + ".docx"
|
|
158
|
+
pptx_out = base_no_ext + ".pptx"
|
|
159
|
+
pdf_out = base_no_ext + ".pdf"
|
|
160
|
+
|
|
161
|
+
# MD temporaire
|
|
162
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as tmp:
|
|
163
|
+
tmp.write(md_text)
|
|
164
|
+
tmp_md = tmp.name
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
# DOCX
|
|
168
|
+
pypandoc.convert_file(tmp_md, to="docx", outputfile=docx_out)
|
|
169
|
+
self.ajouter_en_tete_pied_docx(
|
|
170
|
+
docx_out,
|
|
171
|
+
"Rapport - Orange AI",
|
|
172
|
+
"Page générée automatiquement - Ne pas diffuser"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# PPTX
|
|
176
|
+
pypandoc.convert_file(tmp_md, to="pptx", outputfile=pptx_out)
|
|
177
|
+
self.ajouter_entete_pied_pptx(
|
|
178
|
+
pptx_out,
|
|
179
|
+
"Orange AI – Présentation",
|
|
180
|
+
"Page générée automatiquement"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# PDF (docx -> pdf) avec chemin de sortie exact
|
|
184
|
+
try:
|
|
185
|
+
convert(docx_out, pdf_out)
|
|
186
|
+
except Exception:
|
|
187
|
+
# fallback pandoc->pdf (si LaTeX dispo)
|
|
188
|
+
try:
|
|
189
|
+
pypandoc.convert_file(tmp_md, to="pdf", outputfile=pdf_out)
|
|
190
|
+
except Exception:
|
|
191
|
+
self.error(f"Échec conversion PDF pour la ligne {i+1}.")
|
|
192
|
+
pdf_out = ""
|
|
193
|
+
finally:
|
|
194
|
+
try:
|
|
195
|
+
os.remove(tmp_md)
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
pdf_paths.append(pdf_out if os.path.isfile(pdf_out) else "")
|
|
200
|
+
docx_paths.append(docx_out if os.path.isfile(docx_out) else "")
|
|
201
|
+
pptx_paths.append(pptx_out if os.path.isfile(pptx_out) else "")
|
|
202
|
+
|
|
203
|
+
# Ajouter colonnes sortie
|
|
204
|
+
table = self.data
|
|
205
|
+
table = table.add_column(StringVariable("output_pdf_path"), pdf_paths)
|
|
206
|
+
table = table.add_column(StringVariable("output_docx_path"), docx_paths)
|
|
207
|
+
table = table.add_column(StringVariable("output_pptx_path"), pptx_paths)
|
|
208
|
+
|
|
209
|
+
return table
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
app = QApplication(sys.argv)
|
|
214
|
+
w = OWExportMarkdown()
|
|
215
|
+
w.show()
|
|
216
|
+
sys.exit(app.exec() if hasattr(app, "exec") else app.exec_())
|