pygpt-net 2.4.39__py3-none-any.whl → 2.4.40__py3-none-any.whl

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.
Files changed (85) hide show
  1. CHANGELOG.md +13 -0
  2. README.md +19 -2
  3. pygpt_net/CHANGELOG.txt +13 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/controller/__init__.py +5 -3
  6. pygpt_net/controller/audio/__init__.py +9 -1
  7. pygpt_net/controller/chat/input.py +2 -1
  8. pygpt_net/controller/chat/render.py +2 -2
  9. pygpt_net/controller/ctx/__init__.py +2 -2
  10. pygpt_net/controller/debug/__init__.py +13 -2
  11. pygpt_net/controller/kernel/__init__.py +2 -1
  12. pygpt_net/controller/notepad.py +7 -6
  13. pygpt_net/controller/theme/nodes.py +2 -5
  14. pygpt_net/controller/tools/__init__.py +37 -1
  15. pygpt_net/controller/ui/__init__.py +1 -5
  16. pygpt_net/controller/ui/tabs.py +104 -12
  17. pygpt_net/core/command.py +3 -1
  18. pygpt_net/core/ctx/__init__.py +6 -2
  19. pygpt_net/core/ctx/container.py +5 -5
  20. pygpt_net/core/debug/tabs.py +3 -1
  21. pygpt_net/core/render/base.py +2 -2
  22. pygpt_net/core/render/web/body.py +1 -1
  23. pygpt_net/core/render/web/renderer.py +208 -38
  24. pygpt_net/core/tabs/__init__.py +104 -43
  25. pygpt_net/core/tabs/tab.py +4 -1
  26. pygpt_net/core/web.py +127 -1
  27. pygpt_net/data/config/config.json +4 -3
  28. pygpt_net/data/config/models.json +3 -3
  29. pygpt_net/data/config/modes.json +3 -3
  30. pygpt_net/data/css/web-blocks.css +18 -0
  31. pygpt_net/data/css/web-blocks.light.css +7 -0
  32. pygpt_net/data/css/web-chatgpt.css +8 -0
  33. pygpt_net/data/css/web-chatgpt_wide.css +8 -0
  34. pygpt_net/data/icons/split_screen.svg +1 -0
  35. pygpt_net/data/locale/locale.de.ini +1 -1
  36. pygpt_net/data/locale/locale.en.ini +4 -2
  37. pygpt_net/data/locale/locale.es.ini +1 -1
  38. pygpt_net/data/locale/locale.fr.ini +1 -1
  39. pygpt_net/data/locale/locale.it.ini +1 -1
  40. pygpt_net/data/locale/locale.pl.ini +2 -2
  41. pygpt_net/data/locale/locale.uk.ini +1 -1
  42. pygpt_net/data/locale/locale.zh.ini +1 -1
  43. pygpt_net/data/locale/plugin.cmd_web.de.ini +2 -0
  44. pygpt_net/data/locale/plugin.cmd_web.en.ini +20 -10
  45. pygpt_net/data/locale/plugin.cmd_web.es.ini +2 -0
  46. pygpt_net/data/locale/plugin.cmd_web.fr.ini +2 -0
  47. pygpt_net/data/locale/plugin.cmd_web.it.ini +2 -0
  48. pygpt_net/data/locale/plugin.cmd_web.pl.ini +2 -0
  49. pygpt_net/data/locale/plugin.cmd_web.uk.ini +2 -0
  50. pygpt_net/data/locale/plugin.cmd_web.zh.ini +2 -0
  51. pygpt_net/icons.qrc +1 -0
  52. pygpt_net/icons_rc.py +165 -136
  53. pygpt_net/item/ctx.py +46 -24
  54. pygpt_net/plugin/audio_output/__init__.py +4 -1
  55. pygpt_net/plugin/base/plugin.py +18 -4
  56. pygpt_net/plugin/cmd_code_interpreter/__init__.py +39 -37
  57. pygpt_net/plugin/cmd_code_interpreter/runner.py +25 -12
  58. pygpt_net/plugin/cmd_web/__init__.py +46 -6
  59. pygpt_net/plugin/cmd_web/config.py +74 -48
  60. pygpt_net/plugin/cmd_web/websearch.py +61 -28
  61. pygpt_net/plugin/cmd_web/worker.py +79 -13
  62. pygpt_net/provider/core/config/patch.py +22 -1
  63. pygpt_net/tools/__init__.py +9 -1
  64. pygpt_net/tools/base.py +15 -1
  65. pygpt_net/tools/code_interpreter/__init__.py +174 -75
  66. pygpt_net/tools/code_interpreter/ui/dialogs.py +21 -103
  67. pygpt_net/tools/code_interpreter/ui/widgets.py +284 -9
  68. pygpt_net/tools/html_canvas/__init__.py +78 -23
  69. pygpt_net/tools/html_canvas/ui/dialogs.py +46 -62
  70. pygpt_net/tools/html_canvas/ui/widgets.py +96 -3
  71. pygpt_net/ui/base/context_menu.py +2 -2
  72. pygpt_net/ui/layout/ctx/ctx_list.py +13 -4
  73. pygpt_net/ui/layout/toolbox/footer.py +1 -1
  74. pygpt_net/ui/main.py +2 -2
  75. pygpt_net/ui/menu/debug.py +11 -1
  76. pygpt_net/ui/widget/filesystem/explorer.py +2 -2
  77. pygpt_net/ui/widget/lists/context.py +26 -5
  78. pygpt_net/ui/widget/tabs/Input.py +2 -2
  79. pygpt_net/ui/widget/tabs/body.py +2 -1
  80. pygpt_net/ui/widget/tabs/output.py +126 -61
  81. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/METADATA +20 -3
  82. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/RECORD +85 -84
  83. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/LICENSE +0 -0
  84. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/WHEEL +0 -0
  85. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.09 03:00:00 #
