Worklog for task "Format content"

11 сент. 2026 г., 10:50:07

Progress has been recorded on the automatic restoration of old content formatting after migration from draft-editor.

A resolver has been written that takes old content variants (contentText and content) and, via an AI agent, reassembles the publication into Markdown while preserving the original words, meaning, and style.

import { Resource } from '@prisma/client'
import { PrismaContext } from 'server/context/interfaces'
import {
  LLMChatMessageRole,
  LlmModel,
  LlmProvider,
} from 'server/llm/client/interfaces'
import { llmChatCompletionResolver } from 'server/schema/types/LLM/resolvers/chatCompletion'

type ProcessConceptsArgs = {
  ctx: PrismaContext
  resource: Resource
}

export async function processOldContent({
  ctx,
  resource,
}: ProcessConceptsArgs): Promise<string | null | undefined> {
  const systemPrompt = `# You are a rewriter.

You will be given different variants of text for the same publication. This can be plain text or technical text with various technical tags, etc. Your task is to completely reassemble the article, preserving the words, meaning, and style, but format everything in Markdown format.

Code listings may also be provided; they need to be inserted into a code block with the language specified. For example:

\`\`\`js
const foo = 0
\`\`\`

List of all supported languages: 
'': 'Plain Text',
text: 'Plain Text',
js: 'JavaScript',
ts: 'TypeScript',
tsx: 'TypeScript (React)',
jsx: 'JavaScript (React)',
css: 'CSS',
html: 'HTML',
json: 'JSON',
bash: 'Bash',
shell: 'Shell',
sql: 'SQL',
gql: 'GraphQL',
python: 'Python',
markdown: 'Markdown',
django: 'Jinja/Django',
env: 'Environment',
yaml: 'YAML',

If none of them fit, do not explicitly specify the language used.


## !!! Very Important !!! 

It is important to preserve the meaning, words, and style. Your task is to format the article, not to rewrite it in your own words. You must not assume, invent, or change anything. If there is ambiguity somewhere, that ambiguity must remain—your only task is to format the article.

You must return only the formatted article in Markdown format, without any technical accompanying code, text, your comments, etc. Just the article itself. 


## Formatting of the Final Publication

Valid Markdown with html support (though Markdown formatting is preferred)

It is important to take into account that spaces are missing in the text in many places. It is very important to recognize this and insert spaces correctly.

Images are inserted as images with a preview. Links as links.

If there are links referencing the community.modx-cms.ru domain, insert them simply as text rather than a link.
For other domains, insert links as they are. 

If there are images from external file hosting services like joxi, etc., do not insert them; they are outdated. Be sure to insert local images where they are specified.
`

  const {
    content,
    contentText,

    /**
     * we do not take contentV2 because it was generated from content and contentText
     */
    // contentV2
  } = resource

  const peaces = [contentText, content]
    .map((n) =>
      n === null ? null : typeof n === 'string' ? n.trim() : JSON.stringify(n),
    )
    .filter((n) => !!n)

  if (!peaces.length) {
    return ''
  }

  const userPrompt = `## Here is the source data for the article. Rewrite this into the final article in Markdown format, preserving the words, meaning, and style, and return only it to me, without any additional technical text and your comments

---

${peaces.join(`

---

`)}
  
`

  const chatResponse = await llmChatCompletionResolver(
    null,
    {
      input: {
        provider: LlmProvider.OpenRouter,
        messages: [
          {
            role: LLMChatMessageRole.system,
            content: systemPrompt,
          },
          {
            role: LLMChatMessageRole.user,
            content: userPrompt,
          },
        ],
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        model: LlmModel.GEMINI_3_5_FLASH_LITE as any,
      },
    },
    ctx,
  )

  const responseContent = chatResponse.choices?.[0]?.message?.content

  return responseContent
}

Execution statistics:

  • 100 publications — about 5 minutes, budget ~$0.30.
  • 1296 resources — about 43 minutes, budget $2.53.

Overall, the processing worked out fine: the text became noticeably more readable. Prior to this, after migrating from draft-editor, a significant portion of publications had essentially turned into a solid block of text without the original formatting.

At the same time, automated processing does not yield a completely clean result and leaves individual artifacts. For example, the following result can occur:

В create- процессоре она есть, а в update- процессоре ее нет. Уточню, что это ключ массива данных, передаваемых в массив 
s
c
r
i
p
t
P
r
o
p
e
r
t
i
e
s
,
доступныйвнавешиваемыхплагинах
.
Приэтомониещеисоветуютэтотключнеиспользовать
,
ииспользуйтетипаключ
scriptProperties,доступныйвнавешиваемыхплагинах.Приэтомониещеисоветуютэтотключнеиспользовать,ииспользуйтетипаключthis->objectType.

The original HTML of this sentence:

<p>В create- процессоре она есть, а в update- процессоре ее нет. Уточню, что это ключ массива данных, передаваемых в массив <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>c</mi><mi>r</mi><mi>i</mi><mi>p</mi><mi>t</mi><mi>P</mi><mi>r</mi><mi>o</mi><mi>p</mi><mi>e</mi><mi>r</mi><mi>t</mi><mi>i</mi><mi>e</mi><mi>s</mi><mo separator="true">,</mo><mtext>доступныйвнавешиваемыхплагинах</mtext><mi mathvariant="normal">.</mi><mtext>Приэтомониещеисоветуютэтотключнеиспользовать</mtext><mo separator="true">,</mo><mtext>ииспользуйтетипаключ</mtext></mrow><annotation encoding="application/x-tex">scriptProperties, доступный в навешиваемых плагинах. При этом они еще и советуют этот ключ не использовать, и используйте типа ключ </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em"></span><span class="mord mathnormal" style="margin-right:0.0278em">scr</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.1389em">ptP</span><span class="mord mathnormal" style="margin-right:0.0278em">r</span><span class="mord mathnormal">o</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.0278em">er</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal">es</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em"></span><span class="mord cyrillic_fallback">доступныйвнавешиваемыхплагинах</span><span class="mord">.</span><span class="mord cyrillic_fallback">Приэтомониещеисоветуютэтотключнеиспользовать</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em"></span><span class="mord cyrillic_fallback">ииспользуйтетипаключ</span></span></span></span>this-&gt;objectType.</p>

Here, the problem was already embedded in the old HTML: plain text ended up wrapped in KaTeX/MathML, causing the model to attempt interpreting a heavily distorted structure, sometimes duplicating or splitting the text.

Conclusion: automated processing significantly improves the baseline state of the content and is suitable as a mass first pass, but final proofreading and minor manual corrections are still needed.

Volume — about 1,500 topics over 13 years. Considering that most of it is visually quite readable after processing and that mostly individual artifacts and complex formatting in some places require manual attention, the volume of final manual work seems acceptable.

26.08.2026

Fix poor page formatting resulting from a low-quality website update.