Many-in-one AI client.

116 lines
2.8 KiB

  1. import Handlebars from 'handlebars';
  2. export enum MessageRole {
  3. SYSTEM = 'system',
  4. USER = 'user',
  5. ASSISTANT = 'assistant',
  6. }
  7. export interface MessageObject {
  8. role: MessageRole;
  9. content: string;
  10. }
  11. export type Message = string | MessageObject;
  12. const isValidMessageObject = (maybeMessage: unknown): maybeMessage is Message => {
  13. if (typeof maybeMessage !== 'object') {
  14. return false;
  15. }
  16. if (maybeMessage === null) {
  17. return false;
  18. }
  19. const messageObject = maybeMessage as Record<string, unknown>;
  20. return (
  21. Object.values(MessageRole).includes(messageObject.role as MessageRole)
  22. && typeof messageObject.content === 'string'
  23. );
  24. };
  25. export const normalizeChatMessage = (messageRaw: Message | Message[]): MessageObject[] => {
  26. if (typeof messageRaw === 'string') {
  27. return [
  28. {
  29. role: MessageRole.USER,
  30. content: messageRaw,
  31. },
  32. ];
  33. }
  34. if (Array.isArray(messageRaw)) {
  35. return messageRaw.map((message) => {
  36. if (typeof message === 'string') {
  37. return {
  38. role: MessageRole.USER,
  39. content: message,
  40. };
  41. }
  42. if (isValidMessageObject(message)) {
  43. return message;
  44. }
  45. throw new Error('Invalid message format');
  46. });
  47. }
  48. if (isValidMessageObject(messageRaw)) {
  49. return [messageRaw];
  50. }
  51. throw new Error('Invalid message format');
  52. };
  53. export const buildChatFromTranscript = (transcript: string) => {
  54. const parameterized = Handlebars.create().compile(transcript, {
  55. noEscape: true,
  56. ignoreStandalone: true,
  57. strict: true,
  58. preventIndent: true,
  59. });
  60. return (params: Record<string, unknown>) => {
  61. const compiled = parameterized(params);
  62. const prompts = compiled.split('\n---\n');
  63. return prompts.map((prompt) => {
  64. const lines = prompt.trim().split('\n\n');
  65. let lastRole = MessageRole.USER;
  66. return lines.filter((s) => s.trim().length > 0).map((lineRaw) => {
  67. const line = lineRaw.replace(/\n/g, ' ');
  68. const lineCheckRole = line.toLowerCase();
  69. if (lineCheckRole.startsWith('system:')) {
  70. lastRole = MessageRole.SYSTEM;
  71. return {
  72. role: MessageRole.SYSTEM,
  73. content: line.substring('system:'.length).trim(),
  74. };
  75. }
  76. if (lineCheckRole.startsWith('user:')) {
  77. lastRole = MessageRole.USER;
  78. return {
  79. role: MessageRole.USER,
  80. content: line.substring('user:'.length).trim(),
  81. };
  82. }
  83. if (lineCheckRole.startsWith('assistant:')) {
  84. lastRole = MessageRole.ASSISTANT;
  85. return {
  86. role: MessageRole.ASSISTANT,
  87. content: line.substring('assistant:'.length).trim(),
  88. };
  89. }
  90. return {
  91. role: lastRole,
  92. content: line.trim(),
  93. };
  94. });
  95. });
  96. };
  97. };