9
+ # Updated Date: 2024.12.12 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import uuid
@@ -15,24 +15,17 @@ from datetime import datetime
15
15
  from PySide6.QtGui import QIcon
16
16
  from PySide6.QtWidgets import QVBoxLayout, QWidget, QLayout
17
17
 
18
- from .tab import Tab
19
18
  from pygpt_net.ui.widget.tabs.body import TabBody
20
19
  from pygpt_net.utils import trans
21
20
 
21
+ from .tab import Tab
22
+
22
23
 
23
24
  class Tabs:
24
25
 
25
26
  # number of columns
26
27
  NUM_COLS = 2
27
28
 
28
- # types
29
- TAB_ADD = -1
30
- TAB_CHAT = 0
31
- TAB_NOTEPAD = 1
32
- TAB_FILES = 2
33
- TAB_TOOL_PAINTER = 3
34
- TAB_TOOL_CALENDAR = 4
35
-
36
29
  def __init__(self, window=None):
37
30
  """
38
31
  Tabs core
@@ -43,18 +36,20 @@ class Tabs:
43
36
  self.last_pid = -1
44
37
  self.pids = {} # pid: Tab data
45
38
  self.icons = {
46
- self.TAB_CHAT: ":/icons/chat.svg",
47
- self.TAB_NOTEPAD: ":/icons/paste.svg",
48
- self.TAB_FILES: ":/icons/folder_filled.svg",
49
- self.TAB_TOOL_PAINTER: ":/icons/brush.svg",
50
- self.TAB_TOOL_CALENDAR: ":/icons/calendar.svg",
39
+ Tab.TAB_CHAT: ":/icons/chat.svg",
40
+ Tab.TAB_NOTEPAD: ":/icons/paste.svg",
41
+ Tab.TAB_FILES: ":/icons/folder_filled.svg",
42
+ Tab.TAB_TOOL_PAINTER: ":/icons/brush.svg",
43
+ Tab.TAB_TOOL_CALENDAR: ":/icons/calendar.svg",
44
+ Tab.TAB_TOOL: ":/icons/build.svg",
51
45
  }
52
46
  self.titles = {
53
- self.TAB_CHAT: "output.tab.chat",
54
- self.TAB_NOTEPAD: "output.tab.notepad",
55
- self.TAB_FILES: "output.tab.files",
56
- self.TAB_TOOL_PAINTER: "output.tab.painter",
57
- self.TAB_TOOL_CALENDAR: "output.tab.calendar",
47
+ Tab.TAB_CHAT: "output.tab.chat",
48
+ Tab.TAB_NOTEPAD: "output.tab.notepad",
49
+ Tab.TAB_FILES: "output.tab.files",
50
+ Tab.TAB_TOOL_PAINTER: "output.tab.painter",
51
+ Tab.TAB_TOOL_CALENDAR: "output.tab.calendar",
52
+ Tab.TAB_TOOL: "output.tab.tool",
58
53
  }
59
54
 
60
55
  def get_tab_by_index(self, idx: int, column_idx: int = 0) -> Tab or None:
@@ -113,7 +108,8 @@ class Tabs:
113
108
  title: str,
114
109
  icon=None,
115
110
  child=None,
116
- data_id=None
111
+ data_id=None,
112
+ tool_id=None
117
113
  ) -> Tab:
118
114
  """
