|
![]() |
|
Registriert seit: 8. Jun 2009 Ort: Bayern 1.153 Beiträge Delphi 11 Alexandria |
#1
Aktuell erhalte ich von der KI keine direkte Anleitung, wie ich ein Problem am besten implementieren kann. Der Code, den ich erhalte, ist meist das Ergebnis einer längeren Iteration mit den entsprechenden Tools.
Kann ich meine Fragen bereits so formulieren, dass ich in der KI-Antwort auch die am besten geeigneten Komponenten (z. B. Value Editor, MemIni-Dateien ...) aufgezeigt bekomme? Welches KI-Tool bietet hierbei die beste Unterstützung? |
![]() |
Registriert seit: 8. Jun 2009 Ort: Bayern 1.153 Beiträge Delphi 11 Alexandria |
#2
Ich habe mir die GUI auch mithilfe der KI und iterativem Fragen erstellen lassen. In der Anlage meine 'Working Version' und die KI-Variante.
|
![]() |
Registriert seit: 27. Nov 2017 2.605 Beiträge Delphi 7 Professional |
#3
Aktuell erhalte ich von der KI keine direkte Anleitung, wie ich ein Problem am besten implementieren kann. Der Code, den ich erhalte, ist meist das Ergebnis einer längeren Iteration mit den entsprechenden Tools.
Kann ich meine Fragen bereits so formulieren, dass ich in der KI-Antwort auch die am besten geeigneten Komponenten (z. B. Value Editor, MemIni-Dateien ...) aufgezeigt bekomme? Welches KI-Tool bietet hierbei die beste Unterstützung? Zur Zeit habe ich 26 KIs, die ich situationsbedingt, kontextbedingt, ... befrage. Jede hat ihre Stärken und Schwächen. Bei komplexeren Sachen frage ich zuerst eine KI und wenn das Ergebnis für mich brauchbar erscheint, frage ich eine andere, ob es wirklich brauchbar ist. Da Du Deine gestellten Fragen nicht zur Verfügung stellst, ist es kaum möglich noch Ratschläge, Tipps, Anregungen zu geben, ob und wie Du Deine Fragestellungen ergänzen könntest. Stelle uns Deine Fragen zur Verfügung, dann können wir ggfls. Deine Frage ![]() Kann ich meine Fragen bereits so formulieren, dass ich in der KI-Antwort auch die am besten geeigneten Komponenten (z. B. Value Editor, MemIni-Dateien ...) aufgezeigt bekomme?
Deine anfangs geposteten Quelltexte hab' ich einfach per Copy&Paste Perplexity.ai zur Verfügung gestellt und gefragt, ob's ok ist (s. o.). Als Ergebnis kam sofort eine Verbesserung heraus. Und da Du ja ChatGPT nutzt, habe ich ChatGPT mal gefragt: ![]() Gibt es noch Verbesserungspotential, Fehler, Redundanzen, ...?
![]() Dein INI-Datei-Editor ist sauber strukturiert, modular aufgebaut und trennt die GUI sinnvoll von der Datenlogik. Sehr durchdacht für ein 2-Stunden-Ergebnis mit KI-Unterstützung! Trotzdem gibt es noch einige Verbesserungsansätze, die du in Betracht ziehen könntest – teils stilistisch, teils funktional:
"Kritik & Verbesserungsvorschläge" "1. Redundanz vermeiden"
Delphi-Quellcode:
// Doppelt aufgerufen:
FDataItemList.LoadFromStringList(StringList); FDataItemList.LoadFromStringList(StringList); ![]() Nur ein Aufruf genügt.
"2. 'FItemIndex' initialisieren" ![]() Der Index ist anfangs nicht gesetzt (Default = 0), was u.*U. zu einem Fehler führt, wenn 'FDataItemList.Count = 0'. Besser:
![]() Und in 'FormCreate' z.*B.:
Delphi-Quellcode:
if FDataItemList.Count > 0 then
FItemIndex := 0; ![]() "3. Typisierung / Eingabebeschränkung vereinheitlichen" Du verwendest Eingabebeschränkung sowohl bei 'KeyDown' als auch bei 'ValidateInput'. Das ist gut – aber du solltest sie ggf. zentralisieren, um Redundanz zu vermeiden. Der Code in 'KeyDown' und 'ValidateInput' ist teilweise doppelt. Optional: Eine Methode 'IsValidCharForType(Char, TInputType): Boolean' auslagern. "4. Verbesserung bei der Darstellung" In 'DisplayActiveItem': ![]() Diese Information sollte ggf. "nicht" editierbar sein, sondern nur angezeigt werden ? 'TLabel' oder 'TReadOnly = True'.
"5. Robustheit bei der Navigation" Aktuell kann der Benutzer mit „Up“ über das Listenende hinaus navigieren:
Delphi-Quellcode:
FItemIndex := FItemIndex + 1;
FItemIndex := Min(FItemIndex, FDataItemList.Count - 1); ![]() Besser wäre:
Delphi-Quellcode:
if FItemIndex < FDataItemList.Count - 1 then
Inc(FItemIndex); ![]() Gleiche Logik bei 'btn_DownClick'.
"6. Wertlose 'edt_propertyValueEnter'-Methode" Aktuell leer:
Delphi-Quellcode:
procedure TForm1.edt_propertyValueEnter(Sender: TObject);
begin /// check for valid user inputs and save data end; ![]() Diese Methode könnte entfernt oder genutzt werden, z.*B. zum "Zurückspeichern vorheriger Eingaben", wenn der Benutzer „vorspult“ ohne zu bestätigen.
"7. JSON-Ausgabe könnte mit 'Indent' formatiert sein" Zur besseren Lesbarkeit in der 'TMemo': ![]() Erfordert ggf. alternative Methode oder JSON-Helper.
"8. Enum-Parsing konsistent" Du benutzt einmal: ![]() und einmal:
![]() ? Vereinheitlichen. Entweder mit oder ohne 'it'-Prefix. Ggf. Hilfsfunktion 'TInputTypeHelper.ToString/FromString'.
"9. Erweiterbarkeit" Wenn du weitere Typen einführen willst (z.*B. 'itColor', 'itPath'), wäre es hilfreich, für jeden Typ Methoden einzuführen:
Delphi-Quellcode:
function TInputType.IsValid(const Value: string): Boolean;
function TInputType.AllowedChars: TCharSet; ![]() "10. Vorschlag für 'TDataItem.ToJSON'"
Der Code zum Erzeugen des JSON-Objekts liegt aktuell in der List-Klasse. Objektorientierter wäre: ![]() Dann sähe 'TDataItemList.GetAsJSON' so aus:
Delphi-Quellcode:
for DataItem in Self do
JSONArray.AddElement(DataItem.ToJSON); ![]() Positives
' "Entkopplung GUI <-> Logik:" sehr gelungen. ' "Modularisierung:" Datenhaltung, Validierung und Darstellung getrennt. ' "Fehlertoleranz:" Validierung bei Eingabe, inklusive Rückmeldung an den User. ' "JSON-Schnittstelle:" Praktisch für Persistenz. ' "Erweiterbarkeit:" Die 'TInputType'-Konstruktion lässt sich später leicht erweitern. Fazit Du hast eine solide, flexible Grundlage geschaffen – vor allem, weil du "nicht" starr an einer FMX-basierten GUI festhängst. Die Daten- und Logikebene lässt sich leicht auch über CLI, Web-Frontend oder Kommandozeile verwenden. Genau richtig für einen INI/Settings-Editor mit Validierung. Wenn du möchtest, helfe ich dir gern bei: ' Unit-Tests für die Datamodule ' Refactoring zu MVVM/MVC ' Zusätzliche Typen/Validierungen (z.*B. 'itPath', 'itEnum', …) ' Auto-Vervollständigung oder Tooltips für den Editor Willst du in diese Richtung noch weiter optimieren? ![]() Könnten Sie mir bitte eine, nach Ihren Anregungen, Kriterien, ... überarbeitete Version der Quelltexte zur Verfügung stellen?
![]() Gerne! Im Folgenden findest du die überarbeiteten Versionen beider Units, basierend auf den genannten Verbesserungsvorschlägen:
"1. 'Unit_TDataModule.pas' – überarbeitet"
Delphi-Quellcode:
unit Unit_TDataModule;
interface uses System.Classes, System.SysUtils, System.Generics.Collections, System.JSON, System.IOUtils, TypInfo; type TInputType = (itString, itNumber, itBoolean); TDataItem = class private FName: string; FDataType: TInputType; FValue: string; FDefaultValue: string; public property Name: string read FName write FName; property DataType: TInputType read FDataType write FDataType; property Value: string read FValue write FValue; property DefaultValue: string read FDefaultValue write FDefaultValue; constructor Create(const AName: string; ADataType: TInputType; const ADefaultValue: string); procedure ResetToDefault; function ToJSON: TJSONObject; end; TDataItemList = class(TObjectList<TDataItem>) private function GetAsJSON: string; public property AsJSON: string read GetAsJSON; procedure LoadFromStringList(AStringList: TStringList); procedure SaveToFile(const AFileName: string); procedure LoadFromFile(const AFileName: string); end; function ParseInputType(const DataTypeStr: string): TInputType; function InputTypeToString(DataType: TInputType): string; implementation function ParseInputType(const DataTypeStr: string): TInputType; begin if SameText(DataTypeStr, 'Boolean') then Result := itBoolean else if SameText(DataTypeStr, 'Number') then Result := itNumber else if SameText(DataTypeStr, 'String') then Result := itString else raise Exception.CreateFmt('Unknown data type: %s', [DataTypeStr]); end; function InputTypeToString(DataType: TInputType): string; begin case DataType of itBoolean: Result := 'Boolean'; itNumber: Result := 'Number'; itString: Result := 'String'; else Result := ''; end; end; { TDataItem } constructor TDataItem.Create(const AName: string; ADataType: TInputType; const ADefaultValue: string); begin inherited Create; FName := AName; FDataType := ADataType; FDefaultValue := ADefaultValue; FValue := ADefaultValue; end; procedure TDataItem.ResetToDefault; begin FValue := FDefaultValue; end; function TDataItem.ToJSON: TJSONObject; begin Result := TJSONObject.Create; Result.AddPair('Name', Name); Result.AddPair('DataType', InputTypeToString(DataType)); Result.AddPair('Value', Value); Result.AddPair('DefaultValue', DefaultValue); end; { TDataItemList } function TDataItemList.GetAsJSON: string; var JSONArray: TJSONArray; DataItem: TDataItem; begin JSONArray := TJSONArray.Create; try for DataItem in Self do JSONArray.AddElement(DataItem.ToJSON); Result := JSONArray.Format(2); // formatiert für bessere Lesbarkeit finally JSONArray.Free; end; end; procedure TDataItemList.LoadFromStringList(AStringList: TStringList); var i: Integer; Parts: TArray<string>; begin for i := 0 to AStringList.Count - 1 do begin Parts := AStringList[i].Split(['|']); if Length(Parts) <> 3 then raise Exception.CreateFmt('Invalid format in line %d: %s', [i + 1, AStringList[i]]); Add(TDataItem.Create(Parts[0], ParseInputType(Parts[1]), Parts[2])); end; end; procedure TDataItemList.SaveToFile(const AFileName: string); begin TFile.WriteAllText(AFileName, AsJSON); end; procedure TDataItemList.LoadFromFile(const AFileName: string); var JSONArray: TJSONArray; JSONObject: TJSONObject; JSONString: string; i: Integer; begin JSONString := TFile.ReadAllText(AFileName); JSONArray := TJSONObject.ParseJSONValue(JSONString) as TJSONArray; if not Assigned(JSONArray) then raise Exception.Create('Invalid JSON format.'); try for i := 0 to JSONArray.Count - 1 do begin JSONObject := JSONArray.Items[i] as TJSONObject; Add(TDataItem.Create( JSONObject.GetValue<string>('Name'), ParseInputType(JSONObject.GetValue<string>('DataType')), JSONObject.GetValue<string>('DefaultValue') )); Self.Last.Value := JSONObject.GetValue<string>('Value'); end; finally JSONArray.Free; end; end; end. ![]() "2. 'StudyDatamodule.pas' – überarbeitet"
Delphi-Quellcode:
unit StudyDatamodule;
interface uses System.SysUtils, System.Classes, System.Types, System.UITypes, System.Rtti, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.Edit, Unit_TDataModule, Math; type TForm1 = class(TForm) mmo1: TMemo; btn_InitList: TButton; edt_propertyValue: TEdit; lbl_PropertyName: TLabel; btn_Up: TButton; btn_Down: TButton; edt_Datatype: TEdit; btn_ListasJSON: TButton; procedure FormCreate(Sender: TObject); procedure btn_UpClick(Sender: TObject); procedure btn_DownClick(Sender: TObject); procedure edt_propertyValueExit(Sender: TObject); procedure edt_propertyValueKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); procedure btn_ListasJSONClick(Sender: TObject); private FDataItemList: TDataItemList; FItemIndex: Integer; procedure DisplayActiveItem(Index: Integer); procedure SaveInputToItem; function ValidateInput(const Value: string; DataType: TInputType): Boolean; public end; var Form1: TForm1; implementation {$R '.fmx} procedure TForm1.FormCreate(Sender: TObject); var StringList: TStringList; begin StringList := TStringList.Create; FDataItemList := TDataItemList.Create(True); try StringList.Add('EnableLogging|Boolean|true'); StringList.Add('MaxLogFileSize|Number|1048576'); StringList.Add('LogFilePath|String|C:\Logs\app.log'); StringList.Add('Theme|String|dark'); StringList.Add('RetryDelay|Number|5'); FDataItemList.LoadFromStringList(StringList); if FDataItemList.Count > 0 then begin FItemIndex := 0; DisplayActiveItem(FItemIndex); end; finally StringList.Free; end; end; procedure TForm1.DisplayActiveItem(Index: Integer); var DataItem: TDataItem; begin if (Index < 0) or (Index >= FDataItemList.Count) then Exit; DataItem := FDataItemList[Index]; lbl_PropertyName.Text := DataItem.Name; edt_propertyValue.Text := DataItem.Value; edt_Datatype.Text := InputTypeToString(DataItem.DataType); end; procedure TForm1.SaveInputToItem; var DataItem: TDataItem; UserInput: string; begin if (FItemIndex < 0) or (FItemIndex >= FDataItemList.Count) then Exit; DataItem := FDataItemList[FItemIndex]; UserInput := Trim(edt_propertyValue.Text); if not ValidateInput(UserInput, DataItem.DataType) then begin ShowMessage(Format('Invalid input for %s. Expected a %s.', [DataItem.Name, InputTypeToString(DataItem.DataType)])); edt_propertyValue.SetFocus; Exit; end; DataItem.Value := UserInput; end; function TForm1.ValidateInput(const Value: string; DataType: TInputType): Boolean; var TempFloat: Double; TempBool: Boolean; begin case DataType of itString: Result := True; itNumber: Result := TryStrToFloat(Value, TempFloat); itBoolean: Result := TryStrToBool(Value, TempBool); else Result := False; end; end; procedure TForm1.btn_UpClick(Sender: TObject); begin if FItemIndex < FDataItemList.Count - 1 then begin SaveInputToItem; Inc(FItemIndex); DisplayActiveItem(FItemIndex); end; end; procedure TForm1.btn_DownClick(Sender: TObject); begin if FItemIndex > 0 then begin SaveInputToItem; Dec(FItemIndex); DisplayActiveItem(FItemIndex); end; end; procedure TForm1.edt_propertyValueExit(Sender: TObject); begin SaveInputToItem; end; procedure TForm1.edt_propertyValueKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); var DataItem: TDataItem; begin if (FItemIndex < 0) or (FItemIndex >= FDataItemList.Count) then Exit; DataItem := FDataItemList[FItemIndex]; case DataItem.DataType of itNumber: if not (KeyChar in ['0'..'9', '.', '-', #8]) then KeyChar := #0; itBoolean: if not (KeyChar in ['t','r','u','e','f','a','l','s','e', #8]) then KeyChar := #0; end; end; procedure TForm1.btn_ListasJSONClick(Sender: TObject); begin mmo1.Lines.Text := FDataItemList.AsJSON; end; end. ![]() Wenn du möchtest, kann ich daraus auch ein kompaktes Demo-Projekt (.dproj + .fmx) ableiten oder Unit-Tests für 'TDataItemList' schreiben. Wäre das hilfreich für dich?
|
![]() |
Registriert seit: 10. Jan 2006 Ort: Offenbach 3.826 Beiträge Delphi 12 Athens |
#4
Ich kann mich nur wiederholen: Interessant ist an dieser Stelle eigentlich nur der Prompt, und gegebenenfalls die Verfeinerungen, die noch formuliert wurden. Der Wall of Code ist mMn irrelevant und für die Allgemeinheit zählt nur, ob du selbst mit dem Ergebnis zufrieden bist. Vorschlag zum letzten Punkt: Pack doch alles in ein Zip (inklusive Kompilat) und jeder kann sich so viel davon ansehen, wie er mag.
Oliver
Geändert von Sherlock (Morgen um 16:78 Uhr) Grund: Weil ich es kann |
![]() |
Registriert seit: 8. Jun 2009 Ort: Bayern 1.153 Beiträge Delphi 11 Alexandria |
#5
Als Anmerkung: Auch die Portierung von FMX auf VCL mittels KI hat funktioniert – natürlich mit iterativem Nachfragen. Die *.dfm-Datei hatte zwar Fehler, diese konnten jedoch einfach/schnell behoben werden.
Meine Fragen und Prompts werde ich beim nächsten Projekt einmal aufheben und anschließend teilen. Das gestellte Problem war einfach, die Algorithmus-Komplexität gleich null. Das resultierende Code-Design würde ich als sauber und solide bezeichnen. Ja, Beispiele, wie man auch komplexere Probleme mit KI effektiv löst, wären hilfreich, um den eigenen Arbeitsstil mit diesen Tools zu verbessern. Aber wenn man keine klare Vorstellung davon hat, was man am Ende erreichen möchte, sehe ich mit den aktuellen Tools keinen einfachen Weg, etwas Sinnvolles zu erstellen. Arbeitszeit:
|
![]() |
Registriert seit: 14. Apr 2009 675 Beiträge |
#6
Die Frage ist auch, wie lange haettest Du ohne KI benoetigt.
Achtung: Bin kein Informatiker sondern komme vom Bau.
|
![]() |
Registriert seit: 5. Nov 2014 Ort: Baden Württemberg 147 Beiträge Delphi 12 Athens |
#7
Ich kann mich nur wiederholen: Interessant ist an dieser Stelle eigentlich nur der Prompt, und gegebenenfalls die Verfeinerungen, die noch formuliert wurden. Der Wall of Code ist mMn irrelevant und für die Allgemeinheit zählt nur, ob du selbst mit dem Ergebnis zufrieden bist. Vorschlag zum letzten Punkt: Pack doch alles in ein Zip (inklusive Kompilat) und jeder kann sich so viel davon ansehen, wie er mag.
- Wie war der initiale Prompt, bzw. die initiale Arbeitsanweisung - welche Schritte wurden unternommen um das Ergebnis zu verfeinern / verbessern Du könntest den Code auch in ein Github repo hochladen inkl. den verwendeten Prompts. Das wäre interessant.
Bastian
|
![]() |
Registriert seit: 15. Sep 2008 Ort: Dubai 752 Beiträge Delphi 10.3 Rio |
#8
Zur Zeit habe ich 26 KIs, die ich situationsbedingt, kontextbedingt, ... befrage. Jede hat ihre Stärken und Schwächen. Bei komplexeren Sachen frage ich zuerst eine KI und wenn das Ergebnis für mich brauchbar erscheint, frage ich eine andere, ob es wirklich brauchbar ist.
Du verschwendest Zeit die Du nutzen könntest um dich selber zu bilden, nur um jemand anderes mit Fragen zu löchern dessen Antowrten danach noch nichtmal Sinn ergeben müssen. Das ist nichts weiter als eine Horde Affen die auf einem Auge bild sind und mit einem Bein aneinandergekettet. Während sie versuchen wild durcheinandern zu rennen stellst du deine Frage und bekommen eine Banane in die Menge geworfen. Einige schreien wild los, das Geschrei zusammen hört sich für dich an wie eine Antwort auf deine Frage. Das ist keine Inteligenz, das ist Zufall der Zufällig eingeschränkt wird. KI ist noch schlimmer als im Forum zu warten, denn die Dinger reflektieren nicht. Wie sollen sie auch, sie haben ja selbst keine Möglichkeit zu erkennen ob die Schwachsinn erzählen.
Stefan
Nur die Besten sterben jung ![]() A constant is a constant until it change. |
![]() |
Registriert seit: 27. Nov 2017 2.605 Beiträge Delphi 7 Professional |
#9
Zur Zeit habe ich 26 KIs, die ich situationsbedingt, kontextbedingt, ... befrage. Jede hat ihre Stärken und Schwächen. Bei komplexeren Sachen frage ich zuerst eine KI und wenn das Ergebnis für mich brauchbar erscheint, frage ich eine andere, ob es wirklich brauchbar ist.
Du verschwendest Zeit die Du nutzen könntest um dich selber zu bilden, nur um jemand anderes mit Fragen zu löchern dessen Antowrten danach noch nichtmal Sinn ergeben müssen. Das ist nichts weiter als eine Horde Affen die auf einem Auge bild sind und mit einem Bein aneinandergekettet. Während sie versuchen wild durcheinandern zu rennen stellst du deine Frage und bekommen eine Banane in die Menge geworfen. Einige schreien wild los, das Geschrei zusammen hört sich für dich an wie eine Antwort auf deine Frage. Das ist keine Inteligenz, das ist Zufall der Zufällig eingeschränkt wird. KI ist noch schlimmer als im Forum zu warten, denn die Dinger reflektieren nicht. Wie sollen sie auch, sie haben ja selbst keine Möglichkeit zu erkennen ob die Schwachsinn erzählen. ![]() Normalerweise weiß ich bei den gestellten Fragen schon recht genau, was da als Ergebnis rauskommen sollte. Und um die Ergebnisse mehrerer KIs miteinander vergleichen zu können und zu beurteilen, ob sie was Vernünftiges liefern oder eher nur Halluzination, musst Du vom Thema schon so einiges verstehen, sonst kannst Du da keine Beurteilung vornehmen. Und da sich die Trainingsdaten der einzelnen KIs z. T. massiv unterscheiden und unterschiedlich alt sind, können die Ergebnisse schon mal deutlich voneinander abweichen. Bei 'ner Frage nach Delphi-Quellcode mag das nicht so auffallen, wenn es um wissenschaftliche Fragen geht aber schon sehr deutlich. Und ich benutze KIs auch wie Suchmaschinen, die liefern bei mehreren Suchbegriffen oft Ergebnisse, bei denen die einzelnen Suchbegriffe mit Oder verknüpft sind, was nicht unbedingt gerade zielführend sein muss. Die per Dokumentation vorgegebenen Optionen für die Einschränkung der Suche, die Suchbegriffsverknüpfungen mit und, mehreren zusammenhängend Wörtern ..., werden von Suchmaschinen gerne mal ignoriert bzw. sie meinen, man wolle was anderes fragen, als man fragt. KIs haben hier den Vorteil, wenn man eine vernünftig ausformulierte Frage stellt, bekommt man auch entsprechende Ergebnisse. Und die muss man selbstverständlich überprüfen. Und ohne Ahnung klappt das mit dem Überprüfen nicht. Antworten von KIs werden von mir nicht übernommen frei nach dem Motto: "Wenn die KI das sagt, wird's schon stimmen." Da geht's bei mir eher nach dem Motto: Selbstdenken macht schlau bzw. hält die grauen Zellen in Schwung. Wirklich benötigen tu ich die KIs eigentlich für nichts, mit denen beschäftige ich mich (meist bei schlechtem Wetter) nur Just For Fun. |
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |