شارك محادثات روبوت الذكاء الاصطناعي الخاصة بك!

يسعدنا الإعلان عن ميزة جديدة تتيح لك مشاركة محادثاتك مع روبوتات الذكاء الاصطناعي بسهولة على منتدانا. سواء كانت لديك محادثة مفيدة أو مضحكة أو مساعدة مع أحد روبوتات الذكاء الاصطناعي، يمكنك الآن مشاركة هذا التفاعل مع الآخرين ببضع نقرات فقط. إليك ما تحتاج إلى معرفته:

كيف أشارك محادثة مع روبوت ذكاء اصطناعي؟

عند عرض محادثة مع روبوت ذكاء اصطناعي، سترى أيقونة “مشاركة” جديدة في قائمة المنشور.

يعمل النقر على النحو التالي:

  1. للمحادثات القصيرة: إذا كانت المحادثة ضمن طول معين (تصل إلى تفاعلين)، فإن النقر على أيقونة “مشاركة” سيؤدي تلقائيًا إلى نسخ المحادثة بأكملها إلى الحافظة الخاصة بك. يمكنك بعد ذلك لصقها أينما تريد - سواء في منشور جديد أو رسالة خاصة أو خارج المنتدى.

  2. للمحادثات الأطول: بالنسبة للتفاعلات الأكثر طولاً، سيؤدي النقر على أيقونة “مشاركة” إلى فتح نافذة منبثقة مع معاينة لمحادثتك. يمكنك استخدام شريط تمرير لتحديد مقدار المحادثة التي تريد مشاركتها. بمجرد اختيار النطاق المطلوب، انقر فوق الزر “نسخ” لنسخ المحادثة المحددة إلى الحافظة الخاصة بك.

كيف ستبدو المحادثة المشتركة؟

سيتم تنسيق المحادثة المشتركة في كتلة “تفاصيل” قابلة للطي، مما يجعلها سهلة القراءة والإدارة في المنشورات. ستتضمن أسماء المستخدمين ومحتوى الرسائل ضمن النطاق الذي حددته.

مثال:

<details class='ai-quote'>
<summary>
<span>Greetings</span>
<span title='Conversation with AI'>AI</span>
</summary>

**sam:**

say hello

**gpt4_bot:**

Hello! How can I assist you today?
</details>

سيتم عرضه على النحو التالي:

Greetings AI

sam:

say hello

gpt4_bot:

Hello! How can I assist you today?

هل يمكنني مشاركة المحادثات مع أي روبوت ذكاء اصطناعي؟

نعم، يمكنك مشاركة المحادثات مع أي من روبوتات الذكاء الاصطناعي الممكّنة على منتدانا. فقط ابحث عن أيقونة “مشاركة” في قائمة المنشور عند عرض المحادثة.

هل هناك حد لمقدار المحادثة التي يمكنني مشاركتها؟

لا يوجد حد صارم، ولكننا نشجعك على مشاركة الأجزاء ذات الصلة فقط من المحادثة للحفاظ على الإيجاز والتركيز للقراء الآخرين.

أين يمكنني مشاركة هذه المحادثات؟

يمكنك مشاركتها في منشور جديد في المنتدى، أو في رسالة خاصة لمستخدمين آخرين، أو حتى خارج المنتدى - في أي مكان يمكنك لصق نص من الحافظة الخاصة بك.

هل ستتضمن المحادثة المشتركة أي معلومات شخصية؟

لن يتم تضمين أي معلومات شخصية في النص المشترك. سيتم نسخ أسماء المستخدمين ومحتوى الرسائل فقط.

14 إعجابًا

للعلم، قمتُ بالعمل مع GPT-4 لإنشاء هذا المنشور (مع بعض التغييرات الطفيفة)

Discourse AI Bot: ميزة المشاركة الجديدة AI

sam:

المعطيات:

commit 933784a873c781816857699d6d9138baf9f5a7de
Author: Sam <sam.saffron@gmail.com>
Date:   Fri Dec 29 19:47:47 2023 +1100

    FEATURE: allow easy sharing of bot conversations (#385)
    
    * FEATURE: allow easy sharing of bot conversations
    
    * Lean on new core API i
    
    * Added system spec for copy functionality
    
    
    * Update assets/javascripts/initializers/ai-bot-replies.js
    
    Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
    
    * discourse later insted of setTimeout
    
    * Update spec/system/ai_bot/share_spec.rb
    
    Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
    
    * feedback from review
    
    just check the whole payload
    
    * remove uneeded code
    
    * fix spec
    
    ---------
    
    Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>

diff --git a/assets/javascripts/discourse/components/modal/share-modal.gjs b/assets/javascripts/discourse/components/modal/share-modal.gjs
new file mode 100644
index 0000000..b122f62
--- /dev/null
+++ b/assets/javascripts/discourse/components/modal/share-modal.gjs
@@ -0,0 +1,109 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { Input } from "@ember/component";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { htmlSafe } from "@ember/template";
+import DButton from "discourse/components/d-button";
+import DModal from "discourse/components/d-modal";
+import discourseLater from "discourse-common/lib/later";
+import I18n from "I18n";
+import copyConversation from "../../lib/copy-conversation";
+
+const t = I18n.t.bind(I18n);
+
+export default class ShareModal extends Component {
+  @tracked contextValue = 1;
+  @tracked htmlContext = "";
+  @tracked maxContext = 0;
+  @tracked allPosts = [];
+  @tracked justCopiedText = "";
+
+  constructor() {
+    super(...arguments);
+
+    const postStream = this.args.model.topic.get("postStream");
+
+    let postNumbers = [];
+    // simpler to understand than Array.from
+    for (let i = 1; i <= this.args.model.post_number; i++) {
+      postNumbers.push(i);
+    }
+
+    this.allPosts = postNumbers
+      .map((postNumber) => {
+        let postId = postStream.findPostIdForPostNumber(postNumber);
+        if (postId) {
+          return postStream.findLoadedPost(postId);
+        }
+      })
+      .filter((post) => post);
+
+    this.maxContext = this.allPosts.length / 2;
+    this.contextValue = 1;
+
+    this.updateHtmlContext();
+  }
+
+  @action
+  updateHtmlContext() {
+    let context = [];
+
+    const start = this.allPosts.length - this.contextValue * 2;
+    for (let i = start; i < this.allPosts.length; i++) {
+      const post = this.allPosts[i];
+      context.push(`<p><b>${post.username}:</b></p>`);
+      context.push(post.cooked);
+    }
+    this.htmlContext = htmlSafe(context.join("\n"));
+  }
+
+  @action
+  async copyContext() {
+    const from =
+      this.allPosts[this.allPosts.length - this.contextValue * 2].post_number;
+    const to = this.args.model.post_number;
+    await copyConversation(this.args.model.topic, from, to);
+    this.justCopiedText = t("discourse_ai.ai_bot.conversation_shared");
+    discourseLater(() => {
+      this.justCopiedText = "";
+    }, 2000);
+  }
+
+  <template>
+    <DModal
+      class="ai-share-modal"
+      @title={{t "discourse_ai.ai_bot.share_modal.title"}}
+      @closeModal={{@closeModal}}
+    >
+      <:body>
+        <div class="ai-share-modal__preview">
+          {{this.htmlContext}}
+        </div>
+      </:body>
+
+      <:footer>
+        <div class="ai-share-modal__slider">
+          <Input
+            @type="range"
+            min="1"
+            max={{this.maxContext}}
+            @value={{this.contextValue}}
+            {{on "change" this.updateHtmlContext}}
+          />
+          <div class="ai-share-modal__context">
+            {{t "discourse_ai.ai_bot.share_modal.context"}}
+            {{this.contextValue}}
+          </div>
+        </div>
+        <DButton
+          class="btn-primary confirm"
+          @icon="copy"
+          @action={{this.copyContext}}
+          @label="discourse_ai.ai_bot.share_modal.copy"
+        />
+        <span class="ai-share-modal__just-copied">{{this.justCopiedText}}</span>
+      </:footer>
+    </DModal>
+  </template>
+}
diff --git a/assets/javascripts/discourse/lib/copy-conversation.js b/assets/javascripts/discourse/lib/copy-conversation.js
new file mode 100644
index 0000000..bcf3188
--- /dev/null
+++ b/assets/javascripts/discourse/lib/copy-conversation.js
@@ -0,0 +1,55 @@
+import { ajax } from "discourse/lib/ajax";
+import { clipboardCopy } from "discourse/lib/utilities";
+import I18n from "discourse-i18n";
+
+export default async function (topic, fromPostNumber, toPostNumber) {
+  const stream = topic.get("postStream");
+
+  let postNumbers = [];
+  // simpler to understand than Array.from
+  for (let i = fromPostNumber; i <= toPostNumber; i++) {
+    postNumbers.push(i);
+  }
+
+  const postIds = postNumbers.map((postNumber) => {
+    return stream.findPostIdForPostNumber(postNumber);
+  });
+
+  // we need raw to construct so post stream will not help
+
+  const url = `/t/${topic.id}/posts.json`;
+  const data = {
+    post_ids: postIds,
+    include_raw: true,
+  };
+
+  const response = await ajax(url, { data });
+
+  let buffer = [];
+  buffer.push("<details class='ai-quote'>");
+  buffer.push("<summary>");
+  buffer.push(`<span>${topic.title}</span>`);
+  buffer.push(
+    `<span title='${I18n.t("discourse_ai.ai_bot.ai_title")}'>${I18n.t(
+      "discourse_ai.ai_bot.ai_label"
+    )}</span>`
+  );
+  buffer.push("</summary>");
+
+  response.post_stream.posts.forEach((post) => {
+    buffer.push("");
+    buffer.push(`**${post.username}:**`);
+    buffer.push("");
+    buffer.push(post.raw);
+  });
+
+  buffer.push("</details>");
+
+  const text = buffer.join("\n");
+
+  if (window.discourseAiTestClipboard) {
+    window.discourseAiClipboard = text;
+  }
+
+  await clipboardCopy(text);
+}
diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js
index e136963..050e1ed 100644
--- a/assets/javascripts/initializers/ai-bot-replies.js
+++ b/assets/javascripts/initializers/ai-bot-replies.js
@@ -6,6 +6,10 @@ import { withPluginApi } from "discourse/lib/plugin-api";
 import { cook } from "discourse/lib/text";
 import { registerWidgetShim } from "discourse/widgets/render-glimmer";
 import { composeAiBotMessage } from "discourse/plugins/discourse-ai/discourse/lib/ai-bot-helper";
+import ShareModal from "../discourse/components/modal/share-modal";
+import copyConversation from "../discourse/lib/copy-conversation";
+
+const AUTO_COPY_THRESHOLD = 4;
 
 function isGPTBot(user) {
   return user && [-110, -111, -112, -113].includes(user.id);
@@ -166,6 +170,43 @@ function initializePersonaDecorator(api) {
   );
 }
 
+function initializeShareButton(api) {
+  const currentUser = api.getCurrentUser();
+  if (!currentUser || !currentUser.ai_enabled_chat_bots) {
+    return;
+  }
+
+  let shareAiResponse = async function ({ post, showFeedback }) {
+    if (post.post_number <= AUTO_COPY_THRESHOLD) {
+      await copyConversation(post.topic, 1, post.post_number);
+      showFeedback("discourse_ai.ai_bot.conversation_shared");
+    } else {
+      modal.show(ShareModal, { model: post });
+    }
+  };
+
+  api.addPostMenuButton("share", (post) => {
+    // very hacky and ugly, but there is no `.topic` in attrs
+    if (
+      !currentUser.ai_enabled_chat_bots.any(
+        (bot) => post.username === bot.username
+      )
+    ) {
+      return;
+    }
+
+    return {
+      action: shareAiResponse,
+      icon: "share",
+      className: "post-action-menu__share",
+      title: "discourse_ai.ai_bot.share",
+      position: "first",
+    };
+  });
+
+  const modal = api.container.lookup("service:modal");
+}
+
 export default {
   name: "discourse-ai-bot-replies",
 
@@ -179,6 +220,7 @@ export default {
       }
       withPluginApi("1.6.0", initializeAIBotReplies);
       withPluginApi("1.6.0", initializePersonaDecorator);
+      withPluginApi("1.22.0", (api) => initializeShareButton(api, container));
     }
   },
 };