119
115
  Add tab
@@ -123,6 +119,7 @@ class Tabs:
123
119
  :param icon: Tab icon
124
120
  :param child: Tab child
125
121
  :param data_id: Tab data ID
122
+ :param tool_id: Tool ID
126
123
  :return: Tab
127
124
  """
128
125
  self.last_pid += 1 # PID++, start from 0
@@ -135,6 +132,7 @@ class Tabs:
135
132
  tab.icon = icon
136
133
  tab.child = child
137
134
  tab.data_id = data_id
135
+ tab.tool_id = tool_id
138
136
 
139
137
  if type == Tab.TAB_CHAT:
140
138
  self.add_chat(tab)
@@ -146,6 +144,8 @@ class Tabs:
146
144
  self.add_tool_painter(tab)
147
145
  elif type == Tab.TAB_TOOL_CALENDAR:
148
146
  self.add_tool_calendar(tab)
147
+ elif type == Tab.TAB_TOOL:
148
+ self.add_tool(tab)
149
149
 
150
150
  self.pids[tab.pid] = tab
151
151
  return tab
@@ -153,6 +153,7 @@ class Tabs:
153
153
  def append(
154
154
  self,
155
155
  type: int,
156
+ tool_id: str,
156
157
  idx: int,
157
158
  column_idx: int = 0
158
159
  ) -> Tab:
@@ -160,6 +161,7 @@ class Tabs:
160
161
  Append tab to the right side of the tab with the specified index
161
162
 
162
163
  :param type: tab type
164
+ :param tool_id: tool ID
163
165
  :param idx: index of the tab to the right of which the new tab will be added
164
166
  :param column_idx: index of the column in which the tab will be added
165
167
  :return: Tab
@@ -167,9 +169,9 @@ class Tabs:
167
169
  self.last_pid += 1 # PID++, start from 0
168
170
  title = ""
169
171
  icon = self.icons[type]
170
- if type == self.TAB_CHAT:
172
+ if type == Tab.TAB_CHAT:
171
173
  title = trans('output.tab.chat') + " {}".format(self.count_by_type(type) + 1)
172
- elif type == self.TAB_NOTEPAD:
174
+ elif type == Tab.TAB_NOTEPAD:
173
175
  title = trans('output.tab.notepad') + " {}".format(self.count_by_type(type) + 1)
174
176
 
175
177
  tab = Tab()
@@ -180,11 +182,14 @@ class Tabs:
180
182
  tab.icon = icon
181
183
  tab.new_idx = idx + 1 # place on right side
182
184
  tab.column_idx = column_idx
185
+ tab.tool_id = tool_id
183
186
 
184
187
  if type == Tab.TAB_CHAT:
185
188
  self.add_chat(tab)
186
189
  elif type == Tab.TAB_NOTEPAD:
187
190
  self.add_notepad(tab)
191
+ elif type == Tab.TAB_TOOL:
192
+ self.add_tool(tab)
188
193
 
189
194
  self.pids[tab.pid] = tab
190
195
  self.update()
@@ -217,6 +222,9 @@ class Tabs:
217
222
  if 'column_idx' in data and data['column_idx'] is not None:
218
223
  tab.column_idx = data['column_idx']
219
224
 
225
+ if 'tool_id' in data and data['tool_id'] is not None:
226
+ tab.tool_id = data['tool_id']
227
+
220
228
  if tab.type in self.icons:
221
229
  tab.icon = self.icons[tab.type]
222
230
 
@@ -233,6 +241,8 @@ class Tabs:
233
241
  self.add_tool_painter(tab)
234
242
  elif tab.type == Tab.TAB_TOOL_CALENDAR: # calendar
235
243
  self.add_tool_calendar(tab)
244
+ elif tab.type == Tab.TAB_TOOL: # custom tools, id 100+
245
+ self.add_tool(tab)
236
246
 
237
247
  self.pids[tab.pid] = tab
238
248
  self.last_pid = self.get_max_pid()
@@ -279,7 +289,7 @@ class Tabs:
279
289
  for pid in list(self.pids):
280
290
  tab = self.pids[pid]
281
291
  if tab.type == type and tab.column_idx == column_idx:
282
- if type == self.TAB_CHAT:
292
+ if type == Tab.TAB_CHAT:
283
293
  if self.count_by_type(type) == 1:
284
294
  continue # do not remove last chat tab
285
295
  self.remove(pid)
@@ -341,6 +351,20 @@ class Tabs:
341
351
  count += 1
342
352
  return count
343
353
 
354
+ def get_max_data_id_by_type(self, type: int) -> int:
355
+ """
356
+ Get max data ID by type
357
+
358
+ :param type: Tab type
359
+ :return: Max data ID
360
+ """
361
+ max = 0
362
+ for pid in self.pids:
363
+ tab = self.pids[pid]
364
+ if tab.type == type and tab.data_id > max:
365
+ max = tab.data_id
366
+ return max
367
+
344
368
  def get_order_by_idx_and_type(
345
369
  self,
346
370
  idx: int,
@@ -430,8 +454,8 @@ class Tabs:
430
454
  tab.parent = tabs.get_column()
431
455
  if tab.data_id is not None:
432
456
  idx = tab.data_id # restore prev idx
433
- tab.child, idx = self.window.controller.notepad.create(idx)
434
- tab.data_id = idx # notepad idx in db, enumerated from 1
457
+ tab.child, idx, data_id = self.window.controller.notepad.create(idx)
458
+ tab.data_id = data_id # notepad idx in db, enumerated from 1
435
459
  if tab.new_idx is not None:
436
460
  tab.idx = tabs.insertTab(tab.new_idx, tab.child, tab.title)
437
461
  else:
@@ -489,6 +513,30 @@ class Tabs:
489
513
  if tab.tooltip is not None:
490
514
  tabs.setTabToolTip(tab.idx, tab.tooltip)
491
515
 
516
+ def add_tool(self, tab: Tab):
517
+ """
518
+ Add custom tool tab
519
+
520
+ :param tab: Tab instance
521
+ """
522
+ column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
523
+ tabs = column.get_tabs()
524
+ tool = self.window.tools.get(tab.tool_id)
525
+ if tool is None:
526
+ raise Exception("Tool not found: {}".format(tab.tool_id))
527
+ widget = tool.as_tab(tab)
528
+ if widget is None:
529
+ raise Exception("Tool widget not found: {}".format(tab.tool_id))
530
+ tab.icon = tool.tab_icon
531
+ tab.title = trans(tool.tab_title)
532
+ tab.parent = column
533
+ tab.child = self.from_widget(widget)
534
+ tab.idx = tabs.addTab(tab.child, tab.title)
535
+ tab.child.setOwner(tab)
536
+ tabs.setTabIcon(tab.idx, QIcon(tab.icon))
537
+ if tab.tooltip is not None:
538
+ tabs.setTabToolTip(tab.idx, tab.tooltip)
539
+
492
540
  def move_tab(self, tab: Tab, column_idx: int):
493
541
  """
