{"id":487952,"date":"2026-07-21T05:36:55","date_gmt":"2026-07-21T05:36:55","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=487952"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=487952","title":{"rendered":"Mermaid \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u043d\u0430\u044f AI \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 Django\/Next"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Mermaid \u044d\u0442\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c. \u0412 \u0441\u0442\u0440\u043e\u043a\u0430\u0445 \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u0438\u043f \u0441\u0445\u0435\u043c\u044b, \u0443\u0437\u043b\u044b \u0438 \u0441\u0432\u044f\u0437\u0438. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f SVG. \u0424\u043e\u0440\u043c\u0430\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f API \u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c, \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435.<\/p>\n<p>\u0412 <code>mermind\/<\/code><a href=\"http:\/\/views.py\" rel=\"noopener noreferrer nofollow\"><code>views.py<\/code><\/a> \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d <code>503<\/code>, \u0435\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439 \u0433\u043e\u043b\u043e\u0432\u044b Mermaid. \u0411\u0435\u0437 \u044d\u0442\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442, \u043d\u043e \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u043d\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f. \u0412 \u043e\u0442\u0432\u0435\u0442\u0435 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f fenced-\u0431\u043b\u043e\u043a\u0438, Markdown, \u0441\u0442\u0440\u043e\u043a\u0438 \u0441 <code>#<\/code>, \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0438 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u044b \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441\u043e\u0431\u0440\u0430\u043d \u043f\u043e\u043b\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0443\u0440. \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f, \u043e\u0447\u0438\u0441\u0442\u043a\u0430 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430, \u0440\u0435\u043d\u0434\u0435\u0440, \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043f\u0440\u0430\u0432\u043a\u0430, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430.<\/p>\n<p>\u041a\u0430\u043a \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c Mermaid-\u043a\u043e\u0434 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438. \u041e\u0442\u0432\u0435\u0442 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0435\u0436\u0435\u0442\u0441\u044f \u0434\u043e fenced-\u0431\u043b\u043e\u043a\u0430. \u041f\u043e\u0442\u043e\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430.<\/p>\n<pre><code class=\"python\">def extract_fenced(text: str) -&gt; str:    if not text:        return \"\"    m = re.search(r\"```(?:mermaid)?\\s*([\\s\\S]*?)```\", text, re.IGNORECASE)    return (m.group(1).strip() if m else text.strip())def looks_like_mermaid(code: str) -&gt; bool:    head = (code or \"\").strip().lower()    return head.startswith(MERMAID_HEADS)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0448\u0430\u0433 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 <code>sanitize_mermaid<\/code>. \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0438\u0449\u0435\u0442 \u043f\u0435\u0440\u0432\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u043d\u0443\u044e \u0433\u043e\u043b\u043e\u0432\u0443 Mermaid, \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0438 \u0434\u043e \u043d\u0435\u0435 \u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0438\u0437 <code>#<\/code> \u0432 <code>%%<\/code>.<\/p>\n<pre><code class=\"python\">def sanitize_mermaid(raw: str) -&gt; str:    s = extract_fenced(raw)    lines = (s or \"\").splitlines()    out, started = [], False    preface_comments = []    for ln in lines:        stripped = ln.strip()        if stripped.startswith(\"```\"):            break        if not started:            if stripped.lower().startswith(MERMAID_HEADS):                started = True                out.append(ln)            else:                if stripped.startswith(\"%%\") or stripped.startswith(\"#\"):                    preface_comments.append(_to_mermaid_comment(ln))            continue        out.append(_to_mermaid_comment(ln))    cleaned = \"\\n\".join(out).strip() or s.strip()    if preface_comments and cleaned:        cleaned = f\"{cleaned}\\n\\n\" + \"\\n\".join(preface_comments)    return cleaned<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e Mermaid-\u043a\u043e\u0434, \u043f\u0440\u0438\u0433\u043e\u0434\u043d\u044b\u0439 \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0448\u0430\u0433\u0430.<\/p>\n<h3>\u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439<\/h3>\n<p>\u0412 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443, \u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0434\u0438\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c \u0431\u0440\u0435\u043d\u0434\u043e\u0432 \u0438 \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u0432. \u041c\u043e\u0434\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c <code>Yandex Pay<\/code>, <code>Yookassa<\/code>, <code>\u042e\u043a\u0430\u0441\u0441\u0430<\/code>, <code>\u042e Kassa<\/code>. \u0412 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u0439 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u044d\u0442\u043e \u0434\u0430\u0435\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u0438\u043c\u0435\u043d\u0438.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 <code>normalize_brand_names<\/code>.<\/p>\n<pre><code class=\"python\">_BRAND_FIXES = [    (re.compile(r'\\bYandex\\s*Pay\\b', re.IGNORECASE), \"YooKassa\"),    (re.compile(r'\\bYandexPay\\b', re.IGNORECASE), \"YooKassa\"),    (re.compile(r'\\bYo+ka?ssa\\b', re.IGNORECASE), \"YooKassa\"),    (re.compile(r'\\b\u042e\\s*\u041a\u0430\u0441\u0441\u0430\\b', re.IGNORECASE), \"\u042eKassa\"),    (re.compile(r'\\b\u042e\u043a\u0430\u0441\u0441\u0430\\b', re.IGNORECASE), \"\u042eKassa\"),]<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f, \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c.<\/p>\n<h3>\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0432 \u043a\u043e\u0434\u0435 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 <code>%%<\/code>. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0431\u0430\u0437\u0435 \u0438 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435. \u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u043d\u0434\u0435\u0440\u043e\u043c \u043e\u043d\u0438 \u0443\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f. \u0412 <code>useMermaidRender<\/code> \u043a\u043e\u0434 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0447\u0438\u0449\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u043f\u043e\u0442\u043e\u043c \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 <code>parse<\/code>, \u043f\u043e\u0442\u043e\u043c \u0438\u0434\u0435\u0442 <code>render<\/code>.<\/p>\n<pre><code class=\"javascript\">function stripMermaidCommentsForRender(src: string) {  return src.split(\/\\r?\\n\/).filter(ln =&gt; !ln.trimStart().startsWith(\"%%\")).join(\"\\n\");}const renderCode = stripMermaidCommentsForRender(code);try {  await mermaid.parse(renderCode);} catch (e) {  hostEl.innerHTML =    `&lt;div style=\"padding:12px;text-align:center;color:#b91c1c;background:#fee2e2;border-radius:6px;\"&gt;       \u041e\u0448\u0438\u0431\u043a\u0430 \u0432 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435: ${(e as Error)?.message || \"Syntax error\"}     &lt;\/div&gt;`;  return;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f. \u0412 \u0440\u0435\u043d\u0434\u0435\u0440 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.<\/p>\n<h3>\u041f\u0443\u043b \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041e\u0434\u0438\u043d <code>model_id<\/code> \u043d\u0435 \u0434\u0430\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041e\u0442\u0432\u0435\u0442 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u0431\u0435\u0437 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439 \u0433\u043e\u043b\u043e\u0432\u044b, \u0441 \u043b\u0438\u0448\u043d\u0438\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u043c \u0438\u043b\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 <code>looks_like_mermaid<\/code>.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 \u043f\u0443\u043b\u0430.<\/p>\n<pre><code class=\"python\">next_model = pick_next_model_after(used or model)if next_model:    out2, used2 = query_openrouter(        prompt=user,        model_id=next_model,        language=lang,        system_prompt=sys,        temperature=0.3,    )    code2 = sanitize_mermaid(_clean_or_template(out2, t))    code2 = normalize_brand_names(code2)    if looks_like_mermaid(code2):        return t, code2, [\"fallback_used\"], (used2 or next_model)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0443\u043b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432 <code>model_<\/code><a href=\"http:\/\/pool.py\" rel=\"noopener noreferrer nofollow\"><code>pool.py<\/code><\/a>.<\/p>\n<pre><code class=\"python\">def get_model_pool() -&gt; List[str]:    pool = cache.get(POOL_CACHE_KEY)    if isinstance(pool, list) and pool:        return pool    top = get_top_models() or {}    code = [m[\"model_id\"] for m in top.get(\"code_models\", [])]    text = [m[\"model_id\"] for m in top.get(\"text_models\", [])]    pool = _dedup_keep_order([*code, *text])    cache.set(POOL_CACHE_KEY, pool, POOL_TTL_SEC)    return pool<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0438\u0434\u0435\u0442 \u043f\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u044f\u0434\u043a\u0443 \u043f\u0443\u043b\u0430.<\/p>\n<h3>\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043f\u0440\u0430\u0432\u043a\u0430<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430, \u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u0430 \u043a\u0430\u043a \u043d\u0430\u0434\u043e, \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u044f\u0437\u044c, \u0443\u0431\u0440\u0430\u0442\u044c \u0431\u043b\u043e\u043a, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u044c, \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>adjust_mermaid<\/code>. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u0434 \u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u043e\u0432\u044b\u0439 Mermaid-\u043a\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0439 \u0436\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<pre><code class=\"python\">@api_view([\"POST\"])@permission_classes([IsAuthenticated])def adjust_mermaid(request):    code = (request.data.get(\"code\") or \"\").strip()    t = request.data.get(\"type\") or \"flowchart\"    instr = (request.data.get(\"instruction\") or \"\").strip()    system = \"\"\"        \u0422\u044b \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u0448\u044c Mermaid \u043a\u043e\u0434.        \u0412\u0435\u0440\u043d\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434 Mermaid \u0432 fenced-\u0431\u043b\u043e\u043a\u0435.        \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u0434\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 %%.    \"\"\".strip()    user = f\"\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f:\\n{instr}\\n\\n\u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u0434:\\n```mermaid\\n{code}\\n```\"    out, used = query_openrouter(...)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0431\u043b\u043e\u043a <code>MermindAdjust<\/code>. \u041f\u0440\u0430\u0432\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.<\/p>\n<h3>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/h3>\n<p>\u0414\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c \u043a\u0430\u043a\u00a0\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432\u00a0\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u043e\u0434\u0435\u043b\u044c <code>Diagram<\/code>.<\/p>\n<pre><code class=\"python\">class Diagram(models.Model):    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name=\"diagrams\")    title = models.CharField(max_length=200, blank=True)    source_text = models.TextField()    type = models.CharField(max_length=20, choices=TYPE_CHOICES, db_index=True)    code = models.TextField()    model_used = models.CharField(max_length=100, blank=True, default=\"\")    warnings = models.JSONField(default=list, blank=True)    tags = models.CharField(max_length=200, blank=True)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>save_diagram<\/code>. \u041e\u0431\u044a\u0435\u043a\u0442 \u043b\u0438\u0431\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f.<\/p>\n<pre><code class=\"python\">@api_view([\"POST\"])@permission_classes([IsAuthenticated])def save_diagram(request):    data = request.data.copy()    data[\"language\"] = \"ru\"    ...    if diagram_id:        obj = get_object_or_404(Diagram, pk=diagram_id, user=request.user)        ser = DiagramSerializer(obj, data=data, partial=True)    else:        ser = DiagramSerializer(data=data)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>useMermindLibrary<\/code>. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a, \u0444\u0438\u043b\u044c\u0442\u0440 \u043f\u043e \u0442\u0438\u043f\u0443, \u0442\u0435\u0433\u0438, rename, delete \u0438 open.<\/p>\n<pre><code class=\"javascript\">const fetchList = useCallback(async () =&gt; {  setLoading(true);  try {    const data = await api.listDiagrams({ q, type, tags, limit });    setItems(data || []);  } finally { setLoading(false); }}, [q, type, tags, limit]);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u0412 <code>generate_mermaid<\/code> \u0441\u0435\u0440\u0432\u0435\u0440 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 Mermaid-\u043a\u043e\u0434 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438. \u0412 <a href=\"http:\/\/normalize.py\" rel=\"noopener noreferrer nofollow\"><code>normalize.py<\/code><\/a> \u043a\u043e\u0434 \u043e\u0447\u0438\u0449\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043a \u0435\u0434\u0438\u043d\u043e\u043c\u0443 \u0432\u0438\u0434\u0443. \u0412 <code>useMermaidRender<\/code> \u043a\u043e\u0434 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u0431\u0435\u0437 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432. \u0412 <code>model_<\/code><a href=\"http:\/\/pool.py\" rel=\"noopener noreferrer nofollow\"><code>pool.py<\/code><\/a> \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430. \u0412 <code>adjust_mermaid<\/code> \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u0412 <code>save_diagram<\/code> \u0438 <code>list_diagrams<\/code> \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443.<\/p>\n<p>\u0422\u0430\u043a\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u0432\u043e\u043e\u0431\u0449\u0435\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043b\u044f Mermaid \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u043d\u043e\u0439 AI-\u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u043f\u0440\u043e\u0435\u043a\u0442 <a href=\"https:\/\/ai-chat-frontend-wy6h.onrender.com\/\" rel=\"noopener noreferrer nofollow\">AI-Chat<\/a>. <\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1061160\/\">https:\/\/habr.com\/ru\/articles\/1061160\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mermaid \u044d\u0442\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c. \u0412 \u0441\u0442\u0440\u043e\u043a\u0430\u0445 \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u0438\u043f \u0441\u0445\u0435\u043c\u044b, \u0443\u0437\u043b\u044b \u0438 \u0441\u0432\u044f\u0437\u0438. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f SVG. \u0424\u043e\u0440\u043c\u0430\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f API \u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c, \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435.\u0412 mermind\/views.py \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d 503, \u0435\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439 \u0433\u043e\u043b\u043e\u0432\u044b Mermaid. \u0411\u0435\u0437 \u044d\u0442\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442, \u043d\u043e \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u043d\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f. \u0412 \u043e\u0442\u0432\u0435\u0442\u0435 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f fenced-\u0431\u043b\u043e\u043a\u0438, Markdown, \u0441\u0442\u0440\u043e\u043a\u0438 \u0441 #, \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0438 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u044b \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441\u043e\u0431\u0440\u0430\u043d \u043f\u043e\u043b\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0443\u0440. \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f, \u043e\u0447\u0438\u0441\u0442\u043a\u0430 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430, \u0440\u0435\u043d\u0434\u0435\u0440, \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043f\u0440\u0430\u0432\u043a\u0430, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430.\u041a\u0430\u043a \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c Mermaid-\u043a\u043e\u0434 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438. \u041e\u0442\u0432\u0435\u0442 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0435\u0436\u0435\u0442\u0441\u044f \u0434\u043e fenced-\u0431\u043b\u043e\u043a\u0430. \u041f\u043e\u0442\u043e\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430.def extract_fenced(text: str) -&gt; str:    if not text:        return &#171;&#187;    m = re.search(r&#187;&#171;`(?:mermaid)?\\s*([\\s\\S]*?)&#171;`&#187;, text, re.IGNORECASE)    return (m.group(1).strip() if m else text.strip())def looks_like_mermaid(code: str) -&gt; bool:    head = (code or &#171;&#187;).strip().lower()    return head.startswith(MERMAID_HEADS)\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0448\u0430\u0433 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 sanitize_mermaid. \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0438\u0449\u0435\u0442 \u043f\u0435\u0440\u0432\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u043d\u0443\u044e \u0433\u043e\u043b\u043e\u0432\u0443 Mermaid, \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0438 \u0434\u043e \u043d\u0435\u0435 \u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0438\u0437 # \u0432 %%.def sanitize_mermaid(raw: str) -&gt; str:    s = extract_fenced(raw)    lines = (s or &#171;&#187;).splitlines()    out, started = [], False    preface_comments = []    for ln in lines:        stripped = ln.strip()        if stripped.startswith(&#171;&#171;`&#187;):            break        if not started:            if stripped.lower().startswith(MERMAID_HEADS):                started = True                out.append(ln)            else:                if stripped.startswith(&#171;%%&#187;) or stripped.startswith(&#171;#&#187;):                    preface_comments.append(_to_mermaid_comment(ln))            continue        out.append(_to_mermaid_comment(ln))    cleaned = &#171;\\n&#187;.join(out).strip() or s.strip()    if preface_comments and cleaned:        cleaned = f&#187;{cleaned}\\n\\n&#187; + &#171;\\n&#187;.join(preface_comments)    return cleaned\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e Mermaid-\u043a\u043e\u0434, \u043f\u0440\u0438\u0433\u043e\u0434\u043d\u044b\u0439 \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0448\u0430\u0433\u0430.\u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439\u0412 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443, \u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0434\u0438\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c \u0431\u0440\u0435\u043d\u0434\u043e\u0432 \u0438 \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u0432. \u041c\u043e\u0434\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c Yandex Pay, Yookassa, \u042e\u043a\u0430\u0441\u0441\u0430, \u042e Kassa. \u0412 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u0439 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u044d\u0442\u043e \u0434\u0430\u0435\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u0438\u043c\u0435\u043d\u0438.\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 normalize_brand_names._BRAND_FIXES = [    (re.compile(r&#8217;\\bYandex\\s*Pay\\b&#8217;, re.IGNORECASE), &#171;YooKassa&#187;),    (re.compile(r&#8217;\\bYandexPay\\b&#8217;, re.IGNORECASE), &#171;YooKassa&#187;),    (re.compile(r&#8217;\\bYo+ka?ssa\\b&#8217;, re.IGNORECASE), &#171;YooKassa&#187;),    (re.compile(r&#8217;\\b\u042e\\s*\u041a\u0430\u0441\u0441\u0430\\b&#8217;, re.IGNORECASE), &#171;\u042eKassa&#187;),    (re.compile(r&#8217;\\b\u042e\u043a\u0430\u0441\u0441\u0430\\b&#8217;, re.IGNORECASE), &#171;\u042eKassa&#187;),]\u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f, \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c.\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u041f\u043e\u0441\u043b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0432 \u043a\u043e\u0434\u0435 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 %%. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0431\u0430\u0437\u0435 \u0438 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435. \u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u043d\u0434\u0435\u0440\u043e\u043c \u043e\u043d\u0438 \u0443\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f. \u0412 useMermaidRender \u043a\u043e\u0434 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0447\u0438\u0449\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u043f\u043e\u0442\u043e\u043c \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 parse, \u043f\u043e\u0442\u043e\u043c \u0438\u0434\u0435\u0442 render.function stripMermaidCommentsForRender(src: string) {  return src.split(\/\\r?\\n\/).filter(ln =&gt; !ln.trimStart().startsWith(&#171;%%&#187;)).join(&#171;\\n&#187;);}const renderCode = stripMermaidCommentsForRender(code);try {  await mermaid.parse(renderCode);} catch (e) {  hostEl.innerHTML =    `&lt;div style=&#187;padding:12px;text-align:center;color:#b91c1c;background:#fee2e2;border-radius:6px;&#187;&gt;       \u041e\u0448\u0438\u0431\u043a\u0430 \u0432 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435: ${(e as Error)?.message || &#171;Syntax error&#187;}     &lt;\/div&gt;`;  return;}\u0412 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f. \u0412 \u0440\u0435\u043d\u0434\u0435\u0440 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.\u041f\u0443\u043b \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438\u041e\u0434\u0438\u043d model_id \u043d\u0435 \u0434\u0430\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041e\u0442\u0432\u0435\u0442 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u0431\u0435\u0437 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439 \u0433\u043e\u043b\u043e\u0432\u044b, \u0441 \u043b\u0438\u0448\u043d\u0438\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u043c \u0438\u043b\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 looks_like_mermaid.\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 \u043f\u0443\u043b\u0430.next_model = pick_next_model_after(used or model)if next_model:    out2, used2 = query_openrouter(        prompt=user,        model_id=next_model,        language=lang,        system_prompt=sys,        temperature=0.3,    )    code2 = sanitize_mermaid(_clean_or_template(out2, t))    code2 = normalize_brand_names(code2)    if looks_like_mermaid(code2):        return t, code2, [&#171;fallback_used&#187;], (used2 or next_model)\u041f\u0443\u043b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432 model_pool.py.def get_model_pool() -&gt; List[str]:    pool = cache.get(POOL_CACHE_KEY)    if isinstance(pool, list) and pool:        return pool    top = get_top_models() or {}    code = [m[&#171;model_id&#187;] for m in top.get(&#171;code_models&#187;, [])]    text = [m[&#171;model_id&#187;] for m in top.get(&#171;text_models&#187;, [])]    pool = _dedup_keep_order([*code, *text])    cache.set(POOL_CACHE_KEY, pool, POOL_TTL_SEC)    return pool\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0438\u0434\u0435\u0442 \u043f\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u044f\u0434\u043a\u0443 \u043f\u0443\u043b\u0430.\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043f\u0440\u0430\u0432\u043a\u0430\u041f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430, \u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u0430 \u043a\u0430\u043a \u043d\u0430\u0434\u043e, \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u044f\u0437\u044c, \u0443\u0431\u0440\u0430\u0442\u044c \u0431\u043b\u043e\u043a, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u044c, \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443.\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f adjust_mermaid. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u0434 \u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f. \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u043e\u0432\u044b\u0439 Mermaid-\u043a\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0439 \u0436\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.@api_view([&#171;POST&#187;])@permission_classes([IsAuthenticated])def adjust_mermaid(request):    code = (request.data.get(&#171;code&#187;) or &#171;&#187;).strip()    t = request.data.get(&#171;type&#187;) or &#171;flowchart&#187;    instr = (request.data.get(&#171;instruction&#187;) or &#171;&#187;).strip()    system = &#171;&#187;&#187;        \u0422\u044b \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u0448\u044c Mermaid \u043a\u043e\u0434.        \u0412\u0435\u0440\u043d\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434 Mermaid \u0432 fenced-\u0431\u043b\u043e\u043a\u0435.        \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u0434\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 %%.    &#171;&#187;&#187;.strip()    user = f&#187;\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f:\\n{instr}\\n\\n\u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u0434:\\n&#171;`mermaid\\n{code}\\n&#171;`&#187;    out, used = query_openrouter(&#8230;)\u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0431\u043b\u043e\u043a MermindAdjust. \u041f\u0440\u0430\u0432\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b.\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u0414\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c \u043a\u0430\u043a\u00a0\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432\u00a0\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u043e\u0434\u0435\u043b\u044c Diagram.class Diagram(models.Model):    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name=&#187;diagrams&#187;)    title = models.CharField(max_length=200, blank=True)    source_text = models.TextField()    type = models.CharField(max_length=20, choices=TYPE_CHOICES, db_index=True)    code = models.TextField()    model_used = models.CharField(max_length=100, blank=True, default=&#187;&#187;)    warnings = models.JSONField(default=list, blank=True)    tags = models.CharField(max_length=200, blank=True)\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 save_diagram. \u041e\u0431\u044a\u0435\u043a\u0442 \u043b\u0438\u0431\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f.@api_view([&#171;POST&#187;])@permission_classes([IsAuthenticated])def save_diagram(request):    data = request.data.copy()    data[&#171;language&#187;] = &#171;ru&#187;    &#8230;    if diagram_id:        obj = get_object_or_404(Diagram, pk=diagram_id, user=request.user)        ser = DiagramSerializer(obj, data=data, partial=True)    else:        ser = DiagramSerializer(data=data)\u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 useMermindLibrary. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a, \u0444\u0438\u043b\u044c\u0442\u0440 \u043f\u043e \u0442\u0438\u043f\u0443, \u0442\u0435\u0433\u0438, rename, delete \u0438 open.const fetchList = useCallback(async () =&gt; {  setLoading(true);  try {    const data = await api.listDiagrams({ q, type, tags, limit });    setItems(data || []);  } finally { setLoading(false); }}, [q, type, tags, limit]);\u0418\u0442\u043e\u0433\u0412 generate_mermaid \u0441\u0435\u0440\u0432\u0435\u0440 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 Mermaid-\u043a\u043e\u0434 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438. \u0412 normalize.py \u043a\u043e\u0434 \u043e\u0447\u0438\u0449\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043a \u0435\u0434\u0438\u043d\u043e\u043c\u0443 \u0432\u0438\u0434\u0443. \u0412 useMermaidRender \u043a\u043e\u0434 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u0431\u0435\u0437 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432. \u0412 model_pool.py \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430. \u0412 adjust_mermaid \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u0412 save_diagram \u0438 list_diagrams \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443.\u0422\u0430\u043a\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u0432\u043e\u043e\u0431\u0449\u0435\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043b\u044f Mermaid \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u043d\u043e\u0439 AI-\u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u043f\u0440\u043e\u0435\u043a\u0442 AI-Chat. \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 https:\/\/habr.com\/ru\/articles\/1061160\/<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-487952","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/487952","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=487952"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/487952\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=487952"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=487952"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=487952"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}