jekyll-ai-chatbot 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8534449e9a21053a99ef60381c817b176a9bf265775a970d21516ec20157d6f8
4
+ data.tar.gz: 3591194a072e9bb4586737c972a0ab516093ffd7bde1d3b48bcc693d60b30955
5
+ SHA512:
6
+ metadata.gz: 34567640711fe7db9ded7543f9d3b092e054f26779679302c98826d648513138041aec90a2e3d35fffa03bc0a80923e6625117442987e7e6fd057b616e15d4bd
7
+ data.tar.gz: b944e557c0005ee750e8ef0367dee607ef6cd7a2cb2192fef5b09387b80f49ac48b0cf217727f58fe9a5e0e66210e8b5c5cd429a2bd15bc48f8f972617731d32
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asyntai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # jekyll-ai-chatbot
2
+
3
+ Add the [Asyntai](https://asyntai.com) AI chat assistant to your Jekyll site.
4
+ It answers visitor questions from your own content, in more than 80 languages.
5
+
6
+ ## Install
7
+
8
+ Add the gem to your `Gemfile`, in the `jekyll_plugins` group:
9
+
10
+ ```ruby
11
+ group :jekyll_plugins do
12
+ gem "jekyll-ai-chatbot"
13
+ end
14
+ ```
15
+
16
+ Run `bundle install`. Then add your widget ID to `_config.yml`:
17
+
18
+ ```yaml
19
+ asyntai:
20
+ widget_id: asyntai_1a2b3c4d5e6f
21
+ ```
22
+
23
+ If your site lists its plugins in `_config.yml` instead of the Gemfile group,
24
+ add it there too:
25
+
26
+ ```yaml
27
+ plugins:
28
+ - jekyll-ai-chatbot
29
+ ```
30
+
31
+ Build the site. The chat bubble is on every page.
32
+
33
+ ## Where the widget ID comes from
34
+
35
+ Open the Install page in your [Asyntai dashboard](https://asyntai.com/dashboard).
36
+ The widget ID is the value of `data-asyntai-id` in the snippet shown there. It
37
+ starts with `asyntai_`.
38
+
39
+ Creating an account is free, and the Free plan is enough to try the widget.
40
+
41
+ ## Options
42
+
43
+ All options go under `asyntai:` in `_config.yml`.
44
+
45
+ | Option | Type | Default | What it does |
46
+ | --- | --- | --- | --- |
47
+ | `widget_id` | string | required | Your Asyntai widget ID |
48
+ | `enable_in_development` | boolean | `true` | Set to `false` to keep the widget off the site while `JEKYLL_ENV` is `development` |
49
+ | `script_url` | string | the Asyntai CDN | Advanced. Load the widget from another URL |
50
+
51
+ A missing or malformed `widget_id` stops the build with a message that says how
52
+ to fix it.
53
+
54
+ Jekyll uses `development` when `JEKYLL_ENV` is not set, also for `jekyll build`.
55
+ If you set `enable_in_development: false`, build with
56
+ `JEKYLL_ENV=production jekyll build` to get the widget.
57
+
58
+ To keep the widget off one page, add this to its front matter:
59
+
60
+ ```yaml
61
+ asyntai: false
62
+ ```
63
+
64
+ ## What the plugin does
65
+
66
+ - It adds the Asyntai loader to every HTML page, just before `</body>`.
67
+ That includes pages, posts, collection documents and pagination pages.
68
+ - The loader waits for the page's `load` event, so your own content always
69
+ renders first.
70
+ - Feeds, JSON, CSS, Sass and other non-HTML files stay as they are.
71
+ - A page that already has the Asyntai snippet keeps it and gets no second one.
72
+
73
+ Tested with Jekyll 3.10 and 4.4.
74
+
75
+ ## GitHub Pages
76
+
77
+ The classic GitHub Pages build runs only the plugins on its own approved list.
78
+ To use this gem on GitHub Pages, build the site with GitHub Actions
79
+ (the "Jekyll" workflow in your repository's Pages settings). Then the gem runs
80
+ like on any other host.
81
+
82
+ ## Answering from your content
83
+
84
+ Asyntai reads your published pages. Add your site URL in the dashboard, and the
85
+ assistant answers from the same pages the widget sits on. You can also add extra
86
+ documents and question and answer pairs.
87
+
88
+ ## Removing it
89
+
90
+ Remove the gem from your `Gemfile` and the `asyntai:` block from `_config.yml`,
91
+ then build again. The widget is gone.
92
+
93
+ ## Support
94
+
95
+ Email [hello@asyntai.com](mailto:hello@asyntai.com), or read the
96
+ [Jekyll guide](https://asyntai.com/documentation/integrations/jekyll/).
97
+
98
+ ## Licence
99
+
100
+ MIT
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Asyntai AI chatbot for Jekyll: the logic, without the hooks, so it can be
4
+ # tested outside a Jekyll build.
5
+ #
6
+ # Adds the Asyntai chat widget to every HTML page the site builds. After a page
7
+ # is rendered, the plugin puts the ordinary embed loader just before </body>
8
+ # and changes nothing else about the page.
9
+
10
+ require "json"
11
+ require "jekyll-ai-chatbot/version"
12
+
13
+ module JekyllAiChatbot
14
+ DEFAULT_SCRIPT_URL = "https://widget.asyntai.com/static/js/chat-widget.js"
15
+ WIDGET_ID_PATTERN = /\Aasyntai_[A-Za-z0-9]+\z/
16
+ NAME = "jekyll-ai-chatbot"
17
+ CONFIG_KEY = "asyntai"
18
+
19
+ class ConfigurationError < StandardError; end
20
+
21
+ Settings = Struct.new(:widget_id, :script_url, :enable_in_development)
22
+
23
+ module_function
24
+
25
+ # Reads the `asyntai:` block of _config.yml. A wrong option stops the build
26
+ # with a clear message instead of producing a site with no chat.
27
+ def validate_options(options)
28
+ unless options.nil? || options.is_a?(Hash)
29
+ raise ConfigurationError,
30
+ "#{NAME}: \"#{CONFIG_KEY}\" in _config.yml must be a block with " \
31
+ "widget_id inside it, for example:\n\n" \
32
+ " #{CONFIG_KEY}:\n widget_id: asyntai_1a2b3c4d5e6f"
33
+ end
34
+
35
+ opts = (options || {}).each_with_object({}) { |(k, v), h| h[k.to_s] = v }
36
+ widget_id = opts["widget_id"].is_a?(String) ? opts["widget_id"].strip : ""
37
+
38
+ if widget_id.empty?
39
+ raise ConfigurationError,
40
+ "#{NAME}: \"widget_id\" is required. Add it to _config.yml under " \
41
+ "\"#{CONFIG_KEY}:\". Copy it from the Install page of your Asyntai " \
42
+ "dashboard, for example asyntai_1a2b3c4d5e6f."
43
+ end
44
+ unless WIDGET_ID_PATTERN.match?(widget_id)
45
+ raise ConfigurationError,
46
+ "#{NAME}: \"#{widget_id}\" does not look like a widget ID. It starts with " \
47
+ "asyntai_, for example asyntai_1a2b3c4d5e6f."
48
+ end
49
+
50
+ script_url = opts["script_url"].is_a?(String) && !opts["script_url"].strip.empty? ?
51
+ opts["script_url"].strip : DEFAULT_SCRIPT_URL
52
+
53
+ Settings.new(
54
+ widget_id,
55
+ script_url,
56
+ # Set to false to keep the widget off the site while JEKYLL_ENV is development.
57
+ opts["enable_in_development"] != false
58
+ )
59
+ end
60
+
61
+ # JSON string, safe inside an HTML <script> element.
62
+ def js_string(value)
63
+ JSON.generate(value.to_s).gsub("<", "\\u003c")
64
+ end
65
+
66
+ # The loader waits for the page's load event before it fetches the widget.
67
+ # An ordinary `<script async src>` holds the load event open until its own
68
+ # fetch finishes, so an unreachable server would delay the host page. This
69
+ # pattern cannot.
70
+ def build_loader(widget_id, script_url)
71
+ "(function(){var l=function(){" \
72
+ "if(document.querySelector(\"script[data-asyntai-id]\"))return;" \
73
+ "var s=document.createElement(\"script\");" \
74
+ "s.src=#{js_string(script_url)};s.async=true;" \
75
+ "s.setAttribute(\"data-asyntai-id\",#{js_string(widget_id)});" \
76
+ "document.head.appendChild(s)};" \
77
+ "if(document.readyState===\"complete\"){l()}" \
78
+ "else{window.addEventListener(\"load\",l)}})();"
79
+ end
80
+
81
+ def build_tag(settings)
82
+ "<script data-asyntai-loader>#{build_loader(settings.widget_id, settings.script_url)}</script>"
83
+ end
84
+
85
+ # Puts the tag before the last </body>, or before </html> when the page has no
86
+ # body tag. A fragment with neither is left alone: it is not a whole page.
87
+ # A page that already carries an Asyntai snippet is left alone too.
88
+ def inject_into_html(html, tag)
89
+ return html unless html.is_a?(String)
90
+ return html if html.include?("data-asyntai-id")
91
+
92
+ lower = html.downcase
93
+ at = lower.rindex("</body>") || lower.rindex("</html>")
94
+ return html if at.nil?
95
+
96
+ html[0...at] + tag + html[at..-1]
97
+ end
98
+
99
+ # Only pages that are written to disk as .html get the widget. Feeds, JSON,
100
+ # CSS, Sass and pages with `published: false` or no output stay as they are.
101
+ def html_output?(document)
102
+ if document.respond_to?(:write?)
103
+ return false unless document.write?
104
+ end
105
+ ext = document.respond_to?(:output_ext) ? document.output_ext.to_s.downcase : ""
106
+ [".html", ".htm"].include?(ext)
107
+ end
108
+
109
+ # A page can opt out with `asyntai: false` in its front matter.
110
+ def opted_out?(document)
111
+ data = document.respond_to?(:data) ? document.data : nil
112
+ data.is_a?(Hash) && data[CONFIG_KEY] == false
113
+ end
114
+
115
+ def apply(document, settings, tag, environment)
116
+ return if !settings.enable_in_development && environment.to_s == "development"
117
+ return unless html_output?(document)
118
+ return if opted_out?(document)
119
+
120
+ document.output = inject_into_html(document.output, tag)
121
+ end
122
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllAiChatbot
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Asyntai AI chatbot for Jekyll.
4
+ #
5
+ # In _config.yml:
6
+ #
7
+ # plugins:
8
+ # - jekyll-ai-chatbot
9
+ #
10
+ # asyntai:
11
+ # widget_id: asyntai_1a2b3c4d5e6f
12
+
13
+ require "jekyll"
14
+ require "jekyll-ai-chatbot/core"
15
+
16
+ module JekyllAiChatbot
17
+ # Settings of the site being built, read once per build.
18
+ @settings = nil
19
+ @tag = nil
20
+
21
+ class << self
22
+ attr_reader :settings, :tag
23
+
24
+ def configure(site)
25
+ @settings = validate_options(site.config[CONFIG_KEY])
26
+ @tag = build_tag(@settings)
27
+ rescue ConfigurationError => e
28
+ raise Jekyll::Errors::FatalException, e.message
29
+ end
30
+ end
31
+ end
32
+
33
+ # `after_reset` runs at the start of every build, also on each rebuild of
34
+ # `jekyll serve`, so a changed widget ID in _config.yml is picked up.
35
+ Jekyll::Hooks.register :site, :after_reset do |site|
36
+ JekyllAiChatbot.configure(site)
37
+ end
38
+
39
+ # Pages, posts and collection documents. Low priority, so the widget goes in
40
+ # after other plugins change the page.
41
+ Jekyll::Hooks.register [:pages, :documents], :post_render, priority: :low do |doc|
42
+ next if JekyllAiChatbot.settings.nil?
43
+
44
+ JekyllAiChatbot.apply(doc, JekyllAiChatbot.settings, JekyllAiChatbot.tag, Jekyll.env)
45
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-ai-chatbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Asyntai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.7'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.7'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ description: Adds the Asyntai AI chatbot to every page of your Jekyll site. It answers
34
+ visitor questions from your own content, in 80+ languages.
35
+ email: hello@asyntai.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - LICENSE
41
+ - README.md
42
+ - lib/jekyll-ai-chatbot.rb
43
+ - lib/jekyll-ai-chatbot/core.rb
44
+ - lib/jekyll-ai-chatbot/version.rb
45
+ homepage: https://asyntai.com/documentation/integrations/jekyll/
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ homepage_uri: https://asyntai.com/documentation/integrations/jekyll/
50
+ source_code_uri: https://github.com/asyntai/jekyll-ai-chatbot
51
+ bug_tracker_uri: https://github.com/asyntai/jekyll-ai-chatbot/issues
52
+ rubygems_mfa_required: 'true'
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '2.5'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.5.22
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Add the Asyntai AI chat assistant to your Jekyll site.
72
+ test_files: []