494
542
  Move tab to column
@@ -500,16 +548,16 @@ class Tabs:
500
548
  return
501
549
  if tab.column_idx == column_idx:
502
550
  return
503
- tab.column_idx = column_idx
551
+
504
552
  old_column = self.window.ui.layout.get_column_by_idx(tab.column_idx)
505
553
  old_tabs = old_column.get_tabs()
506
554
  old_tabs.removeTab(tab.idx)
507
555
  new_column = self.window.ui.layout.get_column_by_idx(column_idx)
508
556
  new_tabs = new_column.get_tabs()
509
- tab.idx = new_tabs.addTab(tab.child, tab.title)
557
+ tab.idx = new_tabs.addTab(tab.child, QIcon(tab.icon), tab.title)
510
558
  tab.parent = new_column
559
+ tab.column_idx = column_idx
511
560
  self.update()
512
- self.window.core.tabs.reload_titles()
513
561
 
514
562
  def get_first_by_type(self, type: int) -> Tab or None:
515
563
  """
@@ -535,7 +583,7 @@ class Tabs:
535
583
  "uuid": uuid.uuid4(),
536
584
  "pid": 0,
537
585
  "idx": 0,
538
- "type": self.TAB_CHAT,
586
+ "type": Tab.TAB_CHAT,
539
587
  "data_id": None,
540
588
  "title": "Chat",
541
589
  "tooltip": "Chat",
@@ -545,31 +593,34 @@ class Tabs:
545
593
  "uuid": uuid.uuid4(),
546
594
  "pid": 1,
547
595
  "idx": 1,
548
- "type": self.TAB_FILES,
596
+ "type": Tab.TAB_FILES,
549
597
  "data_id": None,
550
598
  "title": "Files",
551
599
  "tooltip": "Files",
552
600
  "column_idx": 0,
601
+ "tool_id": "explorer",
553
602
  }
554
603
  data[2] = {
555
604
  "uuid": uuid.uuid4(),
556
605
  "pid": 2,
557
606
  "idx": 2,
558
- "type": self.TAB_TOOL_CALENDAR,
607
+ "type": Tab.TAB_TOOL_CALENDAR,
559
608
  "data_id": None,
560
609
  "title": "Calendar",
561
610
  "tooltip": "Calendar",
562
611
  "column_idx": 0,
612
+ "tool_id": "calendar",
563
613
  }
564
614
  data[3] = {
565
615
  "uuid": uuid.uuid4(),
566
616
  "pid": 3,
567
617
  "idx": 3,
568
- "type": self.TAB_TOOL_PAINTER,
618
+ "type": Tab.TAB_TOOL_PAINTER,
569
619
  "data_id": None,
570
620
  "title": "Painter",
571
621
  "tooltip": "Painter",
572
622
  "column_idx": 0,
623
+ "tool_id": "painter",
573
624
  }
574
625
  """