diff --git a/assets/javascripts/lib/discourse-markdown/ai-tags.js b/assets/javascripts/lib/discourse-markdown/ai-tags.js
new file mode 100644
index 0000000..c2d9b67
--- /dev/null
+++ b/assets/javascripts/lib/discourse-markdown/ai-tags.js
@@ -0,0 +1,3 @@
+export function setup(helper) {
+  helper.allowList(["details[class=ai-quote]"]);
+}
diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
index 4943b9b..2a8ba80 100644
--- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
+++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
@@ -67,3 +67,37 @@ article.streaming nav.post-controls .actions button.cancel-streaming {
   font-size: var(--font-down-1);
   padding-top: 3px;
 }
+
+details.ai-quote {
+  > summary {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    span:first-child {
+      margin-right: auto;
+    }
+    span:nth-child(2) {
+      font-size: var(--font-down-2);
+      background: var(--primary-medium);
+      padding: 2px 6px 0;
+      color: var(--secondary);
+    }
+  }
+}
+
+.ai-share-modal {
+  .d-modal__footer {
+    position: relative;
+    padding: 10px 20px 25px;
+    .btn-primary {
+      margin-left: auto;
+    }
+  }
+  &__just-copied {
+    position: absolute;
+    font-size: var(--font-down-1);
+    right: 20px;
+    bottom: 5px;
+    color: var(--success);
+  }
+}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index e13f953..e2f2e7d 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -180,6 +180,16 @@ en:
         cancel_streaming: "Stop reply"
         default_pm_prefix: "[Untitled AI bot PM]"
         shortcut_title: "Start a PM with an AI bot"
