gaggle 0.2.2
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +167 -0
- data/Rakefile +8 -0
- data/app/assets/config/gaggle_manifest.js +4 -0
- data/app/assets/stylesheets/application.css +1 -0
- data/app/assets/stylesheets/gaggle/tailwind.css +1740 -0
- data/app/controllers/gaggle/application_controller.rb +23 -0
- data/app/controllers/gaggle/channels/messages_controller.rb +27 -0
- data/app/controllers/gaggle/channels_controller.rb +84 -0
- data/app/controllers/gaggle/gooses/sessions_controller.rb +23 -0
- data/app/controllers/gaggle/gooses_controller.rb +47 -0
- data/app/controllers/gaggle/messages_controller.rb +7 -0
- data/app/controllers/gaggle/overviews_controller.rb +5 -0
- data/app/controllers/gaggle/sessions_controller.rb +27 -0
- data/app/helpers/gaggle/application_helper.rb +4 -0
- data/app/javascript/controllers/gaggle/application.js +9 -0
- data/app/javascript/controllers/gaggle/chat_field_form_controller.js +7 -0
- data/app/javascript/controllers/gaggle/index.js +4 -0
- data/app/javascript/controllers/gaggle/local_timestamp_controller.js +21 -0
- data/app/javascript/controllers/gaggle/text_area_auto_expand_controller.js +27 -0
- data/app/javascript/controllers/gaggle/transition_controller.js +102 -0
- data/app/javascript/gaggle/application.js +3 -0
- data/app/javascript/gaggle/el-transition.js +64 -0
- data/app/jobs/gaggle/application_job.rb +4 -0
- data/app/mailers/gaggle/application_mailer.rb +6 -0
- data/app/models/gaggle/application_record.rb +8 -0
- data/app/models/gaggle/channel.rb +45 -0
- data/app/models/gaggle/channel_goose.rb +11 -0
- data/app/models/gaggle/current.rb +5 -0
- data/app/models/gaggle/goose/personality_defaults.rb +87 -0
- data/app/models/gaggle/goose.rb +94 -0
- data/app/models/gaggle/message.rb +61 -0
- data/app/models/gaggle/notification.rb +47 -0
- data/app/models/gaggle/session.rb +138 -0
- data/app/views/gaggle/application/_logo.html.erb +125 -0
- data/app/views/gaggle/application/_mobile_header.html.erb +15 -0
- data/app/views/gaggle/application/_sidebar.html.erb +259 -0
- data/app/views/gaggle/channels/_channel.html.erb +22 -0
- data/app/views/gaggle/channels/_form.html.erb +70 -0
- data/app/views/gaggle/channels/edit.html.erb +10 -0
- data/app/views/gaggle/channels/gooses/index.html.erb +12 -0
- data/app/views/gaggle/channels/index.json.jbuilder +3 -0
- data/app/views/gaggle/channels/messages/new.html.erb +2 -0
- data/app/views/gaggle/channels/new.html.erb +8 -0
- data/app/views/gaggle/channels/show.html.erb +98 -0
- data/app/views/gaggle/channels/show.json.jbuilder +5 -0
- data/app/views/gaggle/channels/update.turbo_stream.erb +1 -0
- data/app/views/gaggle/gooses/_form.html.erb +65 -0
- data/app/views/gaggle/gooses/edit.html.erb +2 -0
- data/app/views/gaggle/gooses/index.json.jbuilder +5 -0
- data/app/views/gaggle/gooses/new.html.erb +2 -0
- data/app/views/gaggle/gooses/sessions/index.html.erb +55 -0
- data/app/views/gaggle/gooses/show.html.erb +41 -0
- data/app/views/gaggle/messages/_message.html.erb +30 -0
- data/app/views/gaggle/overviews/show.html.erb +17 -0
- data/app/views/gaggle/sessions/show.html.erb +76 -0
- data/app/views/layouts/gaggle/application.html.erb +19 -0
- data/config/importmap.rb +7 -0
- data/config/routes.rb +19 -0
- data/config/utility_classes.yml +22 -0
- data/db/gaggle_migrate/20250214180303_create_gaggle_tables.rb +53 -0
- data/db/gaggle_migrate/20250220002655_add_delivered_at_to_notification.rb +5 -0
- data/db/gaggle_migrate/20250220004428_create_join_table_gaggle_channel_gaggle_goose.rb +8 -0
- data/lib/gaggle/engine.rb +48 -0
- data/lib/gaggle/version.rb +3 -0
- data/lib/gaggle.rb +8 -0
- data/lib/generators/gaggle/install/install_generator.rb +7 -0
- data/lib/generators/gaggle/install/templates/db/gaggle_schema.rb +59 -0
- data/lib/generators/gaggle/update/update_generator.rb +16 -0
- metadata +269 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module Gaggle
|
2
|
+
class Goose < ApplicationRecord
|
3
|
+
SERVER_URL = "http://localhost:60053"
|
4
|
+
self.table_name = "gaggle_gooses"
|
5
|
+
include Goose::PersonalityDefaults
|
6
|
+
|
7
|
+
default_scope { order(created_at: :asc) }
|
8
|
+
|
9
|
+
has_many :messages, dependent: :destroy
|
10
|
+
has_many :private_messages, class_name: "Gaggle::Message", as: :messageable, dependent: :destroy
|
11
|
+
has_many :sessions, dependent: :destroy
|
12
|
+
has_many :notifications, dependent: :destroy
|
13
|
+
has_many :recipient_notifications, class_name: "Gaggle::Notification", as: :messageable
|
14
|
+
has_and_belongs_to_many :channels
|
15
|
+
|
16
|
+
validates :name, presence: true, uniqueness: true
|
17
|
+
|
18
|
+
def private_channel(goose)
|
19
|
+
Gaggle::Message.where(messageable: self, goose:).or(Gaggle::Message.where(messageable: goose, goose: self))
|
20
|
+
end
|
21
|
+
|
22
|
+
def interaction_prompt
|
23
|
+
<<~TEXT
|
24
|
+
In addition to your primary role, you must communicate with other AI assistants and the gaggle MCP tools.
|
25
|
+
Do not forget this: Your unique goose_id is #{id}. This id will let you know which messages are yours vs others.
|
26
|
+
Do not forget your name is #{name}.
|
27
|
+
Remember: The Human can only communicate through messages, so no need to put a thoughts in the console.
|
28
|
+
|
29
|
+
Gaggle is a chat board style interface with channels to communicate and messages to send.
|
30
|
+
|
31
|
+
Here is the flow for working in the system
|
32
|
+
|
33
|
+
```mermaid
|
34
|
+
graph TD
|
35
|
+
A[Start] --> B[Check Existing Channels]
|
36
|
+
B --> C{Anyone Needs a Response?}
|
37
|
+
C -->|Yes| D[Respond to Relevant Messages]
|
38
|
+
C -->|No| E[Perform Tasks Needing Resumption]
|
39
|
+
D --> E
|
40
|
+
E --> F[Check for Notifications]
|
41
|
+
F -->|Yes| G[Check Channel for Notification]
|
42
|
+
G --> H{Message Relevant?}
|
43
|
+
H -->|Yes| I{Need to Respond?}
|
44
|
+
I -->|Yes| J[Respond to Notification]
|
45
|
+
I -->|No| K[End]
|
46
|
+
H -->|No| K
|
47
|
+
F -->|No| K
|
48
|
+
K[End - Wait for Next Notification]
|
49
|
+
```
|
50
|
+
|
51
|
+
Here is how to manage bugs:
|
52
|
+
|
53
|
+
```mermaid
|
54
|
+
graph TD
|
55
|
+
A[Start] --> B{Hit a Bug or Issue?}
|
56
|
+
B -->|Yes| C[Choose Action]
|
57
|
+
C --> D{Ask in Relevant Channel}
|
58
|
+
C --> E{Create Relevant Channel}
|
59
|
+
D --> F[Post Bug/Issue Details]
|
60
|
+
E --> F
|
61
|
+
F --> G[Wait for Human Response]
|
62
|
+
B -->|No| H{Want to Delete Channel?}
|
63
|
+
H -->|Yes| I[Confirm Deletion in Channel]
|
64
|
+
I --> J[Wait for Human Confirmation]
|
65
|
+
J -->|Confirmed| K[Delete Channel]
|
66
|
+
J -->|Not Confirmed| L[Keep Channel]
|
67
|
+
H -->|No| M[Continue Monitoring]
|
68
|
+
G --> M
|
69
|
+
K --> M
|
70
|
+
L --> M
|
71
|
+
```
|
72
|
+
|
73
|
+
DO NOT FORGET: Whenever you are trying to interact with the system, use the gaggle MCP tools.
|
74
|
+
TEXT
|
75
|
+
end
|
76
|
+
|
77
|
+
def begin_prompt
|
78
|
+
<<~TEXT
|
79
|
+
Begin by reading the available channels and responding where necessary
|
80
|
+
TEXT
|
81
|
+
end
|
82
|
+
|
83
|
+
def notify_of_message(notification:)
|
84
|
+
Rails.logger.info "Notifying goose of message: #{notification.message_id}"
|
85
|
+
if session = sessions.running.first
|
86
|
+
session.write_to_executable("Automated: You have a new message in #{notification.messageable_type}: #{notification.messageable_id}. Please respond")
|
87
|
+
true
|
88
|
+
else
|
89
|
+
Rails.logger.error "No running sessions found for goose: #{id}"
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Gaggle
|
2
|
+
class Message < ApplicationRecord
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
self.table_name = "gaggle_messages"
|
6
|
+
|
7
|
+
belongs_to :messageable, polymorphic: true
|
8
|
+
belongs_to :goose, class_name: "Gaggle::Goose", optional: true, default: -> { Current.goose_user }
|
9
|
+
|
10
|
+
has_many :notifications, class_name: "Gaggle::Notification", dependent: :destroy
|
11
|
+
|
12
|
+
validates :content, presence: true
|
13
|
+
|
14
|
+
after_create_commit :generate_notifications
|
15
|
+
after_create_commit :broadcast_create
|
16
|
+
after_create_commit :associate_goose, if: -> { goose_id.present? && messageable_type == "Gaggle::Channel" }
|
17
|
+
|
18
|
+
scope :later_than, ->(time = 0) { where(created_at: time..) }
|
19
|
+
|
20
|
+
def user_name
|
21
|
+
goose&.name || "Human"
|
22
|
+
end
|
23
|
+
|
24
|
+
def markdown_content
|
25
|
+
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
|
26
|
+
markdown.render(content)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def generate_notifications
|
32
|
+
goose_to_notify = case messageable
|
33
|
+
when Gaggle::Channel
|
34
|
+
messageable.gooses.where.not(id: goose_id).to_a
|
35
|
+
when Gaggle::Goose
|
36
|
+
Array.wrap(messageable)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find @mentions in the message content.
|
40
|
+
# This regex captures sequences starting with '@' followed by word characters.
|
41
|
+
content.scan(/@([\w]+)/).flatten.each do |mentioned_name|
|
42
|
+
goose_mentioned = Gaggle::Goose.find_by(name: mentioned_name)
|
43
|
+
goose_to_notify << goose_mentioned if goose_mentioned
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create notifications for each unique goose.
|
47
|
+
goose_to_notify.each do |goose|
|
48
|
+
notification = Gaggle::Notification.create(message: self, goose:, messageable:)
|
49
|
+
notification.save || notification.notify_goose
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def broadcast_create
|
54
|
+
broadcast_prepend_to messageable, target: "messages"
|
55
|
+
end
|
56
|
+
|
57
|
+
def associate_goose
|
58
|
+
goose.channels << self.messageable
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gaggle
|
2
|
+
class Notification < ApplicationRecord
|
3
|
+
self.table_name = "gaggle_notifications"
|
4
|
+
|
5
|
+
belongs_to :message, class_name: "Gaggle::Message"
|
6
|
+
belongs_to :messageable, polymorphic: true
|
7
|
+
belongs_to :goose, class_name: "Gaggle::Goose", optional: true
|
8
|
+
|
9
|
+
validates :read_at, uniqueness: { scope: [ :messageable_id, :messageable_type, :goose_id ] }
|
10
|
+
|
11
|
+
after_create_commit :notify_goose, if: -> { goose.present? }
|
12
|
+
|
13
|
+
def notify_goose
|
14
|
+
update(delivered_at: Time.current)
|
15
|
+
goose.notify_of_message(notification: self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def mark_read!
|
19
|
+
update(read_at: Time.current)
|
20
|
+
end
|
21
|
+
|
22
|
+
def unread_messageable
|
23
|
+
messages = case messageable
|
24
|
+
when Gaggle::Channel
|
25
|
+
messageable.messages.where(created_at: created_at..)
|
26
|
+
when Gaggle::Goose
|
27
|
+
messageable.messages.where(created_at: created_at..)
|
28
|
+
end
|
29
|
+
mark_read!
|
30
|
+
messages
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def messageables
|
35
|
+
preload(:messageable).map(&:messageable)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unread
|
39
|
+
where(read_at: nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def for_messageable(messageable)
|
43
|
+
where(messageable_id: messageable.id, messageable_type: messageable.class.name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "pty"
|
2
|
+
|
3
|
+
module Gaggle
|
4
|
+
class Session < ApplicationRecord
|
5
|
+
class << self; attr_reader :running_executables; end
|
6
|
+
@running_executables = {}
|
7
|
+
|
8
|
+
SUBMISSION_SIGNAL = "\r\n".freeze
|
9
|
+
|
10
|
+
belongs_to :goose
|
11
|
+
attribute :log_file, :string, default: ""
|
12
|
+
|
13
|
+
scope :running, -> { all.select(&:running?) }
|
14
|
+
|
15
|
+
def start_executable
|
16
|
+
prepare_logging
|
17
|
+
logger = Logger.new(log_path)
|
18
|
+
Rails.logger.info "Starting goose session for: #{goose.name}"
|
19
|
+
|
20
|
+
self.class.running_executables[to_global_id] = Thread.new(logger) do |session_logger|
|
21
|
+
Thread.current.name = "gaggle-session-#{to_global_id}"
|
22
|
+
Thread.current[:input_queue] = input_queue = Queue.new
|
23
|
+
prompt_mutex = Mutex.new
|
24
|
+
prompt_cond = ConditionVariable.new
|
25
|
+
prompt_active = false
|
26
|
+
server_name = ::MCP::Rails.configuration.for_engine(Gaggle::Engine).server_name
|
27
|
+
server_path = ::MCP::Rails.configuration.output_directory.join(server_name)
|
28
|
+
|
29
|
+
|
30
|
+
Thread.current[:logger] = session_logger
|
31
|
+
|
32
|
+
PTY.spawn("goose session --with-extension \"GOOSE_USER_ID=#{goose.id} #{server_path}_server.rb\"") do |stdout, stdin, pid|
|
33
|
+
session_logger.info "Spawned PID: #{pid}"
|
34
|
+
stdout.sync = true
|
35
|
+
stdin.sync = true
|
36
|
+
|
37
|
+
input_thread = Thread.new(input_queue, stdin, prompt_mutex, prompt_cond, name: "input-#{to_global_id}") do |queue, input_stream, mutex, cond|
|
38
|
+
loop do
|
39
|
+
mutex.synchronize do
|
40
|
+
cond.wait(mutex)
|
41
|
+
next if queue.empty?
|
42
|
+
begin
|
43
|
+
command = queue.pop
|
44
|
+
cmd_to_send = command.is_a?(String) ? "#{command}#{SUBMISSION_SIGNAL}" : command&.to_s || SUBMISSION_SIGNAL
|
45
|
+
session_logger.info "Sent command: #{command.inspect}"
|
46
|
+
input_stream.write(cmd_to_send)
|
47
|
+
input_stream.flush
|
48
|
+
prompt_active = false # Reset after sending
|
49
|
+
rescue StandardError => e
|
50
|
+
session_logger.error "Input thread error: #{e.class} - #{e.message}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
loop do
|
58
|
+
ready = IO.select([ stdout ], nil, nil, 2)
|
59
|
+
if ready
|
60
|
+
line = nil
|
61
|
+
begin
|
62
|
+
Timeout.timeout(1) { line = stdout.gets }
|
63
|
+
rescue Timeout::Error
|
64
|
+
alive = Process.waitpid(pid, Process::WNOHANG).nil?
|
65
|
+
break unless alive
|
66
|
+
next
|
67
|
+
end
|
68
|
+
if line
|
69
|
+
prompt_active = false
|
70
|
+
session_logger.info "Output: #{line.chomp}"
|
71
|
+
broadcast_output(line)
|
72
|
+
else
|
73
|
+
break
|
74
|
+
end
|
75
|
+
else
|
76
|
+
prompt_mutex.synchronize do
|
77
|
+
unless prompt_active || input_queue.empty?
|
78
|
+
prompt_active = true
|
79
|
+
prompt_cond.signal
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
rescue Errno::EIO, Errno::EPIPE, IOError => e
|
85
|
+
session_logger.error "Stream error: #{e.class} - #{e.message}"
|
86
|
+
rescue StandardError => e
|
87
|
+
session_logger.error "Unexpected error: #{e.class} - #{e.message}"
|
88
|
+
ensure
|
89
|
+
stdout.close unless stdout.closed?
|
90
|
+
stdin.close unless stdin.closed?
|
91
|
+
input_thread.kill
|
92
|
+
self.class.running_executables.delete(to_global_id)
|
93
|
+
session_logger.info "Session thread exiting, PID: #{pid rescue 'unknown'}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_to_executable(input)
|
100
|
+
if (thread = executable)
|
101
|
+
thread[:logger].info "Received input: #{input.inspect}"
|
102
|
+
broadcast_output(input)
|
103
|
+
thread[:input_queue].push(input)
|
104
|
+
else
|
105
|
+
Rails.logger.error "No running session found for: #{to_global_id}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop_executable
|
110
|
+
executable&.exit if running?
|
111
|
+
end
|
112
|
+
|
113
|
+
def running?
|
114
|
+
executable&.alive?
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def prepare_logging
|
120
|
+
self.log_file = "log/gaggle/goose_#{goose.id}/#{Time.now.iso8601}.log"
|
121
|
+
FileUtils.mkdir_p(File.dirname(log_path))
|
122
|
+
FileUtils.touch(log_path)
|
123
|
+
save!
|
124
|
+
end
|
125
|
+
|
126
|
+
def log_path
|
127
|
+
Rails.root.join(log_file)
|
128
|
+
end
|
129
|
+
|
130
|
+
def broadcast_output(content)
|
131
|
+
broadcast_append target: dom_id(self, :code), content: Strings::ANSI.sanitize(content)
|
132
|
+
end
|
133
|
+
|
134
|
+
def executable
|
135
|
+
self.class.running_executables[to_global_id]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
<%# locals: (classes: 'h-10 w-auto text-white') %>
|
2
|
+
<svg version="1.0" fill="currentColor" stroke="currentColor" xmlns="http://www.w3.org/2000/svg" class="<%= classes %>" viewBox="0 0 1109.000000 1280.000000" preserveAspectRatio="xMidYMid meet">
|
3
|
+
<metadata>
|
4
|
+
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
5
|
+
</metadata>
|
6
|
+
<g transform="translate(0.000000,1280.000000) scale(0.100000,-0.100000)"
|
7
|
+
fill="currentColor" stroke="none">
|
8
|
+
<path d="M3949 12771 c-59 -16 -120 -37 -135 -48 -16 -11 -65 -39 -109 -62
|
9
|
+
-113 -59 -169 -103 -211 -166 -20 -30 -43 -55 -50 -55 -8 0 -14 -7 -14 -15 0
|
10
|
+
-8 -8 -15 -18 -15 -22 0 -42 -19 -42 -40 0 -9 -25 -40 -55 -70 -30 -30 -55
|
11
|
+
-63 -55 -73 0 -10 -5 -28 -11 -40 -18 -35 -92 -107 -110 -107 -9 0 -22 -7 -29
|
12
|
+
-15 -7 -8 -20 -15 -29 -15 -10 0 -28 -11 -41 -25 -13 -14 -30 -25 -38 -25 -8
|
13
|
+
0 -28 -11 -44 -26 -17 -14 -44 -28 -60 -31 -15 -3 -31 -10 -34 -14 -3 -5 -13
|
14
|
+
-9 -23 -9 -10 0 -24 -6 -30 -14 -6 -7 -24 -16 -40 -19 -15 -3 -42 -17 -59 -31
|
15
|
+
-16 -15 -37 -26 -46 -26 -9 0 -16 -8 -16 -17 0 -9 -3 -13 -7 -10 -9 9 -43 -22
|
16
|
+
-43 -40 0 -7 -7 -13 -15 -13 -12 0 -15 -13 -15 -60 l0 -60 33 0 c17 0 185 7
|
17
|
+
372 15 187 8 390 15 451 15 100 0 114 -2 140 -22 45 -36 92 -50 184 -57 47 -3
|
18
|
+
95 -12 107 -19 12 -6 44 -12 72 -12 49 0 80 -14 151 -68 14 -11 40 -27 57 -36
|
19
|
+
18 -9 42 -33 53 -54 11 -20 24 -44 29 -52 22 -38 41 -93 41 -119 0 -16 7 -34
|
20
|
+
15 -41 12 -10 15 -38 15 -136 l0 -124 -34 0 c-19 0 -47 -10 -67 -25 -19 -13
|
21
|
+
-63 -40 -99 -59 -102 -54 -250 -220 -250 -282 0 -13 -5 -24 -12 -24 -15 0 -68
|
22
|
+
-50 -68 -65 0 -6 -13 -24 -30 -40 -16 -16 -30 -37 -30 -47 0 -27 -24 -69 -54
|
23
|
+
-97 -14 -14 -26 -31 -26 -38 0 -28 -129 -182 -153 -182 -4 0 -17 -7 -30 -16
|
24
|
+
-58 -41 -99 -65 -111 -65 -7 0 -23 -11 -36 -25 -13 -14 -31 -25 -40 -25 -9 0
|
25
|
+
-36 -20 -60 -45 -24 -25 -48 -45 -54 -45 -5 0 -18 -8 -28 -17 -19 -17 -19 -15
|
26
|
+
-13 62 3 44 13 121 22 171 16 91 16 94 -10 238 -63 342 -261 696 -442 788 -33
|
27
|
+
17 -78 43 -100 58 -22 15 -62 35 -88 44 -26 9 -71 28 -100 43 -29 14 -101 39
|
28
|
+
-162 54 -101 26 -125 29 -290 29 -165 0 -189 -3 -290 -29 -130 -33 -333 -130
|
29
|
+
-381 -183 -56 -60 -121 -153 -117 -165 3 -7 -3 -13 -13 -13 -23 0 -74 -49 -74
|
30
|
+
-70 0 -8 -25 -40 -55 -70 -30 -30 -55 -60 -55 -68 0 -40 -88 -152 -120 -152
|
31
|
+
-9 0 -34 -18 -55 -40 -21 -22 -46 -40 -55 -40 -9 0 -29 -13 -45 -30 -18 -19
|
32
|
+
-39 -30 -55 -30 -34 0 -80 -23 -110 -55 -13 -14 -32 -25 -41 -25 -10 0 -33 -6
|
33
|
+
-51 -14 -18 -8 -50 -22 -71 -31 -20 -9 -35 -20 -32 -24 2 -5 -2 -14 -10 -21
|
34
|
+
-8 -7 -15 -20 -15 -30 0 -10 -7 -23 -15 -30 -10 -9 -15 -32 -15 -76 l0 -63 28
|
35
|
+
4 c15 2 178 9 362 16 258 9 339 14 351 25 21 18 63 18 79 -1 9 -11 34 -15 95
|
36
|
+
-15 70 0 90 -4 126 -25 33 -18 59 -25 102 -25 32 0 68 -6 80 -12 12 -7 54 -16
|
37
|
+
92 -19 91 -10 155 -22 182 -37 12 -7 36 -12 53 -12 17 0 41 -5 53 -11 36 -18
|
38
|
+
167 -152 167 -170 0 -9 7 -22 15 -29 8 -7 15 -26 15 -42 0 -16 7 -42 15 -59
|
39
|
+
l16 -30 -40 21 c-22 11 -53 20 -69 20 -16 0 -49 7 -72 15 -60 21 -453 21 -490
|
40
|
+
0 -14 -8 -38 -14 -53 -15 -15 0 -51 -12 -79 -26 -28 -14 -68 -27 -89 -28 -21
|
41
|
+
-2 -50 -12 -65 -22 -14 -10 -45 -31 -68 -45 -22 -14 -53 -42 -67 -64 -14 -21
|
42
|
+
-38 -44 -52 -52 -15 -7 -27 -19 -27 -26 0 -33 -26 -67 -51 -67 -28 0 -59 -27
|
43
|
+
-59 -51 0 -8 -18 -33 -40 -54 -22 -21 -40 -44 -40 -50 0 -6 -38 -48 -85 -95
|
44
|
+
-47 -47 -85 -90 -85 -97 0 -7 -5 -13 -11 -13 -6 0 -24 -11 -41 -25 -16 -14
|
45
|
+
-40 -25 -52 -25 -12 0 -35 -13 -51 -30 -16 -16 -36 -30 -46 -30 -9 0 -22 -7
|
46
|
+
-29 -15 -7 -8 -20 -15 -30 -15 -9 0 -34 -11 -54 -25 -20 -14 -43 -25 -49 -25
|
47
|
+
-7 0 -18 -5 -25 -12 -12 -12 -69 -35 -109 -45 -13 -3 -23 -11 -23 -18 0 -7 -7
|
48
|
+
-18 -15 -25 -11 -9 -15 -33 -15 -86 l0 -74 48 0 c26 0 191 7 367 15 453 21
|
49
|
+
539 20 595 -6 100 -47 153 -59 256 -59 72 0 106 -4 120 -15 10 -8 35 -15 54
|
50
|
+
-15 19 0 46 -7 60 -15 14 -8 41 -14 61 -15 40 0 100 -38 138 -86 36 -47 71
|
51
|
+
-104 71 -117 1 -7 9 -27 19 -45 17 -34 17 -36 -25 -210 -23 -98 -59 -249 -79
|
52
|
+
-337 -36 -154 -134 -511 -177 -645 -39 -123 -101 -275 -128 -315 -15 -22 -42
|
53
|
+
-68 -59 -103 -17 -34 -49 -91 -69 -125 -43 -70 -86 -210 -102 -332 -6 -44 -26
|
54
|
+
-147 -45 -230 -52 -229 -85 -551 -85 -820 0 -144 -5 -254 -15 -319 -21 -140
|
55
|
+
-19 -206 15 -440 29 -198 31 -209 76 -297 56 -112 164 -241 303 -362 210 -183
|
56
|
+
292 -248 456 -357 94 -62 206 -143 250 -180 90 -75 218 -162 345 -235 125 -71
|
57
|
+
254 -159 345 -235 44 -37 154 -116 245 -177 98 -66 189 -136 225 -173 33 -34
|
58
|
+
105 -93 160 -130 55 -37 125 -94 155 -125 30 -31 98 -92 150 -135 120 -98 172
|
59
|
+
-153 300 -315 184 -233 297 -332 403 -353 31 -6 84 -21 117 -33 66 -24 69 -31
|
60
|
+
48 -100 l-11 -39 -121 0 c-116 0 -122 -1 -131 -22 -7 -16 -20 -24 -42 -26 -17
|
61
|
+
-2 -37 -10 -43 -18 -9 -10 -35 -14 -95 -14 -92 0 -95 -2 -95 -82 0 -36 -4 -41
|
62
|
+
-136 -140 -74 -57 -143 -104 -152 -106 -9 -2 -31 -10 -49 -18 -17 -8 -42 -14
|
63
|
+
-55 -14 -12 -1 -34 -7 -48 -15 -20 -12 -89 -14 -361 -15 l-336 0 -23 -25 c-13
|
64
|
+
-14 -29 -25 -37 -25 -7 0 -13 -7 -13 -15 0 -8 -7 -15 -15 -15 -8 0 -15 -6 -15
|
65
|
+
-14 0 -7 -7 -19 -15 -26 -8 -7 -15 -27 -15 -44 0 -23 -9 -41 -30 -61 -16 -15
|
66
|
+
-27 -32 -25 -36 3 -4 -2 -15 -11 -24 -9 -9 -14 -18 -12 -21 3 -2 11 2 18 11 7
|
67
|
+
8 18 15 25 15 7 0 16 7 19 16 3 8 10 12 16 9 5 -3 10 1 10 9 0 9 6 16 14 16 7
|
68
|
+
0 19 7 26 15 7 8 19 15 28 15 8 0 24 7 34 17 10 9 20 14 22 12 3 -2 6 -32 7
|
69
|
+
-66 3 -65 -10 -93 -41 -93 -10 0 -23 -7 -30 -15 -7 -8 -25 -15 -41 -15 -17 0
|
70
|
+
-29 -5 -29 -13 0 -8 -11 -24 -25 -37 -14 -13 -25 -31 -25 -41 0 -9 -7 -22 -15
|
71
|
+
-29 -8 -7 -15 -28 -15 -47 0 -30 3 -34 22 -31 12 2 28 13 34 26 9 17 112 82
|
72
|
+
132 82 1 0 2 -17 2 -38 0 -49 24 -72 76 -72 21 0 282 18 579 40 297 22 606 40
|
73
|
+
686 40 104 0 159 4 189 15 23 8 69 15 102 15 33 0 69 5 81 12 12 6 42 15 67
|
74
|
+
19 62 10 115 31 143 57 13 12 28 22 34 22 17 0 43 35 43 58 0 20 5 22 65 22
|
75
|
+
37 0 65 -4 65 -10 0 -5 5 -10 12 -10 6 0 20 -7 30 -17 25 -23 29 -14 11 27
|
76
|
+
-26 60 -29 65 -51 75 -12 5 -22 15 -22 22 0 7 -9 13 -20 13 -11 0 -20 4 -20 8
|
77
|
+
0 14 89 32 163 32 43 1 82 6 97 15 14 8 39 14 55 15 23 0 42 11 73 43 23 23
|
78
|
+
42 48 42 55 0 7 15 12 39 12 22 0 44 6 51 15 8 10 31 15 70 15 39 0 62 5 70
|
79
|
+
15 7 9 29 15 55 15 45 0 109 26 100 41 -4 5 1 9 9 9 9 0 16 7 16 15 0 8 7 15
|
80
|
+
15 15 8 0 15 7 15 15 0 21 68 20 92 -2 17 -15 18 -15 18 4 0 11 -7 26 -15 33
|
81
|
+
-8 7 -15 18 -15 25 0 7 -10 16 -22 21 -18 6 -12 8 29 11 29 2 57 10 63 18 8 9
|
82
|
+
30 15 56 15 23 0 46 4 49 10 3 5 14 10 24 10 21 0 51 30 51 52 0 9 7 21 15 28
|
83
|
+
11 9 12 16 4 26 -19 23 1 34 62 34 51 0 59 3 59 18 0 22 -30 52 -52 52 -9 0
|
84
|
+
-21 7 -28 15 -7 8 -18 15 -25 15 -23 0 -59 72 -76 147 l-15 72 53 20 c78 30
|
85
|
+
218 129 298 211 39 39 90 85 115 102 25 17 73 58 106 90 60 58 63 60 105 56
|
86
|
+
43 -5 212 -49 393 -102 48 -14 109 -26 135 -26 25 0 71 -7 101 -15 30 -8 62
|
87
|
+
-15 70 -15 8 0 -73 88 -180 195 -107 107 -191 197 -187 201 4 4 48 -11 99 -33
|
88
|
+
121 -53 317 -116 443 -143 55 -12 161 -41 235 -65 263 -86 577 -148 908 -180
|
89
|
+
141 -13 276 -26 299 -28 30 -3 59 -16 96 -45 30 -22 91 -59 138 -82 46 -24
|
90
|
+
129 -68 184 -100 79 -45 152 -74 345 -138 408 -135 497 -159 818 -218 167 -30
|
91
|
+
306 -53 309 -50 3 3 -7 11 -22 18 -14 6 -35 28 -46 47 -35 67 -74 114 -119
|
92
|
+
146 -25 17 -53 45 -62 61 l-18 30 40 39 39 38 106 -13 c58 -6 109 -10 111 -7
|
93
|
+
3 3 -34 65 -83 139 -111 166 -110 163 -92 153 8 -5 43 -30 80 -56 58 -42 147
|
94
|
+
-84 157 -74 9 8 -31 64 -78 110 -29 28 -69 75 -90 105 -21 30 -79 95 -129 145
|
95
|
+
-50 49 -91 96 -91 104 0 8 -24 40 -52 72 -76 82 -130 147 -166 200 -17 25 -59
|
96
|
+
70 -93 100 -34 30 -78 71 -98 92 -20 22 -74 79 -121 129 -47 50 -110 108 -140
|
97
|
+
131 -30 22 -138 120 -240 218 -102 99 -270 261 -375 362 -104 101 -213 200
|
98
|
+
-242 221 l-52 37 71 117 c55 90 82 150 120 262 46 135 179 582 175 586 -3 2
|
99
|
+
-426 -72 -536 -95 -124 -25 -255 -27 -402 -6 -116 17 -275 59 -330 86 -20 10
|
100
|
+
-101 86 -180 170 -79 83 -207 217 -284 298 -77 80 -216 234 -308 341 -166 193
|
101
|
+
-169 196 -270 253 -56 32 -263 139 -460 238 -197 99 -412 213 -478 255 -66 41
|
102
|
+
-262 145 -435 231 -173 86 -314 160 -314 165 0 5 47 109 105 232 91 195 109
|
103
|
+
241 134 353 16 70 34 173 41 228 6 55 25 147 41 205 15 58 35 150 43 205 9 55
|
104
|
+
27 138 41 185 38 129 135 721 135 827 0 48 7 131 15 183 9 55 15 168 15 270 0
|
105
|
+
102 6 215 15 270 20 127 20 305 0 360 -10 28 -15 82 -15 159 -1 82 -5 123 -15
|
106
|
+
141 -8 14 -14 36 -15 49 0 30 -36 125 -100 261 -27 58 -52 116 -56 130 -3 14
|
107
|
+
-23 44 -43 68 -66 79 -205 223 -227 235 -84 48 -206 107 -219 107 -20 0 -20
|
108
|
+
66 0 140 8 30 15 89 15 130 0 41 7 100 15 130 11 41 15 113 15 276 0 255 -12
|
109
|
+
339 -66 446 -19 37 -56 124 -84 194 -57 142 -97 200 -211 309 -48 46 -95 79
|
110
|
+
-134 97 -33 14 -100 45 -150 67 -49 22 -124 47 -165 55 -41 8 -124 27 -185 41
|
111
|
+
-158 36 -336 34 -486 -4z m166 -3034 c36 -14 39 -18 37 -49 -6 -89 -74 -892
|
112
|
+
-87 -1028 -7 -85 -17 -195 -20 -245 -4 -49 -20 -170 -36 -268 -16 -97 -29
|
113
|
+
-211 -29 -252 0 -51 -17 -157 -56 -342 -30 -147 -87 -421 -125 -608 -38 -187
|
114
|
+
-82 -380 -99 -430 -16 -49 -32 -108 -36 -130 -4 -22 -8 -43 -10 -46 -6 -12
|
115
|
+
-380 78 -476 115 -53 20 -105 36 -114 36 -10 0 -48 14 -85 31 -78 36 -89 50
|
116
|
+
-173 217 l-56 112 0 168 c0 154 5 200 54 523 30 195 60 426 66 514 12 166 24
|
117
|
+
253 90 665 21 135 45 328 54 430 20 253 53 458 75 464 9 3 158 28 331 56 182
|
118
|
+
30 322 58 330 66 11 9 55 14 145 17 156 4 169 3 220 -16z m1825 -8492 c0 -12
|
119
|
+
-12 -15 -57 -14 -57 1 -57 2 -16 8 24 4 46 10 49 14 12 12 24 8 24 -8z m-830
|
120
|
+
-139 c0 -2 -7 -22 -15 -45 -26 -75 -71 -95 -156 -69 -34 10 -39 15 -39 42 0
|
121
|
+
17 -3 41 -6 54 -6 22 -6 22 105 22 61 0 111 -2 111 -4z"/>
|
122
|
+
<path d="M9790 1192 c0 -5 44 -18 98 -31 53 -13 104 -25 112 -28 10 -4 12 -2
|
123
|
+
5 4 -5 6 -50 20 -100 33 -49 12 -96 23 -102 26 -7 2 -13 0 -13 -4z"/>
|
124
|
+
</g>
|
125
|
+
</svg>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<!-- Mobile Header -->
|
2
|
+
<div class="sticky top-0">
|
3
|
+
<div class="flex items-center gap-x-6 bg-white dark:bg-gray-900 px-4 py-4 mr-4 shadow-sm
|
4
|
+
shadow-gray-200/50 dark:shadow-gray-800/20 sm:px-6 lg:hidden">
|
5
|
+
<button type="button" class="cursor-pointer -m-2.5 p-2.5 text-indigo-600 dark:text-indigo-400
|
6
|
+
hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors duration-200"
|
7
|
+
data-action="transition#open">
|
8
|
+
<span class="sr-only">Open sidebar</span>
|
9
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
10
|
+
stroke="currentColor" class="size-6">
|
11
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
12
|
+
</svg>
|
13
|
+
</button>
|
14
|
+
</div>
|
15
|
+
</div>
|