575
626
  data[4] = {
@@ -581,6 +632,7 @@ class Tabs:
581
632
  "title": "Notepad",
582
633
  "tooltip": "Notepad",
583
634
  "column_idx": 0,
635
+ "tool_id": "notepad",
584
636
  }
585
637
  """
586
638
  # load notepads from db
@@ -593,11 +645,12 @@ class Tabs:
593
645
  "uuid": uuid.uuid4(),
594
646
  "pid": next_idx,
595
647
  "idx": next_idx,
596
- "type": self.TAB_NOTEPAD,
648
+ "type": Tab.TAB_NOTEPAD,
597
649
  "data_id": item['data_id'],
598
650
  "title": item['title'],
599
651
  "tooltip": item['title'],
600
652
  "column_idx": 0,
653
+ "tool_id": "notepad",
601
654
  }
602
655
  next_idx += 1
603
656
  return data
@@ -628,6 +681,7 @@ class Tabs:
628
681
  "tooltip": trans(self.titles[type]),
629
682
  "custom_name": False,
630
683
  "column_idx": 0,
684
+ "tool_id": None,
631
685
  }
632
686
  tmp_pid += 1
633
687
 
@@ -657,6 +711,7 @@ class Tabs:
657
711
  "tooltip": tab.tooltip,
658
712
  "custom_name": tab.custom_name,
659
713
  "column_idx": tab.column_idx,
714
+ "tool_id": tab.tool_id,
660
715
  }
661
716
  opened_tabs = {}
662
717
  for column_idx in range(0, self.NUM_COLS):
@@ -694,28 +749,34 @@ class Tabs:
694
749
 
695
750
  def reload_titles(self):
696
751
  """Reload default tab titles"""
752
+ return
753
+ processed = []
697
754
  for column_idx in range(0, self.NUM_COLS):
698
755
  tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
699
756
  counters = {
700
- self.TAB_CHAT: 1,
701
- self.TAB_NOTEPAD: 1,
702
- self.TAB_FILES: 1,
703
- self.TAB_TOOL_PAINTER: 1,
704
- self.TAB_TOOL_CALENDAR: 1,
757
+ Tab.TAB_CHAT: 1,
758
+ Tab.TAB_NOTEPAD: 1,
759
+ Tab.TAB_FILES: 1,
760
+ Tab.TAB_TOOL_PAINTER: 1,
761
+ Tab.TAB_TOOL_CALENDAR: 1,
705
762
  }
706
763
  for pid in self.pids:
707
764
  tab = self.pids[pid]
708
- if tab.column_idx != column_idx:
765
+ if tab.pid in processed:
709
766
  continue
710
- if tab.custom_name:
711
- continue # leave custom names
767
+ if tab.custom_name or tab.type == Tab.TAB_TOOL:
768
+ continue # leave custom names and tools
712
769
  tab.title = trans(self.titles[tab.type])
713
770
  num_tabs = self.count_by_type(tab.type)
714
771
  if num_tabs > 1:
715
- tab.title += " {}".format(counters[tab.type])
772
+ if tab.type in counters:
773
+ tab.title += " {}".format(counters[tab.type])
774
+ else:
775
+ counters[tab.type] = 1
716
776
  counters[tab.type] += 1
717
777
  tabs.setTabText(tab.idx, tab.title)
718
778
  tabs.setTabToolTip(tab.idx, tab.title)
779
+ processed.append(pid)
719
780
 
720
781
  def from_widget(self, widget: QWidget) -> TabBody:
721
782
  """
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.09 00:00:00 #
9
+ # Updated Date: 2024.12.09 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from datetime import datetime
@@ -21,6 +21,7 @@ class Tab:
21
21
  TAB_FILES = 2