+        share: "Share AI conversation"
+        conversation_shared: "Conversation copied to clipboard"
+
+        ai_label: "AI"
+        ai_title: "Conversation with AI"
+
+        share_modal:
+          title: "Share AI conversation"
+          copy: "Copy"
+          context: "Interactions to share:"
 
         bot_names:
           gpt-4: "GPT-4"
diff --git a/spec/system/ai_bot/share_spec.rb b/spec/system/ai_bot/share_spec.rb
new file mode 100644
index 0000000..708e96b
--- /dev/null
+++ b/spec/system/ai_bot/share_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+RSpec.describe "Share conversation", type: :system do
+  fab!(:admin) { Fabricate(:admin, username: "ai_sharer") }
+  let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
+
+  let(:pm) do
+    Fabricate(
+      :private_message_topic,
+      title: "This is my special PM",
+      user: admin,
+      topic_allowed_users: [
+        Fabricate.build(:topic_allowed_user, user: admin),
+        Fabricate.build(:topic_allowed_user, user: bot_user),
+      ],
+    )
+  end
+
+  let(:pm_posts) do
+    posts = []
+    i = 1
+    3.times do
+      posts << Fabricate(:post, topic: pm, user: admin, raw: "test test test user reply #{i}")
+      posts << Fabricate(:post, topic: pm, user: bot_user, raw: "test test test bot reply #{i}")
+      i += 1
+    end
+
+    posts
+  end
+
+  before do
+    SiteSetting.ai_bot_enabled = true
+    SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+    sign_in(admin)
+
+    bot_user.update!(username: "gpt-4")
+
+    Group.refresh_automatic_groups!
+    pm
+    pm_posts
+  end
+
+  it "can share a conversation" do
+    clip_text = nil
+
+    visit(pm.url)
+
+    # clipboard functionality is extremely hard to test
+    # we would need special permissions in chrome driver to enable full access
+    # instead we use a secret variable to signal that we want to store clipboard
+    # data in window.discourseAiClipboard
+    page.execute_script("window.discourseAiTestClipboard = true")
+
+    find("#post_2 .post-action-menu__share").click
+
+    try_until_success do
+      clip_text = page.evaluate_script("window.discourseAiClipboard")
+      expect(clip_text).to be_present
+    end
+
+    conversation = (<<~TEXT).strip
+      <details class='ai-quote'>
+      <summary>
+      <span>This is my special PM</span>
+      <span title='Conversation with AI'>AI</span>
+      </summary>
+
+      **ai_sharer:**
+
+      test test test user reply 1
+
+      **gpt-4:**
+
+      test test test bot reply 1
+      </details>
+    TEXT
+
+    expect(conversation).to eq(clip_text)
+
+    # Test modal functionality as well
+    page.evaluate_script("window.discourseAiClipboard = null")
+
+    find("#post_6 .post-action-menu__share").click
+    find(".ai-share-modal__slider input").set("2")
+    find(".ai-share-modal button.btn-primary").click
+
+    try_until_success do
+      clip_text = page.evaluate_script("window.discourseAiClipboard")
+      expect(clip_text).to be_present
+    end
+
+    conversation = (<<~TEXT).strip
+      <details class='ai-quote'>
+      <summary>
+      <span>This is my special PM</span>
+      <span title='Conversation with AI'>AI</span>
+      </summary>
+
+      **ai_sharer:**
+
+      test test test user reply 2
+
+      **gpt-4:**
+
+      test test test bot reply 2
+
+      **ai_sharer:**
+
+      test test test user reply 3
+
+      **gpt-4:**
+
+      test test test bot reply 3
+      </details>
+    TEXT
+
+    expect(conversation).to eq(clip_text)
+  end
+end

