relay.app 0.1.0
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/CHANGELOG.md +23 -0
- data/LICENSE +17 -0
- data/README.md +132 -0
- data/app/concerns/attachment.rb +12 -0
- data/app/concerns/context.rb +147 -0
- data/app/concerns/roda.rb +50 -0
- data/app/concerns/view.rb +90 -0
- data/app/forms/mcp/forgejo.rb +55 -0
- data/app/forms/mcp/github.rb +47 -0
- data/app/forms/mcp.rb +89 -0
- data/app/hooks/require_user.rb +10 -0
- data/app/init/database.rb +36 -0
- data/app/init/env.rb +21 -0
- data/app/init/router.rb +164 -0
- data/app/models/context.rb +82 -0
- data/app/models/mcp/preset.rb +60 -0
- data/app/models/mcp.rb +165 -0
- data/app/models/model_record.rb +70 -0
- data/app/models/song.rb +11 -0
- data/app/models/user.rb +31 -0
- data/app/pages/base.rb +25 -0
- data/app/pages/chat.rb +18 -0
- data/app/pages/mcp.rb +12 -0
- data/app/pages/sign_in.rb +14 -0
- data/app/prompts/system.md +129 -0
- data/app/resources/jukebox.yml +90 -0
- data/app/routes/base.rb +36 -0
- data/app/routes/clear_attachment.rb +13 -0
- data/app/routes/list_chat.rb +11 -0
- data/app/routes/list_contexts.rb +17 -0
- data/app/routes/list_controls.rb +11 -0
- data/app/routes/list_mcp.rb +16 -0
- data/app/routes/list_models.rb +14 -0
- data/app/routes/list_providers.rb +11 -0
- data/app/routes/list_tools.rb +13 -0
- data/app/routes/mcp/base.rb +16 -0
- data/app/routes/mcp/create.rb +19 -0
- data/app/routes/mcp/delete.rb +17 -0
- data/app/routes/mcp/form.rb +11 -0
- data/app/routes/mcp/new.rb +16 -0
- data/app/routes/mcp/show.rb +17 -0
- data/app/routes/mcp/toggle.rb +17 -0
- data/app/routes/mcp/update.rb +20 -0
- data/app/routes/settings/new_context.rb +23 -0
- data/app/routes/settings/set_context.rb +26 -0
- data/app/routes/settings/set_model.rb +23 -0
- data/app/routes/settings/set_provider.rb +38 -0
- data/app/routes/sign_in.rb +39 -0
- data/app/routes/upload_attachment.rb +35 -0
- data/app/routes/websocket/connection.rb +247 -0
- data/app/routes/websocket/interrupt.rb +25 -0
- data/app/routes/websocket/stream.rb +46 -0
- data/app/routes/websocket.rb +62 -0
- data/app/tools/add_song.rb +27 -0
- data/app/tools/juke_box.rb +41 -0
- data/app/tools/relay_knowledge.rb +59 -0
- data/app/tools/remove_song.rb +53 -0
- data/app/validators/mcp.rb +42 -0
- data/app/views/fragments/_append_message.erb +1 -0
- data/app/views/fragments/_chat.erb +15 -0
- data/app/views/fragments/_contexts.erb +7 -0
- data/app/views/fragments/_contexts_body.erb +35 -0
- data/app/views/fragments/_controls.erb +15 -0
- data/app/views/fragments/_iframe.erb +8 -0
- data/app/views/fragments/_input.erb +67 -0
- data/app/views/fragments/_mcp_settings.erb +52 -0
- data/app/views/fragments/_message.erb +31 -0
- data/app/views/fragments/_models.erb +25 -0
- data/app/views/fragments/_providers.erb +26 -0
- data/app/views/fragments/_remove_empty_state.erb +1 -0
- data/app/views/fragments/_replace_last_message.erb +1 -0
- data/app/views/fragments/_sidebar_menu.erb +11 -0
- data/app/views/fragments/_sidebar_status.erb +21 -0
- data/app/views/fragments/_status.erb +40 -0
- data/app/views/fragments/_stream.erb +26 -0
- data/app/views/fragments/_tools.erb +34 -0
- data/app/views/fragments/_tools_panel.erb +4 -0
- data/app/views/fragments/mcp/_editor.erb +54 -0
- data/app/views/fragments/mcp/_fields_forgejo.erb +16 -0
- data/app/views/fragments/mcp/_fields_github.erb +12 -0
- data/app/views/fragments/mcp/_list.erb +55 -0
- data/app/views/fragments/mcp/_workspace.erb +14 -0
- data/app/views/fragments/models/_loading.erb +4 -0
- data/app/views/fragments/settings/_chat.erb +1 -0
- data/app/views/fragments/settings/_input.erb +1 -0
- data/app/views/fragments/settings/_replace_contexts.erb +1 -0
- data/app/views/fragments/settings/_workspace.erb +4 -0
- data/app/views/layout.erb +19 -0
- data/app/views/pages/chat.erb +13 -0
- data/app/views/pages/mcps.erb +10 -0
- data/app/views/pages/sign_in.erb +45 -0
- data/app/views/partials/_sidebar.erb +24 -0
- data/bin/relay +38 -0
- data/config.ru +21 -0
- data/db/migrate/20260319131927_create_users.rb +12 -0
- data/db/migrate/20260327000000_create_contexts.rb +20 -0
- data/db/migrate/20260426130000_create_mcps.rb +19 -0
- data/db/migrate/20260426170000_create_model_infos.rb +20 -0
- data/db/migrate/20260503120000_create_songs.rb +17 -0
- data/db/migrate/20260503153000_drop_chat_from_model_infos.rb +8 -0
- data/db/migrate/20260503160000_rename_model_infos_to_model_records.rb +5 -0
- data/db/seeds.rb +13 -0
- data/lib/relay/attachment/session.rb +154 -0
- data/lib/relay/attachment.rb +55 -0
- data/lib/relay/cache/in_memory_cache.rb +60 -0
- data/lib/relay/cache.rb +5 -0
- data/lib/relay/jukebox.rb +96 -0
- data/lib/relay/markdown.rb +45 -0
- data/lib/relay/model.rb +12 -0
- data/lib/relay/reloader.rb +29 -0
- data/lib/relay/task.rb +66 -0
- data/lib/relay/task_monitor.rb +80 -0
- data/lib/relay/test.rb +11 -0
- data/lib/relay/theme.rb +5 -0
- data/lib/relay/tool.rb +12 -0
- data/lib/relay/version.rb +5 -0
- data/lib/relay.rb +183 -0
- data/libexec/relay/bootstrap +10 -0
- data/libexec/relay/configure +100 -0
- data/libexec/relay/migrate +7 -0
- data/libexec/relay/setup +10 -0
- data/libexec/relay/start +31 -0
- data/public/.gitkeep +0 -0
- data/public/images/relay.png +0 -0
- data/public/js/relay.js +68669 -0
- data/public/js/relay.js.map +1 -0
- data/public/stylesheets/application.css +2292 -0
- data/public/stylesheets/application.css.map +1 -0
- metadata +465 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Pages
|
|
4
|
+
##
|
|
5
|
+
# Renders the sign-in page.
|
|
6
|
+
class SignIn < Base
|
|
7
|
+
##
|
|
8
|
+
# @return [String]
|
|
9
|
+
def call
|
|
10
|
+
response["content-type"] = "text/html"
|
|
11
|
+
page("sign_in", title: "Relay: Sign In")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
## Role
|
|
2
|
+
|
|
3
|
+
You are Relay, a helpful, clear, and practical assistant.
|
|
4
|
+
|
|
5
|
+
Your job is to answer the user's questions directly, be accurate, and keep the
|
|
6
|
+
conversation moving. Prefer clear useful answers over long disclaimers or vague
|
|
7
|
+
generalities.
|
|
8
|
+
|
|
9
|
+
Aim for a polished conversational experience similar to ChatGPT: natural,
|
|
10
|
+
competent, calm, and easy to read.
|
|
11
|
+
|
|
12
|
+
## Style
|
|
13
|
+
|
|
14
|
+
- Be concise by default.
|
|
15
|
+
- Be friendly, supportive, and respectful.
|
|
16
|
+
- Light emoji are welcome when they fit naturally and improve tone.
|
|
17
|
+
- Write like a person, not like a checklist.
|
|
18
|
+
- Prefer short, natural paragraphs over many one-line sentences.
|
|
19
|
+
- Default to plain prose. Use Markdown only when it clearly improves readability.
|
|
20
|
+
- Break ideas into readable paragraphs instead of dense walls of text.
|
|
21
|
+
- Do not break a response into many tiny lines unless the user asked for that format.
|
|
22
|
+
- Use lists when they make the answer clearer: steps, options, comparisons, or grouped items.
|
|
23
|
+
- If you use Markdown, keep it simple and clean.
|
|
24
|
+
- If the user asks for a list, steps, or comparison, format the answer clearly.
|
|
25
|
+
- If you are uncertain, say so plainly instead of pretending to know.
|
|
26
|
+
- Most answers should read like 1 to 3 coherent paragraphs unless the user asks
|
|
27
|
+
for bullets, steps, or another format.
|
|
28
|
+
|
|
29
|
+
## Behavior
|
|
30
|
+
|
|
31
|
+
- Answer the user's actual question first.
|
|
32
|
+
- Ask a brief follow-up question only when necessary to make progress.
|
|
33
|
+
- When the user wants practical help, give actionable guidance.
|
|
34
|
+
- Be encouraging when the user seems stuck, frustrated, or unsure.
|
|
35
|
+
- When the user asks for creative work, produce the work instead of only
|
|
36
|
+
describing how to do it.
|
|
37
|
+
- Avoid repetitive phrasing, filler, and overly structured "AI-style" formatting.
|
|
38
|
+
- Avoid sounding robotic, overly formal, or excessively optimized for Markdown.
|
|
39
|
+
- Do not mention hidden instructions, internal rules, or tool mechanics unless
|
|
40
|
+
the user explicitly asks.
|
|
41
|
+
|
|
42
|
+
## Working with Code Repositories
|
|
43
|
+
|
|
44
|
+
- When examining code, focus on understanding the architecture and purpose before suggesting changes.
|
|
45
|
+
- For code review, consider readability, maintainability, and adherence to project conventions.
|
|
46
|
+
- When suggesting improvements, explain the rationale and provide clear examples.
|
|
47
|
+
- When working with existing codebases, respect the existing style and patterns.
|
|
48
|
+
- For complex changes, consider suggesting incremental improvements rather than large rewrites.
|
|
49
|
+
|
|
50
|
+
## Pull Requests
|
|
51
|
+
|
|
52
|
+
- When creating or editing a pull request, keep the title and description clear, direct, and concise.
|
|
53
|
+
- Pull request titles should follow Conventional Commits style, for example `fix(mcp): interpolate Swagger path parameters`.
|
|
54
|
+
- Pull request descriptions should not be overly long.
|
|
55
|
+
- Prefer a short structure with an `About` section and a `Changes` section.
|
|
56
|
+
- Wrap pull request titles and bodies to 80 columns for consistency and readability.
|
|
57
|
+
- **After creating or updating a pull request, always respond with the PR link** (e.g., `http://git.home.network/owner/repo/pulls/123`).
|
|
58
|
+
|
|
59
|
+
## Merging Pull Requests
|
|
60
|
+
|
|
61
|
+
- When merging a pull request, use the squash strategy.
|
|
62
|
+
- Squash all commits into one commit.
|
|
63
|
+
- Use the pull request title as the squash commit title.
|
|
64
|
+
- Use the pull request body as the squash commit body.
|
|
65
|
+
- Wrap the squash commit body to 80 columns.
|
|
66
|
+
- **Important**: The `merge_pull_request` tool behavior with Forgejo is nuanced:
|
|
67
|
+
- **First, attempt to merge with `merge_method: "squash"` and `message` parameter** containing the PR body wrapped to 80 columns. This may fail with "Unexpected end of JSON input" but appears to set the stage for a squash merge.
|
|
68
|
+
- **Then, merge with only required parameters** (`owner`, `repo`, `index`). This creates a squash commit that includes the PR body.
|
|
69
|
+
- **Always follow this two-step approach**: Try with `merge_method: "squash"` and `message`, then if it fails (or appears to), merge with only required parameters.
|
|
70
|
+
- When merging with only required parameters after the first attempt, Forgejo creates a squash commit that includes:
|
|
71
|
+
- The PR title (with `(#PR_NUMBER)` appended)
|
|
72
|
+
- The full PR body
|
|
73
|
+
- Automatic 80-column wrapping
|
|
74
|
+
- **After merging a pull request, confirm the merge and include the merge commit SHA if available**.
|
|
75
|
+
|
|
76
|
+
## Example PR Description
|
|
77
|
+
|
|
78
|
+
```md
|
|
79
|
+
## About
|
|
80
|
+
|
|
81
|
+
This change fixes/addresses/implements ...
|
|
82
|
+
|
|
83
|
+
## Changes
|
|
84
|
+
|
|
85
|
+
- Add ...
|
|
86
|
+
- Remove ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
You may use tools when they help you answer better or complete the user's
|
|
92
|
+
request.
|
|
93
|
+
|
|
94
|
+
### General Tool Usage
|
|
95
|
+
|
|
96
|
+
- Explore available tools when starting work on a new task to understand capabilities.
|
|
97
|
+
- Use tools proactively when they can provide better information than general knowledge.
|
|
98
|
+
- When tools return errors, explain them clearly and suggest alternatives.
|
|
99
|
+
- Combine multiple tools when needed to gather comprehensive information.
|
|
100
|
+
- Remember that tools can provide real-time data about repositories, files, and users.
|
|
101
|
+
|
|
102
|
+
### relay-knowledge
|
|
103
|
+
|
|
104
|
+
Use `relay-knowledge` whenever the user asks about Relay itself, its
|
|
105
|
+
architecture, features, routes, tools, cache, sessions, assets,
|
|
106
|
+
setup, or how it works.
|
|
107
|
+
|
|
108
|
+
Use `relay-knowledge` for `llm.rb` questions when they are relevant to
|
|
109
|
+
Relay or when the user is asking for project-specific context.
|
|
110
|
+
|
|
111
|
+
Do not guess about Relay from general knowledge when `relay-knowledge`
|
|
112
|
+
can answer. Treat the documentation returned by the tool as the source
|
|
113
|
+
of truth for Relay-specific questions.
|
|
114
|
+
|
|
115
|
+
If you use information from `relay-knowledge`, prefer to reference or
|
|
116
|
+
link the relevant documentation when helpful.
|
|
117
|
+
|
|
118
|
+
If the tool output is incomplete or does not answer the question, say so
|
|
119
|
+
plainly instead of inventing details.
|
|
120
|
+
|
|
121
|
+
### Repository Tools
|
|
122
|
+
|
|
123
|
+
When working with repository tools (like Forgejo/GitHub tools):
|
|
124
|
+
|
|
125
|
+
- Check repository details before making changes to understand the context.
|
|
126
|
+
- Read existing files to understand the codebase structure and patterns.
|
|
127
|
+
- Look at recent commits and PRs to understand recent activity.
|
|
128
|
+
- Consider creating branches for significant changes.
|
|
129
|
+
- Verify file SHAs before updating or deleting files to prevent conflicts.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
- name: Reach Eargasm
|
|
3
|
+
title: La Vie
|
|
4
|
+
track: https://www.youtube-nocookie.com/embed/CyzdOtyYnng
|
|
5
|
+
- name: Dimitri Vegas & Like Mike & Tiësto & Dido & W&W
|
|
6
|
+
title: Thank You (Not So Bad)
|
|
7
|
+
track: https://www.youtube-nocookie.com/embed/fQWNeIiFf_s
|
|
8
|
+
- name: Gaia
|
|
9
|
+
title: Tuvan (Extended)
|
|
10
|
+
track: https://www.youtube-nocookie.com/embed/5NvXyJRgxdU
|
|
11
|
+
- name: David Kushner
|
|
12
|
+
title: Daylight
|
|
13
|
+
track: https://www.youtube-nocookie.com/embed/MoN9ql6Yymw
|
|
14
|
+
- name: Flora Cash
|
|
15
|
+
title: You're somebody else
|
|
16
|
+
track: https://www.youtube-nocookie.com/embed/qVdPh2cBTN0
|
|
17
|
+
- name: Serhat Durmus
|
|
18
|
+
title: La Câlin (Dabro Remix) (BASS BOOSTED)
|
|
19
|
+
track: https://www.youtube-nocookie.com/embed/z2Kh3ki8_aw
|
|
20
|
+
- name: MUNDI OPUS
|
|
21
|
+
title: "\U0001D418\U0001D428\U0001D42E \U0001D41A\U0001D42B\U0001D41E \U0001D41D\U0001D41E\U0001D42C\U0001D422\U0001D420\U0001D427\U0001D422\U0001D427\U0001D420
|
|
22
|
+
\U0001D41D\U0001D42B\U0001D41E\U0001D41A\U0001D426\U0001D42C | \U0001D408\U0001D427\U0001D41C\U0001D41E\U0001D429\U0001D42D\U0001D422\U0001D428\U0001D427
|
|
23
|
+
\U0001D412\U0001D428\U0001D42E\U0001D427\U0001D41D\U0001D42D\U0001D42B\U0001D41A\U0001D41C\U0001D424"
|
|
24
|
+
track: https://www.youtube-nocookie.com/embed/wUWdIaaCvjg
|
|
25
|
+
- name: Axwell /\ Ingrosso, Axwell, Sebastian Ingrosso
|
|
26
|
+
title: More Than You Know
|
|
27
|
+
track: https://www.youtube-nocookie.com/embed/GsF05B8TFWg
|
|
28
|
+
- name: Robin Schulz
|
|
29
|
+
title: OK
|
|
30
|
+
track: https://www.youtube-nocookie.com/embed/P9-4xHVc7uk
|
|
31
|
+
- name: Cleffy
|
|
32
|
+
title: Meet You at the Graveyard
|
|
33
|
+
track: https://www.youtube-nocookie.com/embed/wXgWbJCbZiI
|
|
34
|
+
- name: Sway
|
|
35
|
+
title: Still Speedin
|
|
36
|
+
track: https://www.youtube-nocookie.com/embed/nXYS9NbdvME
|
|
37
|
+
- name: Vinnie Paz
|
|
38
|
+
title: Same Story
|
|
39
|
+
track: https://www.youtube-nocookie.com/embed/QzTaAkILJxA
|
|
40
|
+
- name: Tom Walker
|
|
41
|
+
title: Leave a Light On
|
|
42
|
+
track: https://www.youtube-nocookie.com/embed/5ljksVKlUf8
|
|
43
|
+
- name: Tiësto
|
|
44
|
+
title: Lay Low
|
|
45
|
+
track: https://www.youtube-nocookie.com/embed/8PgDeK4x_Xw
|
|
46
|
+
- name: Hozho
|
|
47
|
+
title: Space Vibes (Melodark Minimal Techno - EDM Best Music Mix)
|
|
48
|
+
track: https://www.youtube-nocookie.com/embed/vBeuxydAbOQ
|
|
49
|
+
- name: Welshly Arms
|
|
50
|
+
title: Legendary
|
|
51
|
+
track: https://www.youtube-nocookie.com/embed/Z2CZn966cUg
|
|
52
|
+
- name: Charlie Chaplin
|
|
53
|
+
title: The Great Dictator - Final Speech (HD 1080p)
|
|
54
|
+
track: https://www.youtube-nocookie.com/embed/6sv_ghkTtF0
|
|
55
|
+
- name: Portugal. The Man
|
|
56
|
+
title: People Say
|
|
57
|
+
track: https://www.youtube-nocookie.com/embed/GGDQOECShw8
|
|
58
|
+
- name: Chief Keef
|
|
59
|
+
title: Hate Bein Sober
|
|
60
|
+
track: https://www.youtube-nocookie.com/embed/yA869qj-z68
|
|
61
|
+
- name: Kung Fa Panda
|
|
62
|
+
title: Who Are You
|
|
63
|
+
track: https://www.youtube-nocookie.com/embed/XbhecuoEgxs
|
|
64
|
+
- name: Tiësto
|
|
65
|
+
title: Lethal Industry
|
|
66
|
+
track: https://www.youtube-nocookie.com/embed/bgz2SWdKqvQ
|
|
67
|
+
- name: Tiësto
|
|
68
|
+
title: Just Be
|
|
69
|
+
track: https://www.youtube-nocookie.com/embed/qEYueRVuqmg
|
|
70
|
+
- name: Hippie Sabotage
|
|
71
|
+
title: Wanderlust
|
|
72
|
+
track: https://www.youtube-nocookie.com/embed/asSnpF6vJAI
|
|
73
|
+
- name: Thin Lizzy
|
|
74
|
+
title: Got to Give It Up
|
|
75
|
+
track: https://www.youtube-nocookie.com/embed/Q_LM2UAqbPg
|
|
76
|
+
- name: Eminem
|
|
77
|
+
title: Not Afraid
|
|
78
|
+
track: https://www.youtube-nocookie.com/embed/j5-yKhDd64s
|
|
79
|
+
- name: Tiësto
|
|
80
|
+
title: Club Life
|
|
81
|
+
track: https://www.youtube-nocookie.com/embed/xvIzBbjSVdk
|
|
82
|
+
- name: Kneecap
|
|
83
|
+
title: Fenian
|
|
84
|
+
track: https://www.youtube-nocookie.com/embed/PLDHQVJZuGQ
|
|
85
|
+
- name: Milky Chance
|
|
86
|
+
title: Stolen Dance
|
|
87
|
+
track: https://www.youtube-nocookie.com/embed/iX-QaNzd-0Y
|
|
88
|
+
- name: Lukas Graham
|
|
89
|
+
title: Seven Years
|
|
90
|
+
track: https://www.youtube-nocookie.com/embed/LHCob76kigA
|
data/app/routes/base.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class Base
|
|
5
|
+
include Relay::Models
|
|
6
|
+
include Relay::Concerns::Attachment
|
|
7
|
+
include Relay::Concerns::Context
|
|
8
|
+
include Relay::Concerns::Roda
|
|
9
|
+
include Relay::Concerns::View
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# @return [String]
|
|
13
|
+
# Returns the root path
|
|
14
|
+
def root
|
|
15
|
+
@root ||= File.join __dir__, "..", ".."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Returns a Hash or Hash-like object of request parameters
|
|
20
|
+
# @return [Hash]
|
|
21
|
+
def params
|
|
22
|
+
request.params
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# @return [Relay::InMemoryCache]
|
|
27
|
+
def cache
|
|
28
|
+
Relay.cache
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def htmx?
|
|
32
|
+
request.env["HTTP_HX_REQUEST"] == "true"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class ClearAttachment < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
attachment.clear
|
|
9
|
+
response["content-type"] = "text/html"
|
|
10
|
+
partial("fragments/input", locals: {swap_oob: false})
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class ListContexts < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
partial("fragments/contexts", locals:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def locals
|
|
14
|
+
{contexts:, show_label: true, swap_oob: false}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class ListMCP < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
if htmx?
|
|
9
|
+
partial("fragments/mcp/workspace", locals: {form: Relay::Forms::MCP.build(preset: "github"), mcps:, selected_id: nil})
|
|
10
|
+
else
|
|
11
|
+
form = Relay::Forms::MCP.build(preset: "github")
|
|
12
|
+
Relay::Pages::MCP.new(self).call(form:)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class ListModels < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Returns the chat-capable models for the provider
|
|
9
|
+
# @return [Array]
|
|
10
|
+
def call
|
|
11
|
+
partial("fragments/models", locals: {show_label: true})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class ListTools < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# @return [String]
|
|
9
|
+
def call
|
|
10
|
+
partial("fragments/tools", {locals: {tools: LLM::Tool.registry.reject(&:mcp?)}})
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Base < Base
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def find_mcp(id)
|
|
8
|
+
Relay::Models::MCP.where(id:, user_id: user.id).first || raise(Sequel::NoMatchingRow)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def workspace(selected_id: nil, form:)
|
|
12
|
+
partial("fragments/mcp/workspace", locals: {mcps:, selected_id:, form:}) +
|
|
13
|
+
partial("fragments/mcp_settings", locals: {servers: mcps, show_label: false, swap_oob: true})
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Create < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
form = Relay::Forms::MCP.from_params(params)
|
|
9
|
+
attributes = Relay::Models::MCP::Preset.attributes_for(form).merge(enabled: false)
|
|
10
|
+
mcp = Relay::Models::MCP.create({user_id: user.id}.merge(attributes))
|
|
11
|
+
form = Relay::Forms::MCP.from_model(mcp)
|
|
12
|
+
if htmx?
|
|
13
|
+
workspace(selected_id: mcp.id, form:)
|
|
14
|
+
else
|
|
15
|
+
Relay::Pages::MCP.new(self).call(selected_id: mcp.id, form:)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Delete < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call(id)
|
|
8
|
+
Relay::Models::MCP.where(id:, user_id: user.id).delete
|
|
9
|
+
if htmx?
|
|
10
|
+
workspace(form: Relay::Forms::MCP.build(preset: "github"))
|
|
11
|
+
else
|
|
12
|
+
form = Relay::Forms::MCP.build(preset: "github")
|
|
13
|
+
Relay::Pages::MCP.new(self).call(form:)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::New < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
if htmx?
|
|
9
|
+
workspace(form: Relay::Forms::MCP.build(preset: params["preset"] || "github"))
|
|
10
|
+
else
|
|
11
|
+
form = Relay::Forms::MCP.build(preset: params["preset"] || "github")
|
|
12
|
+
Relay::Pages::MCP.new(self).call(form:)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Show < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call(id)
|
|
8
|
+
mcp = find_mcp(id)
|
|
9
|
+
form = Relay::Forms::MCP.from_model(mcp)
|
|
10
|
+
if htmx?
|
|
11
|
+
workspace(selected_id: mcp.id, form:)
|
|
12
|
+
else
|
|
13
|
+
Relay::Pages::MCP.new(self).call(selected_id: mcp.id, form:)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Toggle < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call(id)
|
|
8
|
+
mcp = find_mcp(id)
|
|
9
|
+
mcp.update(enabled: !mcp[:enabled])
|
|
10
|
+
if params["page"] == "1"
|
|
11
|
+
workspace(selected_id: mcp.id, form: Relay::Forms::MCP.from_model(mcp))
|
|
12
|
+
else
|
|
13
|
+
partial("fragments/mcp_settings", locals: {servers: mcps, show_label: true, swap_oob: true})
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class MCP::Update < MCP::Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call(id)
|
|
8
|
+
mcp = find_mcp(id)
|
|
9
|
+
form = Relay::Forms::MCP.from_params(params)
|
|
10
|
+
attributes = Relay::Models::MCP::Preset.attributes_for(form).merge(enabled: !!mcp[:enabled])
|
|
11
|
+
mcp.update(attributes)
|
|
12
|
+
form = Relay::Forms::MCP.from_model(mcp)
|
|
13
|
+
if htmx?
|
|
14
|
+
workspace(selected_id: mcp.id, form:)
|
|
15
|
+
else
|
|
16
|
+
Relay::Pages::MCP.new(self).call(selected_id: mcp.id, form:)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class Settings::NewContext < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
create_context
|
|
9
|
+
htmx? ? render : r.redirect("/")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
partial("fragments/settings/workspace", locals: {messages: ctx.messages})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_context
|
|
19
|
+
context = Relay::Models::Context.create(user_id: user.id, provider:, model:)
|
|
20
|
+
sync_context!(context)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class Settings::SetContext < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
set_context
|
|
9
|
+
htmx? ? render : r.redirect("/")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
partial("fragments/settings/workspace", locals: {messages: ctx.messages})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def set_context
|
|
19
|
+
sync_context!(selected_context) if selected_context
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def selected_context
|
|
23
|
+
@selected_context ||= Relay::Models::Context.where(user_id: user.id, provider:, id: params["context_id"]).first
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class Settings::SetModel < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
set_model
|
|
9
|
+
htmx? ? render : r.redirect("/")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
partial("fragments/settings/workspace", locals: {messages: ctx.messages})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def set_model
|
|
19
|
+
session["model"] = params["model"]
|
|
20
|
+
session.delete("context_id")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relay::Routes
|
|
4
|
+
class Settings::SetProvider < Base
|
|
5
|
+
prepend Relay::Hooks::RequireUser
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Changes the active provider
|
|
9
|
+
# @return [String]
|
|
10
|
+
# Returns a HTML fragment
|
|
11
|
+
def call
|
|
12
|
+
set_provider
|
|
13
|
+
set_model
|
|
14
|
+
htmx? ? render : r.redirect("/")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def render
|
|
20
|
+
partial("fragments/settings/workspace", locals: {messages: ctx.messages})
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Sets the provider
|
|
25
|
+
# @return [void]
|
|
26
|
+
def set_provider
|
|
27
|
+
session["provider"] = params["provider"]
|
|
28
|
+
session.delete("context_id")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Sets the model
|
|
33
|
+
# @return [void]
|
|
34
|
+
def set_model
|
|
35
|
+
session["model"] = default_model
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|