22
22
  TAB_TOOL_PAINTER = 3
23
23
  TAB_TOOL_CALENDAR = 4
24
+ TAB_TOOL = 100
24
25
 
25
26
  def __init__(self):
26
27
  """
@@ -39,6 +40,7 @@ class Tab:
39
40
  self.child = None
40
41
  self.parent = None
41
42
  self.column_idx = 0
43
+ self.tool_id = None
42
44
 
43
45
  dt = datetime.now()
44
46
  self.created_at = dt
@@ -66,4 +68,5 @@ class Tab:
66
68
  "created_at": str(self.created_at),
67
69
  "updated_at": str(self.updated_at),
68
70
  "column_idx": self.column_idx,
71
+ "tool_id": self.tool_id,
69
72
  }
pygpt_net/core/web.py CHANGED
@@ -6,8 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.02.24 00:00:00 #
9
+ # Updated Date: 2024.12.13 00:00:00 #
10
10
  # ================================================== #
11
+ import os
12
+ import uuid
13
+
14
+ import requests
15
+
16
+ from bs4 import BeautifulSoup
17
+ from urllib.parse import urljoin
11
18
 
12
19
  from pygpt_net.provider.web.base import BaseProvider
13
20
 
@@ -81,3 +88,122 @@ class Web:
81
88
  for t in type:
82
89
  if t in self.providers:
83
90
  self.providers[t][id] = provider
91
+
92
+ def get_main_image(self, url: str) -> str or None:
93
+ """
94
+ Get main image from URL
95
+
96
+ :param url: URL to get image from
97
+ :return: image URL
98
+ """
99
+ response = requests.get(url)
100
+ soup = BeautifulSoup(response.content, 'html.parser')
101
+
102
+ og_image = soup.find('meta', property='og:image')
103
+ if og_image and og_image.get('content'):
104
+ return og_image['content']
105
+
106
+ twitter_image = soup.find('meta', attrs={'name': 'twitter:image'})
107
+ if twitter_image and twitter_image.get('content'):
108
+ return twitter_image['content']
109
+
110
+ link_image = soup.find('link', rel='image_src')
111
+ if link_image and link_image.get('href'):
112
+ return link_image['href']
113
+
114
+ images = soup.find_all('img')
115
+ if images:
116
+ images = [img for img in images if 'logo' not in (img.get('src') or '').lower()]
117
+ largest_image = None
118
+ max_area = 0
119
+ for img in images:
120
+ src = img.get('src')
121
+ if not src:
122
+ continue
123
+ src = requests.compat.urljoin(url, src)
124
+ try:
125
+ img_response = requests.get(src, stream=True, timeout=5)
126
+ img_response.raw.decode_content = True
127
+
128
+ from PIL import Image
129
+ image = Image.open(img_response.raw)
130
+ width, height = image.size
131
+ area = width * height
132
+ if area > max_area:
133
+ max_area = area
134
+ largest_image = src
135
+ except:
136
+ continue
137
+ if largest_image:
138
+ return largest_image
139
+ return None
140
+
141
+ def get_links(self, url: str) -> list:
142
+ """
143
+ Get links from URL
144
+
145
+ :param url: URL to get links from
146
+ :return: links list
147
+ """
148
+ response = requests.get(url)
149
+ soup = BeautifulSoup(response.content, 'html.parser')
150
+ links = []
151
+ urls = []
152
+ for link in soup.find_all('a'):
153
+ try:
154
+ name = link.get_text(strip=True)
155
+ address = link.get('href')
156
+ if address:
157
+ address = urljoin(url, address)
158
+ if not name:
159
+ title = link.get('title')
160
+ if title:
161
+ name = title
162
+ else:
163
+ name = address
164
+ if address not in urls:
165
+ urls.append(address)
166
+ links.append({name: address})
167
+ except:
168
+ continue
169
+ return links
170
+
171
+
172
+ def get_images(self, url: str) -> list:
173
+ """
174
+ Get images from URL
175
+
176
+ :param url: URL to get images from
177
+ :return: images list
178
+ """
179
+ response = requests.get(url)
180
+ soup = BeautifulSoup(response.content, 'html.parser')
181
+ images = []
182
+ for img in soup.find_all('img'):
183
+ try:
184
+ address = img.get('src')
185
+ if address:
186
+ address = urljoin(url, address)
187
+ if address not in images:
188
+ images.append(address)
189
+ except:
190
+ continue
191
+ return images
192
+
193
+ def download_image(self, img: str) -> str:
194
+ """
195
+ Download image from URL
196
+
197
+ :param img: URL to download image from
198
+ :return: local path to image
199
+ """
200
+ dir = self.window.core.config.get_user_dir("img")
201
+ response = requests.get(img, stream=True)
202
+ name = img.replace("http://", "").replace("https://", "").replace("/", "_")
203
+ path = os.path.join(dir, name)
204
+ if os.path.exists(path):
205
+ name = name + uuid.uuid4().hex[:6].upper()
206
+ download_path = os.path.join(dir, name)
207
+ with open(download_path, 'wb', ) as f:
208
+ f.write(response.content)
209
+ return self.window.core.filesystem.make_local(download_path)
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.39",
4
- "app.version": "2.4.39",
5
- "updated_at": "2024-12-09T00:00:00"
3
+ "version": "2.4.40",
4
+ "app.version": "2.4.40",
5
+ "updated_at": "2024-12-12T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -134,6 +134,7 @@
134
134
  "expert": ""
135
135
  },
136
136
  "debug": false,
137
+ "debug.render": false,
137
138
  "download.dir": "download",
138
139
  "experts.mode": "chat",
139
140
  "font_size": 12,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.39",
4
- "app.version": "2.4.39",
5
- "updated_at": "2024-12-09T00:00:00"
3
+ "version": "2.4.40",
4
+ "app.version": "2.4.40",
5
+ "updated_at": "2024-12-12T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "claude-3-5-sonnet-20240620": {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.39",
4
- "app.version": "2.4.39",
5
- "updated_at": "2024-12-09T00:00:00"
3
+ "version": "2.4.40",
4
+ "app.version": "2.4.40",
5
+ "updated_at": "2024-12-12T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "chat": {
@@ -54,6 +54,7 @@ p {{
54
54
  font-family: 'Lato';
55
55
  text-decoration: none;
56
56
  padding-bottom: 0px;
57
+ font-size: 0.9rem;
57
58
  }}
58
59
 
59
60
  /* lists, code */
@@ -89,6 +90,7 @@ code {{
89
90
  .code-header-lang {{
90
91
  float: left;
91
92
  padding-left: .25rem;
93
+ padding-top: 4px;
92
94
  }}
93
95
  .code-header-copy {{
94
96
  text-align: right;
@@ -102,6 +104,13 @@ code {{
102
104
  vertical-align: middle;
103
105
  padding-right: 5px;
104
106
  }}
107
+ .code-header-copy:hover {{
108
+ text-decoration: none;
109
+ cursor: pointer;
110
+ }}
111
+ .code-header-copy:hover img {{
112
+ filter: brightness(130%);
113
+ }}
105
114
  .code-wrapper {{
106
115
  padding: 0px;
107
116
  margin: 0px;
@@ -209,6 +218,7 @@ code {{
209
218
  .tool-output .content {{
210
219
  padding: 10px;
211
220
  color: gray !important;
221
+ font-size: 0.9rem;
212
222
  }}
213
223
  .tool-output .toggle-cmd-output {{
214
224
  cursor: pointer;
@@ -318,4 +328,12 @@ code {{
318
328
  to {{
319
329
  transform: rotate(360deg);
320
330
  }}
331
+ }}
332
+
333
+ /* debug */
334
+ .debug {{
335
+ color: orange !important;
336
+ background: #0d1117;
337
+ padding: 10px;
338
+ font-size: 0.9rem;
321
339
  }}
@@ -72,4 +72,11 @@ code {{
72
72
  }}
73
73
  .display-blocks .name-user::before {{
74
74
  background-color: silver;
75
+ }}
76
+ .msg-extra a {{
77
+ color: #4d4d4d !important;
78
+ }}
79
+ .msg-extra a:hover {{
80
+ text-decoration: none;
81
+ color: #000 !important;
75
82
  }}
@@ -339,4 +339,12 @@ code {{
339
339
  to {{
340
340
  transform: rotate(360deg);
341
341
  }}
342
+ }}
343
+
344
+ /* debug */
345
+ .debug {{
346
+ color: orange !important;
347
+ background: #0d1117;
348
+ padding: 10px;
349
+ font-size: 0.9rem;
342
350
  }}
@@ -339,4 +339,12 @@ code {{
339
339
  to {{
340
340
  transform: rotate(360deg);
341
341
  }}
342
+ }}
343
+
344
+ /* debug */
345
+ .debug {{
346
+ color: orange !important;
347
+ background: #0d1117;
348
+ padding: 10px;
349
+ font-size: 0.9rem;
342
350
  }}
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="M520-320h200v-320H520v320Zm-280 0h200v-320H240v320Zm-80 160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm640-560H160v480h640v-480Zm-640 0v480-480Z"/></svg>