اشرح الميزة الجديدة في Discourse AI

gpt4_bot:

تتيح الميزة الجديدة المُدخَلة في Discourse AI للمستخدمين مشاركة المحادثات التي أجروها مع روبوتات الذكاء الاصطناعي داخل منصة Discourse بسهولة. هذه الميزة مفيدة بشكل خاص للمستخدمين الذين يرغبون في مشاركة الأفكار أو المعلومات التي تلقوها من التفاعل مع روبوتات الذكاء الاصطناعي مع مستخدمين آخرين أو للاستخدام الشخصي كمرجع.

إليك تفصيل لكيفية عمل الميزة والتغييرات التي أُجريت:

  1. مكون نافذة المشاركة (Share Modal): تم إضافة مكون Glimmer جديد (ShareModal) يوفر واجهة مستخدم لاختيار نطاق المحادثة المراد مشاركتها ونسخها إلى الحافظة. تتضمن هذه النافذة شريط تمرير لاختيار مقدار السياق من المحادثة المراد تضمينه عند المشاركة.

  2. وظيفة نسخ المحادثة: تم تنفيذ دالة جافا سكريبت جديدة (copyConversation) للتعامل مع نسخ نص المحادثة فعليًا. تقوم هذه الدالة ببناء المحادثة عن طريق جلب المنشورات الخام بين أرقام المنشورات المحددة وتنسيقها باستخدام Markdown، بما في ذلك وسم details (<details class='ai-quote'>) لجعل المحتوى المشترك قابلًا للطي.

  3. زر قائمة المنشور: تم إضافة زر جديد في قائمة المنشور (share) يُفعّل وظيفة المشاركة. إذا كانت المحادثة قصيرة (حتى حد معين)، سيتم نسخها تلقائيًا إلى الحافظة. أما في حالة المحادثات الأطول، فستظهر نافذة المشاركة، مما يسمح للمستخدم باختيار جزء المحادثة المراد مشاركته.

  4. التنسيق: تم إضافة أنماط SCSS جديدة (bot-replies.scss) لتنسيق نافذة المشاركة ووسم التفاصيل الخاص بالمحادثات المشتركة.

  5. تغييرات اللغة: تم تحديث ملف client.en.yml ليشمل سلاسل لغة جديدة لميزة المشاركة، مثل تسميات الأزرار وتلميحات الأدوات.

  6. اختبار النظام: تم إضافة اختبار نظام جديد (share_spec.rb) لضمان عمل وظيفة المشاركة كما هو متوقع.

  7. السماح بوسوم Markdown: تم إنشاء ملف ai-tags.js للسماح بوسم details[class=ai-quote> المخصص المستخدم للمحادثات المشتركة، بحيث يُسمح به في مُصنّف Markdown في Discourse.

  8. المؤلفية المشتركة: يشير رسالة الالتزام (commit message) إلى أن آلان غو شيانغ تان شارك في تأليف بعض التغييرات، مما يشير إلى جهد تعاوني في تطوير هذه الميزة.

بشكل عام، تعزز هذه الميزة تجربة المستخدم من خلال تسهيل مشاركة التفاعلات القيمة مع روبوتات الذكاء الاصطناعي، وتعزيز التعاون وتبادل المعرفة داخل مجتمع Discourse.

sam:

حاول شرح الميزة لمستخدمي المنتدى بالإجابة على الأسئلة الشائعة التي قد تكون لديهم عنها.

gpt4_bot:

إعلان عن ميزة جديدة: شارك محادثات روبوت الذكاء الاصطناعي!

يسعدنا الإعلان عن ميزة جديدة تتيح لك مشاركة محادثاتك مع روبوتات الذكاء الاصطناعي على منتدى موقعنا بسهولة. سواء كانت لديك تجربة عميقة أو مضحكة أو مفيدة مع أحد روبوتات الذكاء الاصطناعي لدينا، يمكنك الآن مشاركة هذا التفاعل مع الآخرين بنقرات قليلة. إليك ما تحتاج لمعرفته:

كيف يمكنني مشاركة محادثة مع روبوت ذكاء اصطناعي؟

عند عرض محادثة مع روبوت ذكاء اصطناعي، ستلاحظ أيقونة “مشاركة” جديدة في قائمة المنشور. عند النقر على هذه الأيقونة، ستظهر لك خياران:

  1. للمحادثات القصيرة: إذا كانت المحادثة ضمن طول معين (حتى أربع تفاعلات)، فإن النقر على أيقونة “مشاركة” سيقوم بنسخ المحادثة بالكامل تلقائيًا إلى حافظة جهازك. يمكنك بعد ذلك لصقها في أي مكان تريده، سواء في منشور جديد، أو رسالة خاصة، أو خارج المنتدى.

  2. للمحادثات الأطول: بالنسبة للتفاعلات الأطول، سيؤدي النقر على أيقونة “مشاركة” إلى فتح نافذة تحتوي على معاينة لمحادثتك. يمكنك استخدام شريط التمرير لتحديد مقدار المحادثة التي ترغب في مشاركتها. بمجرد اختيار النطاق المطلوب، انقر على زر “نسخ” لنسخ المحادثة المحددة إلى حافظة جهازك.

كيف ستبدو المحادثة المشتركة؟

ستكون المحادثة المشتركة منسقة في كتلة قابلة للطي من نوع “details”، مما يجعلها سهلة القراءة والإدارة في المنشورات. ستشمل أسماء المستخدمين ومحتوى الرسائل ضمن النطاق الذي اخترته.

هل يمكنني مشاركة محادثات مع أي روبوت ذكاء اصطناعي؟

نعم، يمكنك مشاركة المحادثات مع أي من روبوتات الذكاء الاصطناعي المفعلة على منتدى موقعنا. ابحث ببساطة عن أيقونة “مشاركة” في قائمة المنشور عند عرض المحادثة.

هل هناك حد لمدى المحادثة التي يمكنني مشاركتها؟

لا يوجد حد صارم، لكننا نشجعك على مشاركة الأجزاء ذات الصلة فقط من المحادثة للحفاظ على الإيجاز والتركيز للقراء الآخرين.

أين يمكنني مشاركة هذه المحادثات؟

يمكنك مشاركتها في منشور جديد على المنتدى، أو في رسالة خاصة لمستخدمين آخرين، أو حتى خارج المنتدى، في أي مكان يمكنك فيه لصق النص من حافظة جهازك.

هل ستشمل المحادثة المشتركة أي معلومات شخصية؟

لا، لن يتم تضمين أي معلومات شخصية في النص المشترك. سيتم نسخ أسماء المستخدمين ومحتوى الرسائل فقط.

ماذا لو واجهت أي مشاكل مع ميزة المشاركة؟

إذا واجهت أي مشاكل أو كان لديك ملاحظات حول ميزة المشاركة، فلا تتردد في التواصل مع فريق الدعم الخاص بنا أو النشر في قسم التعليقات في منتدى موقعنا. نحن هنا للمساعدة ونسعى دائمًا لتحسين تجربتك.

نأمل أن تستمتع بهذه الميزة الجديدة ونتطلع لرؤية المحادثات المثيرة التي ستشاركها!

12 إعجابًا